WWDC19开始,UICollectionView相比之前简单的DataSource、Delegate的形式多了一种新写法,通过DiffableDataSource支持了局部刷新、cell的方便增删改等;通过UICollectionViewCompositionalLayout
帮助开发者更简单地去构建复杂布局。下面给出了一些简单的特性实践,需要注意的是,也是最关键的一点,这些特性仅支持iOS13+ (部分iOS14+)
1. UICollectionViewCompositionalLayout
我们在写现有的layout的时候,了解到目前的collectionView有item和section两个维度。而在modern collectionview里,在这两者之间增加了一个Group纬度。为的就是更方便地创造出更复杂的布局:
有了group以后,我们可以嵌套多种排列的Item。这样有利有弊,好处不言而喻,坏处当然是理解起来又要多一个层级。
1.1 Group
-
Group 是新引入的组成布局的基本单元,它有三种形式
水平(horizontal)
垂直(vertical)
自定义(custom)
Group 的大小页需要通过
NSCollectionLayoutSize
决定。如果是自定义布局,需要传入一个NSCollectionLayoutGroupCustomItemProvider
来决定这个 Group 中 Item 的布局方式。通过 Group 可以在同一个 Section 中实现不同的布局方式。定义如下:
class NSCollectionLayoutGroup: NSCollectionLayoutItem {
class func horizontal(layoutSize: NSCollectionLayoutSize, subitems: [NSCollectionLayoutItem]) -> Self
class func vertical(layoutSize: NSCollectionLayoutSize, subitems: [NSCollectionLayoutItem]) -> Self
class func custom(layoutSize: NSCollectionLayoutSize, itemProvider: NSCollectionLayoutGroupCustomItemProvider) -> Self
}
具体的使用参见Demo (文末给到)
注:横滑样式可以用Group的方式实现了
1.2 Section && Item
section也迎来了一些更新
1.2.1 可以传入group进行初始化
let section = NSCollectionLayoutSection(group: group)
1.2.2 加入NSCollectionLayoutAnchor 实现小红点或者未读消息视图
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing],
fractionalOffset: CGPoint(x: 0.5, y: -0.5))
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(16),
heightDimension: .absolute(16))
let badge = NSCollectionLayoutSupplementaryItem(layoutSize: badgeSize, elementKind: "badge", containerAnchor: badgeAnchor)
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])
以上可以通过在Item上设置SupplementaryItem实现小红点。
1.2.3 控制Section的滚动模式
UICollectionLayoutSectionOrthogonalScrollingBehavior
现在可以控制Section的滚动模式
主要是根据这个参数可以控制Section滚动时的表现。比如翻页的按页滚动、或者不减速滚动等,总体而言更多样了。
关于layout先到这里。还有一些更新和实例在官方Demo中均有体现。
2.DiffableDataSource
类似于DiffableDataSource的概念其实我们在IGList中有接触,通过为数据源设置不同的identifier实现数据源和UI的绑定。在刷新数据源时,只要重新计算diff,计算进行局部刷新,可以大大提高UICollectionView的性能。 在modern collectionview 中也引入了新的DiffableDataSource的概念,我们不再需要设置Datasource通过一系列数据源方法返回,而是通过一开始的绑定,通过snapshot进行刷新。
比如我要实现这样一个CollectionView:
三个section,每个section有自己的Item
具体代码如下:
Layout部分:
这里虽然看起来是一个tableview,我们也可以使用tableview实现,但是我选择使用iOS14的新属性:
UICollectionLayoutListConfiguration
今年WWDC20的session里,比较大的两个更新就是增加了outline展开收起样式的支持和新增UItableViewlike的 collectionView。也就意味着collectionView可以做UItableView的事情了(不再需要UItableView),同样,该样式还支持左滑删除等tableview的特色属性。
实现起来也很简单:
func createLayout() -> UICollectionViewLayout {
let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
return UICollectionViewCompositionalLayout.list(using: configuration)
}
数据源部分:
func configureDataSource() {
//设置cell的数据处理(绑定UI元素到数据源)
let cellRegi = UICollectionView.CellRegistration {
(cell, indexPath, emoji) in
var contentConfiguration = UIListContentConfiguration.valueCell()
contentConfiguration.text = emoji;
cell.contentConfiguration = contentConfiguration
cell.accessories = [.disclosureIndicator()]
}
//设置dataSource
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
(collectionView, indexPath, item) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegi, for: indexPath, item: item.title)
}
}
在需要刷新的时候,使用 DataSourceSnapshot 处理变更后的数据源,其有 append、delete、move、insert 等方法。DiffableDataSource 通过调用自身 apply 方法将 DataSourceSnapshot 变更后的数据更新同步到 UICollectionView。
主要是引入了SnapShot ,快照的概念,我理解是处理DataSource 的一个数据中心。
func applyInitialSnapshots() {
//有 1 2 3 三个section
for category in ["1","2","3"]{
//每个section有两个item
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot- ()
let items = [Item(title: "item" + category),Item(title: "item2" + category)];
sectionSnapshot.append(items)
dataSource.apply(sectionSnapshot, to: category, animatingDifferences: false)
}
}
关于列表元素的增删改操作如下:
通过数据快照,可以很方便地对列表进行局部刷新增删改查操作。而操作Item的原子元素其实是数据源。
var snap = dataSource.snapshot()
snap.deleteSections(["2"]);
snap.appendItems([Item(title: "item Insert")], toSection: "1")
snap.deleteItems([Item(title: "item23")]);
dataSource.apply(snap);
在这里我对Item这个model的定义是根据title的name区分identifier。所以传入一个new 的item,只要id一样就是同一个:
struct Item: Hashable {
static func == (lhs: TestViewController.Item, rhs: TestViewController.Item) -> Bool {
return lhs.title == rhs.title;
}
let title: String
init(title: String) {
self.title = title
}
}
3.其他
Modern Collection Views 是iOS13推出iOS14持续更新的新概念,有兴趣的童鞋可以继续去https://developer.apple.com/news/?id=d9kd3m7g 看一下Session和Video,我这里只提到了部分更新,其实WWDC19 + 20 关于CollectionView的改造还是比较多的。总结出整体的大趋势是 去TableView化、推动DiffDataSource和支持更复杂的布局样式。
以上~
//上面截图的CollectionView的实现(完整代码)
官方demo:
https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views
题外话:
WWDC20可以看出苹果的未来在开发上的方向肯定是扶正Swift以及SwfitUI,iOS14 的Widgets仅支持SwiftUI开发。同样属于新特性的app clips 在官方演示中也是只用了SwfitUI(鉴于特殊性还是支持UIKit)。随着Swift的ABI稳定,相信苹果还会推出更多新特性新功能。