UICollectionViewCompositionalLayout
参考
- 215: Advances in Collection View Layout 中的讲解
- UICollectionView 全新布局框架:UICollectionViewCompositionalLayout
在iOS6中引入的UICollectionView,是一个基于行的布局系统(line-based layout system)。但是现在由于设备的异构型的增强和屏幕大小的改变,情况越来越复杂
所以推出了Compositional Layout 组合布局,其特点
- Composable - 可组合的,使用简单的东西来制作复杂的东西
- Flexible - 可以用组合布局来写任何的布局
- Fast
Compositional Layout是一种描述性API,组合布局使用小的布局组合成更大的布局
Compositional Layout组合布局,不需要使用子类来继承,只需要创建一些东西,将它们组合起来
例如,创建一个简单的list
// Create a List by Specifying Three Core Components: Item, Group and Section
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44.0))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
item被加入到group中,group被加入到section中
Core Concepts
NSCollectionLayoutSize
NSCollectionLayoutSize表示的是大小,这里的size指的是Width和Height的尺寸(dimension)
class NSCollectionLayoutSize {
init(widthDimension: NSCollectionLayoutDimension, heightDimension: NSCollectionLayoutDimension)
}
注意这里它们不是标量,而是NSCollectionLayoutDimension类型,它是一种不依赖于轴来描述一个具体的轴的大小,有4个方法来定义:
class NSCollectionLayoutDimension {
class func fractionalWidth(_ fractionalWidth: CGFloat) -> Self
class func fractionalHeight(_ fractionalHeight: CGFloat) -> Self
class func absolute(_ absoluteDimension: CGFloat) -> Self
class func estimated(_ estimatedDimension: CGFloat) -> Self
}
使用fractional variants来创建dimension
let widthDimension = NSCollectionLayoutDimension.fractionalWidth(0.5)
let heightDimension = NSCollectionLayoutDimension.fractionalHeight(0.3)
表示这里的item的宽度将会是其容器宽度的0.5,高度是其容器高度的0.3
let size = NSCollectionLayoutDimension(widthDimension: .fractionalWidth(0.25),
heightDimension: .fractionalWidth(0.25))
使用point-based值来创建
let heightDimension = NSCollectionLayoutDimension.absolute(200)
表示将元素的宽或者高写死一个绝对的值
预估值
let heightDimension = NSCollectionLayoutDimension.estimated(200)
表示预估高度
NSCollectionLayoutItem
NSCollectionLayoutItem
表示的是item的布局,item指的就是cell或者supplementary,即呈现在屏幕上的东西
class NSCollectionLayoutItem {
convenience init(layoutSize: NSCollectionLayoutSize)
var contentInsets: NSDirectionalEdgeInsets
}
NSCollectionLayoutGroup
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
}
Group是组成布局的基本单元,它有三种形式
- 水平
- 垂直
- 自定义
如果你没有一个沿着一条line的布局,可使用自定义group。自定义group让你以一种自定义的方式,描述item的绝对大小和位置,如果你有预先定义好的生成器来生成布局,就可以使用自定义group。例如定义一个径向(radial)的布局
而在自定义中需要传入一个
NSCollectionLayoutGroupCustomItemProvider
来决定这个 group 中 Item 得布局方式。通过NSCollectionLayoutGroup
我们可以实现在同一个 section 中实现不同得布局方式
NSCollectionLayoutSection
class NSCollectionLayoutSection {
convenience init(layoutGroup: NSCollectionLayoutGroup)
var contentInsets: NSDirectionalEdgeInsets
}
初始化section需要group,contentInsets
来决定这个Section内凹的大小
UICollectionViewCompositionalLayout
UICollectionViewCompositionalLayout
class UICollectionViewCompositionalLayout: UICollectionViewLayout {
init(section: NSCollectionLayoutSection)
init(sectionProvider: @escaping SectionProvider)
}
可以指定一个闭包,闭包将会回调,并将在每个section的基础上请求这些section的定义
例子
以一个简单的列表为例:
NumberCell.swift
继承自UICollectionViewCell
,仅仅包含一个label
class NumberCell: UICollectionViewCell {
//标识符
static let reuseIndentifier = String(describing: NumberCell.self)
@IBOutlet weak var label: UILabel!
}
ViewController.swift
主要逻辑在于配置组合布局和配置数据源
class ViewController: UIViewController {
//表示section,默认遵循Hashable协议
enum Section {
case Main
}
@IBOutlet weak var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<Section, Int>!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = configureLayout()
configureDataSource()
}
//布局
func configureLayout() -> UICollectionViewCompositionalLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
func configureDataSource() {
//如何设置cell
dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: self.collectionView, cellProvider: { (collectionView, indexPath, number) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NumberCell.reuseIndentifier, for: indexPath) as? NumberCell
else {
fatalError("Cannot create new cell")
}
cell.label.text = number.description
return cell
})
//设置数据源
//先添加section
var initialSnapshot = NSDiffableDataSourceSnapshot<Section, Int>()
initialSnapshot.appendSections([.Main])
//添加item
initialSnapshot.appendItems(Array(1...100), toSection: .Main)
//应用数据源
dataSource.apply(initialSnapshot, animatingDifferences: false)
}
}
效果如下:
组合布局的好处是经过简单的修改就可以实现不同的效果
例如,如果上面的例子想实现2列布局,只需要将itemSize
的fractionalWidth
这是为0.5
//布局
func configureLayout() -> UICollectionViewCompositionalLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
NSCollectionLayoutItem
有个contentInsets
属性,可以设置inset,实现边距
//布局
func configureLayout() -> UICollectionViewCompositionalLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
//contentInsets
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
实现5列布局
//布局
func configureLayout() -> UICollectionViewCompositionalLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
//contentInsets
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}