Created By Ningyuan — 2020/12/20
前言
🍎为iOS14 引入了新的API——UICollectionViewListCell,本文将在UI上分享简单的使用方式。
UICollectionViewListCell需要配合iOS13中推出的UICollectionViewCompositionalLayout、DiffableDataSource等,不太清楚它们工作方式的,可以先自行了解
- 使用UICollectionViewListCell,可在UICollectionView中实现UITableViewCell样式的布局
- 过去麻烦的自定义展开收起操作,现在只需要对数据源进行操作
创建UICollectionView
首先先来创建UICollectionView,常规操作,唯一不同点就是collectionViewLayout,下面使用了UICollectionViewCompositionalLayout中的list配置样式。
lazy var collectionView: UICollectionView = { [weak self] in
// 配置布局样式,样式有5种,可自行查看`UICollectionLayoutListConfiguration.Appearance`
var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
// 配置布局
let layout = UICollectionViewCompositionalLayout.list(using: listConfiguration)
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height), collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.delegate = self
return collectionView
}()
配置UICollectionViewDiffableDataSource
它iOS13中引入了NSDiffableDataSource,UITableView和UICollectionView都有各自的实现对象,可对数据实现局部刷新的数据源对象。
定义:
class UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UICollectionViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
SectionIdentifierType和ItemIdentifierType都必须具有唯一标识,从而确保了数据的唯一性。
代码如下:
lazy var diffDataSource: UICollectionViewDiffableDataSource<String, String> = {
/// 配置数据源之前需要注册Cell
let cellRegist = UICollectionView.CellRegistration<UICollectionViewListCell, String> { [weak self] (cell, indexPath, item) in
guard let self = self else {return}
// 对默认的cell进行有限的配置
// 通过contentConfiguration配置cell的显示内容
var config = cell.defaultContentConfiguration()
config.text = "item\(item)"
config.secondaryText = "section\(indexPath.section)"
config.textProperties.adjustsFontSizeToFitWidth = true
config.secondaryTextProperties.numberOfLines = 0
cell.contentConfiguration = config
}
// 这里定义了SectionIdentifierType为String类型,ItemIdentifierType为String类型
let dataSource = UICollectionViewDiffableDataSource<String, String>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegist, for: indexPath, item: item)
}
return dataSource
}()
还需要配合NSDiffableDataSourceSnapshot(数据源快照对象)使用,可以把它看作是数据源的缓冲层,
lazy var snapShot: NSDiffableDataSourceSnapshot<String, String> = {
var snapShot = NSDiffableDataSourceSnapshot<String, String>()
// 这里添加3个Section,SectionIdentify为String
snapShot.appendSections(["Section1", "Section2", "Section3"])
// 分别向3个Section里添加item,ItemIdentifier为String
snapShot.appendItems(["0", "1", "2"], toSection: "Section1")
snapShot.appendItems(["3", "4", "5"], toSection: "Section2")
snapShot.appendItems(["7", "8", "9", "10"], toSection: "Section3")
return snapShot
}()
之后使用apply更新数据源,无需我们计算发生变化的indexPath,dataSource会自动与snapshot进行比对,计算出差异,即可对UI进行更新,不必手动调用reloadData或reloadSection
diffDataSource.apply(snapShot, animatingDifferences: true, completion: nil)
对比一下以往的写法,每次改变dataSource需要调用 reload方法更新UI:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return UICollectionViewCell()
}
添加手势
UICollectionViewListCell可以像UITableView中一样,添加左右滑动手势,以响应不同的操作需求,但它不是对单个listCell的操作,而是当成整个列表的一项配置,需要在 UICollectionViewLayoutList Configuration中配置。配置代码如下:
/// 左滑手势
listConfiguration.leadingSwipeActionsConfigurationProvider = .some({ [weak self] (indexPath) -> UISwipeActionsConfiguration? in
guard let self = self else {return nil}
return UISwipeActionsConfiguration(actions: [UIContextualAction(style: .destructive, title: "Add", handler: { (contextualAction, view, complete) in
if let listCell = self.collectionView.cellForItem(at: indexPath) as? UICollectionViewListCell {
let sectionIndentifier = self.snapShot.sectionIdentifiers[indexPath.section]
var sectionSnapShop = self.diffDataSource.snapshot(for: sectionIndentifier)
let items = sectionSnapShop.visibleItems
if items.count > 0 {
/// 简单添加数据
sectionSnapShop.insert([items[indexPath.row] + "Add\(Int(arc4random()) % 99999999)"], after: items[indexPath.row])
self.diffDataSource.apply(sectionSnapShop, to: sectionIndentifier, animatingDifferences: true) {
complete(true)
}
}
}
})])
})
/// 右滑手势
listConfiguration.trailingSwipeActionsConfigurationProvider = .some({ [weak self] (indexPath) -> UISwipeActionsConfiguration? in
guard let self = self else {return nil}
return UISwipeActionsConfiguration(actions: [UIContextualAction(style: .destructive, title: "Delete", handler: { (contextualAction, view, complete) in
if let listCell = self.collectionView.cellForItem(at: indexPath) as? UICollectionViewListCell {
let sectionIndetifier = self.snapShot.sectionIdentifiers[indexPath.section]
var sectionSnapShot = self.diffDataSource.snapshot(for: sectionIndetifier)
let items = sectionSnapShot.visibleItems
if items.count > 0 {
let item = items[indexPath.row]
let alertCro = UIAlertController(title: "message", message: "Are you sure to Delete【item\(item)】", preferredStyle: .alert)
alertCro.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: { (action) in
complete(false)
}))
alertCro.addAction(UIAlertAction(title: "confirm", style: .default, handler: { (action) in
/// 删除指定数据
sectionSnapShot.delete([item])
self.diffDataSource.apply(sectionSnapShot, to: sectionIndetifier, animatingDifferences: true) {
complete(true)
}
}))
self.present(alertCro, animated: true, completion: nil)
}
}
})])
})
添加头部/尾部 NSCollectionLayoutBoundarySupplementaryItem
对Section添加头部、尾部视图,在新API中并没有特别区分了,被归为了一个通用的视图,我们只需要对想要添加的View进行注册,然后再指定位置就可以了。
在dataSource中注册视图,相当于之前UICollecetionViewReusableView的概念
/// 注册、配置
let suppleRegist = UICollectionView.SupplementaryRegistration(elementKind: "CustomIdentifier0") { (reusableView, elementKind, indexPath) in
for subView in reusableView.subviews {
subView.removeFromSuperview()
}
let titleLabel = UILabel()
titleLabel.text = "Section\(indexPath.section) - \(elementKind)"
titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .medium)
titleLabel.sizeToFit()
titleLabel.frame.origin = CGPoint(x: 10, y: 10)
reusableView.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.5)
reusableView.addSubview(titleLabel)
}
dataSource.supplementaryViewProvider = .some({ (collectionView, elementKind, indexPath) -> UICollectionReusableView? in
return collectionView.dequeueConfiguredReusableSupplementary(using: suppleRegist, for: indexPath)
})
为指定section添加刚刚注册的boundarySupplementaryItems
layoutSize
用于确定视图的SizeelementKind
用于指定已注册的视图aligment
用于确定布局位置
let section = NSCollectionLayoutSection(group: group)
// 控制section的滚动模式
section.orthogonalScrollingBehavior = .groupPagingCentered
section.boundarySupplementaryItems = [NSCollectionLayoutBoundarySupplementaryItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.12)), elementKind: "CustomIdentifier0" alignment: .top)]
延伸:UICollectionViewCompositionalLayout
该部分是对UICollectionViewCompositionalLayout的简单介绍,仅阅读UICollectionViewListCell的可跳过
UICollectionViewCompositionalLayout(可译作合成布局),这是苹果在iOS13中引入的一种新的布局对象,可以让开发者自行对UICollectionView的布局进行各种组合,这个布局与以往的布局相比(Section
与Item
),多加了一个Group
。UICollectionViewCompositionalLayout.lsit()内部已对相关配置进行封装,无需再自行配置,当然,如果样式复杂,还可以进行自定义配置。
引用下图所示,多个item组成一个Group,多个Group组成一个Section,多个Section则组成CompositionalLayout。上面Demo中的布局,就是由6个Section组合而成的。
NSCollectionLayoutDimension
用于描述以父视图为基准的比例大小
// 基于父视图的宽度得出进行比例值
open class func fractionalWidth(_ fractionalWidth: CGFloat) -> Self
// 基于父视图的高度得出进行比例值
open class func fractionalHeight(_ fractionalHeight: CGFloat) -> Self
// 绝对值
open class func absolute(_ absoluteDimension: CGFloat) -> Self
// 估计值
open class func estimated(_ estimatedDimension: CGFloat) -> Self
下面截取一段代码,来介绍一下
// itemSize,取Group宽度的0.3倍,Group高度的0.3倍
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalWidth(0.3))
// 通过itemSize创建一个item
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// groupSize,取Section宽度的1倍,Section高度的0.32倍
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.32))
// 通过groupSize来初始化一个水平布局的group,内部item为上述item
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
// 创建section
let section = NSCollectionLayoutSection(group: group)
/// 为该section设置滚动方式,具体可自行尝试各种样式
section.orthogonalScrollingBehavior = .none
// 最后生成一个layout
let composionalLayout = UICollectionViewCompositionalLayout(section: section)
NSCollectionLayoutItem:用来描述Item的大小
NSCollectionLayoutSection:描述Section的大小
NSCollectionLayoutGroup:描述Group的大小
他们的布局大小,由NSCollectionLayoutDimension决定。
本次composionalLayout组合自由度极高,经过尝试,原本一些需要多个自定义View+UITableView或者需要UITableView嵌套UICollectoinView的布局,都可由一个UICollectionView替代实现了。
小结
- iOS14上更新的UICollectionViewListCell,是对iOS13上UICollectionView的新Api(DiffableDataSource、UICollectionViewCompositionalLayout)进行封装,所以需要先对iOS13的Api进行熟悉,才能在使用时得心应手,未提及的部分需各位自行了解
- UICollectionViewCompositionalLayout可定制组合多种布局样式,以满足不同场景的需求
参考
今天的文章iOS 14 新API – UICollectionViewListCell分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21072.html