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

  • layoutSizeUsed to determine the Size of the view
  • elementKindUse to specify a registered view
  • aligmentUsed 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…