Created By Ningyuan — 2020/12/20
preface
🍎 has introduced a new API for iOS14, UICollectionViewListCell. This article will share a simple way to use it on the UI.
UICollectionViewListCell need to coordinate its iOS13 UICollectionViewCompositionalLayout, DiffableDataSource etc, don’t know their way to work, can check first
- Using UICollectionViewListCell, you can implement a UITableViewCell-style layout in a UICollectionView
- What used to be cumbersome custom expansion and collapse operations now only need to be performed on the data source
Create UICollectionView
First of all to create the first UICollectionView, normal operation, the only difference is collectionViewLayout, use the below list of UICollectionViewCompositionalLayout configuration style.
lazy var collectionView: UICollectionView ={[weak self] in
/ / configuration layout style, style has 5 kinds, can see ` UICollectionLayoutListConfiguration. Appearance `
var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
// Configure the layout
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
}()
Copy the code
Configuration UICollectionViewDiffableDataSource
It introduced in iOS13 NSDiffableDataSource, UITableView and UICollectionView have their own implementation object, can achieve local data refresh data source object.
Definition:
class UICollectionViewDiffableDataSource<SectionIdentifierType.ItemIdentifierType> : NSObject.UICollectionViewDataSource where SectionIdentifierType : Hashable.ItemIdentifierType : Hashable
Copy the code
Both SectionIdentifierType and ItemIdentifierType must have unique identifiers to ensure that the data is unique.
The code is as follows:
lazy var diffDataSource: UICollectionViewDiffableDataSource<String.String> = {
// register the Cell before configuring the data source
let cellRegist = UICollectionView.CellRegistration<UICollectionViewListCell.String> {[weak self] (cell, indexPath, item) in
guard let self = self else {return}
// Limited configuration of the default cell
// Use contentConfiguration to configure the display content of the 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 is String and ItemIdentifierType is String
let dataSource = UICollectionViewDiffableDataSource<String.String>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegist, for: indexPath, item: item)
}
return dataSource
}()
Copy the code
Also need to cooperate with NSDiffableDataSourceSnapshot use snapshot data source object, it can be regarded as data buffer layer,
lazy var snapShot: NSDiffableDataSourceSnapshot<String.String> = {
var snapShot = NSDiffableDataSourceSnapshot<String.String> ()// Add 3 sections here, SectionIdentify as String
snapShot.appendSections(["Section1"."Section2"."Section3"])
// Add item to each Section. ItemIdentifier is String
snapShot.appendItems(["0"."1"."2"], toSection: "Section1")
snapShot.appendItems(["3"."4"."5"], toSection: "Section2")
snapShot.appendItems(["Seven"."8"."9"."10"], toSection: "Section3")
return snapShot
}()
Copy the code
After that, apply is used to update the dataSource. The dataSource will automatically compare with the snapshot without calculating the changed indexPath. After calculating the difference, the UI can be updated without manually calling reloadData or reloadSection
diffDataSource.apply(snapShot, animatingDifferences: true, completion: nil)
Copy the code
For comparison, every time you change the dataSource, you need to call the Reload method to update the 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()}Copy the code
Add gestures
UICollectionViewListCell can add left and right swipes as in UITableView to respond to different operations, but instead of acting on a single listCell, it acts as a configuration for the entire list. Need to be in UICollectionViewLayoutList configured in the Configuration. The configuration code is as follows:
/// left swipe gesture
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 {
/// Simply add data
sectionSnapShop.insert([items[indexPath.row] + "Add\(Int(arc4random()) % 99999999)"], after: items[indexPath.row])
self.diffDataSource.apply(sectionSnapShop, to: sectionIndentifier, animatingDifferences: true) {
complete(true)}}}})])})/// right swipe gesture
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
// delete the specified data
sectionSnapShot.delete([item])
self.diffDataSource.apply(sectionSnapShot, to: sectionIndetifier, animatingDifferences: true) {
complete(true)}}))self.present(alertCro, animated: true, completion: nil)}}})])})Copy the code
Add the head/tail NSCollectionLayoutBoundarySupplementaryItem
Adding header and tail views to a Section is not a special distinction in the new API. It is classified as a general View. We just need to register the View we want to add, and then specify the location.
Registered in the dataSource view, the concept of equivalent to UICollecetionViewReusableView before
// register and configure
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)
})
Copy the code
Add just registered boundarySupplementaryItems for a given section
layoutSize
Used to determine the Size of the viewelementKind
Use to specify a registered viewaligment
Used to determine the layout position
let section = NSCollectionLayoutSection(group: group)
// Controls section scroll mode
section.orthogonalScrollingBehavior = .groupPagingCentered
section.boundarySupplementaryItems = [NSCollectionLayoutBoundarySupplementaryItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.12)), elementKind: "CustomIdentifier0" alignment: .top)]
Copy the code
Extension: UICollectionViewCompositionalLayout
This part is the simple introduction of UICollectionViewCompositionalLayout UICollectionViewListCell only reading can be skipped
UICollectionViewCompositionalLayout synthetic layout (translation), which is apple introduced a new layout in iOS13 object, allows developers to various combinations of the layout of the UICollectionView This layout adds a Section and Item Group to the previous layout. UICollectionViewCompositionalLayout. Lsit () inside have to encapsulate related configuration, no need to configure, of course, if the style is complex, also can be custom configurations.
As shown in the figure below, multiple items form a Group, multiple groups form a Section, and multiple sections form CompositionalLayout. The layout in the Demo above is made up of six sections.
NSCollectionLayoutDimension
Used to describe the scale based on the superview
// Scale values based on the width of the superview
open class func fractionalWidth(_ fractionalWidth: CGFloat) - >Self// Get the scale value based on the height of the superviewopen class func fractionalHeight(_ fractionalHeight: CGFloat) - >Self/ / the absolute valueopen class func absolute(_ absoluteDimension: CGFloat) - >Self/ / estimateopen class func estimated(_ estimatedDimension: CGFloat) - >Self
Copy the code
Here is a snippet of code to introduce it
ItemSize = 0.3 times the Group width and 0.3 times the Group height
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalWidth(0.3))
// Create an item with itemSize
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// groupSize = 1 times the Section width and 0.32 times the Section height
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.32))
// Initialize a horizontal layout group by groupSize
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
/ / create a section
let section = NSCollectionLayoutSection(group: group)
/// Set the scrolling mode for this section
section.orthogonalScrollingBehavior = .none
// Finally generate a layout
let composionalLayout = UICollectionViewCompositionalLayout(section: section)
Copy the code
NSCollectionLayoutItem: Describes the size of an Item
NSCollectionLayoutSection: describe the size of the Section
NSCollectionLayoutGroup: Describes the size of the Group
The layout of their size, decided by NSCollectionLayoutDimension.
This composionalLayout combination has a very high degree of freedom. After trying, some original layouts require multiple custom views +UITableView or UITableView nested UICollectoinView. Can be implemented by a UICollectionView instead.
summary
- UICollectionViewListCell updated on iOS14, Is a new Api UICollectionView on iOS13 (DiffableDataSource, UICollectionViewCompositionalLayout) encapsulation, so iOS13 apis need to be familiar with, In order to be handy when using, the unmentioned parts need to be understood by yourselves
- Variety of layout style UICollectionViewCompositionalLayout combination can be customized to meet the needs of different scenarios
reference
Developer.apple.com/documentati…
Devstreaming-cdn.apple.com/videos/wwdc…