概述:
在iOS 13、iOS 14以后,UICollectionView 进行了很多变化。其中最主要的两点就是:
- UICollectionViewCompositionalLayout
组合布局,一种集合视图布局,它是可组合的、灵活的和快速的,允许您为内容构建任何类型的视觉安排。 - UICollectionViewDiffableDataSource
一种特殊类型的数据源,它提供了简单高效地管理集合视图数据和用户界面更新所需的行为。
一、UICollectionViewCompositionalLayout介绍
自从iOS 6出现了UICollectionView,在iOS 13以前我们创建并显示,是通过UICollectionViewFlowLayout布局做到的。
其中UICollectionViewFlowLayout拥有
@property (nonatomic) CGFloat minimumLineSpacing;
@property (nonatomic) CGFloat minimumInteritemSpacing;
@property (nonatomic) CGSize itemSize;
@property (nonatomic) CGSize estimatedItemSize API_AVAILABLE(ios(8.0)); // defaults to CGSizeZero - setting a non-zero size enables cells that self-size via -preferredLayoutAttributesFittingAttributes:
@property (nonatomic) UICollectionViewScrollDirection scrollDirection; // default is UICollectionViewScrollDirectionVertical
@property (nonatomic) CGSize headerReferenceSize;
@property (nonatomic) CGSize footerReferenceSize;
@property (nonatomic) UIEdgeInsets sectionInset;
等属性,来控制每一个item在UICollectionView中的显示。
到了iOS 13,苹果引入了UICollectionViewCompositionalLayout用来设置UICollectionView的布局。
总览
合成布局是一种集合视图布局。 它的设计具有可组合性,灵活性和快速性,可让您通过将每个较小的组件组合(或合成)成完整的布局来为内容构建任何形式的视觉布置。
合成布局由一个或多个部分组成,这些部分将布局分解为不同的视觉分组。 每个部分都是由各个项目组成的组,这些项目是要显示的最小数据单元。 一组人员可以将其项目布置在水平行,垂直列或自定义排列中。
您可以通过以下方式将组件组合在一起:从项目(item)到一个组(group),从组到一个节(section),最后到完整的布局。
UICollectionViewCompositionalLayout总览
创建网络布局的代码如下:
private func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
知识点:1.NSCollectionLayoutSize为:
每个组件的尺寸(NSCollectionLayoutDimension)的一对宽、高的尺寸。集合视图布局的每个组件都有一个明确的大小。
原文:
A size is a pair of dimensions (NSCollectionLayoutDimension): a width dimension and a height dimension. Every component of a collection view layout has an explicit size.
知识点:2.NSCollectionLayoutDimension为:
集合视图中项目尺寸(宽度或高度)的单个尺寸。
集合视图中的每个项目都有一个明确的宽度尺寸和高度尺寸,这两个尺寸组合在一起定义了项目的尺寸(NSCollectionLayoutSize)。
您可以使用绝对值,估计值或分数值来表示项目的尺寸。
使用绝对值来指定确切的尺寸,例如44 x 44点正方形:
let absoluteSize = NSCollectionLayoutSize(widthDimension: .absolute(44),
heightDimension: .absolute(44))
如果内容的大小可能在运行时发生更改(例如,在加载数据时或响应系统字体大小更改时),请使用估计值。 您提供初始估计大小,然后系统会计算实际值。
(这个应该是为了根据内容自适应使用的)
let estimatedSize = NSCollectionLayoutSize(widthDimension: .estimated(200),
heightDimension: .estimated(100))
使用分数值来定义相对于商品容器尺寸的值。 此选项简化了宽高比的指定。
//以下项目的宽度和高度都等于其容器(父view)宽度的20%,从而创建一个随容器大小变化而增大和缩小的正方形。
let fractionalSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalWidth(0.2))

知识点:3. 使用horizontal(layoutSize:subitem:count:)创建布局
使用count参数中指定的确切项数创建一个组。这种方法简化了精确指定一个组包含多少项的过程。在这种情况下,count参数优先于itemSize,并且自动计算项大小以适合指定的项数。
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)

知识点:4. NSCollectionLayoutItem
集合视图布局的最基本组成部分
总览
一项是关于如何在集合视图中调整大小,空间和安排单个内容的蓝图。一个项目代表在屏幕上呈现的单个视图。通常,项目是一个单元格,但项目可以是补充视图,例如页眉,页脚和其他装饰。
例如,在“照片”应用中,一个项目可能代表一张照片。在App Store应用程序中,项目可能是一个单元格,用于在特色应用程序列表中显示有关单个应用程序的信息,例如应用程序图标,应用程序名称,标语和下载按钮。
(见上图UICollectionViewCompositionalLayout总览)
知识点:5. NSCollectionLayoutGroup
一组项目的容器,用于沿着路径布置项目。
总览
组确定集合视图中的items如何相对于彼此进行布局。可以将items按照行、列或自定义布置到Group中。
一个组确定有关如何相互呈现项目的规则,但其本身不呈现任何内容。
例如,在“照片”应用中,一组项目是一排照片。在App Store应用程序中,组可能是垂直列中排列的一列单元格(项目)。
(见上图UICollectionViewCompositionalLayout总览)
因为组是NSCollectionLayoutItem的子类,所以它的行为类似于Item。您可以将一个组与其他项目和组组合成更复杂的布局。
多个NSCollectionLayoutGroup组合
多个group组合,以上显示可以用代码实现为:
//先定义左边大的主item,设置宽度占整个group的70%,高度为100%
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
//再定义右边两个小的为一组,按照竖排列,2个一组
//两个小的item,设置宽度占整个group的100%,高度为50%
let trailingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.5)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let trailingGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0)),
subitem: trailingItem, count: 2)
//再把上面的leadingItem和trailingGroup放到一个组里
let nestedGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.4)),
subitems: [leadingItem, trailingGroup])
知识点:6. NSCollectionLayoutSection
一个将一个个Group组合为不同的可视分组的容器。
总览
集合视图布局具有一个或多个section。 各section提供了一种将布局分成不同部分的方法。
每个section可以具有与集合视图中其他section相同或不同的布局。 section的布局由用于创建section的组(NSCollectionLayoutGroup)的属性确定。
例如,在“照片”应用中,“年份”页面中的每个部分都使用相同的布局。 在App Store中,“应用程序”页面显示具有不同内容排列的几个部分。
(见上图UICollectionViewCompositionalLayout总览)
通过以上1、2、3、4、5、6各部分,就可以组成一个UICollectionViewCompositionalLayout了。
let layout = UICollectionViewCompositionalLayout(section: section)
var collectionView: UICollectionView! = nil
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
就可以初始化一个collectionView了。
如果拥有多个section,并且section之间显示还不一样,可以用如下方法实现
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
if sectionIndex == 0{
let section = NSCollectionLayoutSection(group: Group0)
return section
} else {
let section = NSCollectionLayoutSection(group: Group1)
return section
}
}
return layout
}
知识点:7. NSCollectionLayoutSupplementaryItem
可以使用NSCollectionLayoutSupplementaryItem给item, group, section来添加角标等信息。
用于向集合视图中的项目添加额外的视觉装饰(例如徽章或框架)的对象。


例子:定义UICollectionViewCompositionalLayout时,在每个item上加角标
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3))
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20),
heightDimension: .absolute(20))
let badge = NSCollectionLayoutSupplementaryItem(
layoutSize: badgeSize,
elementKind: "badge-element-kind",
containerAnchor: badgeAnchor)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])
给UICollectionViewDiffableDataSource添加属性
let supplementaryRegistration = UICollectionView.SupplementaryRegistration<BadgeSupplementaryView>(elementKind: "badge-element-kind") {
(badgeView, string, indexPath) in
let hasBadgeCount = (indexPath.row%2) > 0//奇数个时显示角标
// Set the badge count as its label (and hide the view if the badge count is zero).
badgeView.label.text = "\(indexPath.row)"
badgeView.isHidden = !hasBadgeCount
}
dataSource.supplementaryViewProvider = {
return self.collectionView.dequeueConfiguredReusableSupplementary(using: supplementaryRegistration, for: $2)
}
得到结果为:

知识点:8. 用UICollectionView实现TableView样式
在 iOS14 中,用 CollectionView 实现一个 TableView 样式的列表非常简单。
func createLayout() -> UICollectionViewLayout {
let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
return UICollectionViewCompositionalLayout.list(using: configuration)
}
其中insetGrouped为样式。还有plain、grouped、insetGrouped、sidebar、sidebarPlain等样式。
二、注册初始化Cell
iOS 14提供了一个新方法:UICollectionView.CellRegistration用来注册Cell
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { cell, indexPath, item in
var contentConfiguration = cell.defaultContentConfiguration()
contentConfiguration.text = "\(item)"
contentConfiguration.textProperties.color = .lightGray
cell.contentConfiguration = contentConfiguration
}
三、UICollectionViewDiffableDataSource
这是iOS 13新提出的方法。
用于管理数据和为集合视图提供单元格的对象。
概述
diffable数据源对象是一种特殊类型的数据源,与collection view一起工作。它提供了以简单、高效的方式管理collection view数据和UI更新所需的行为。它还符合UICollectionViewDataSource协议,并为该协议的所有方法提供实现。
要用数据填充collection view,请执行以下操作:
- 将可扩散数据源连接到collection view。
- 实现单元提供程序以配置collection view的cells 。
- 生成数据的当前状态。
- 在UI中显示数据。
要将可扩散数据源连接到集合视图,可以使用其init(collectionView:cellProvider:)与要在view中传递数据的初始值设定项相关联。您还将传入一个cell提供程序,在该程序中配置每个单元格以确定如何在UI中显示数据。
知识点:1. 将Diffable Data Source连接到collection view
dataSource = UICollectionViewDiffableDataSource.init(collectionView: collectionView, cellProvider: {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
})
知识点:2. NSDiffableDataSourceSnapshot
然后,生成数据的当前状态,并通过构造和应用快照在UI中显示数据:
视图中数据在特定时间点的状态表示。
概述
Diffable data sources使用快照为collection views 和 table views提供数据。通过快照,可以设置在视图中显示的数据的初始状态,然后更新该数据。
快照中的数据由要显示的sections和items组成,按要显示的特定顺序排列。您可以通过添加adding、删除deleting或移动moving sections和items来配置要显示的内容。重要
每个sections和items都必须具有符合哈希协议的唯一标识符。
要使用快照在视图中显示数据,请执行以下操作:
创建快照并用要显示的数据的状态填充它。
应用快照以反映UI中的更改。
您可以通过以下方式之一创建和配置快照:
创建一个空快照,然后向其附加节和项。
通过调用diffable数据源的snapshot()方法获取当前快照,然后修改该快照以反映要显示的数据的新状态。
var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
snapshot.appendSections([.main])
snapshot.appendItems(Array(0..<30))
dataSource.apply(snapshot, animatingDifferences: false)
其中Section具有符合哈希协议的唯一标识符
enum Section {
case main
}
网友评论