[SWIFT] Implement UICollectionView of iOS14 with CustomCell.

Introduction

Last time, I wrote Article implementing UICollectionView of iOS14, so it seems that the handling of custom cells has changed, so let's write this in the article as well! I wrote it thinking that, but it was difficult because the treatment has changed considerably from before. I'm worried if it's right, so I'm wrong here, you should write this! Please comment if you have any: persevere:

Custom cell character introduction corner

UICollectionViewListCell class

It's just a cell class. I don't do anything in particular.

UICellConfigurationState class

It keeps the state. ʻIsSelected and ʻisFocused are provided by default. You can also customize and add. (Not dealt with this time.)

UIView class

Create a custom view.

UIContentConfiguration class

A class that initializes a custom view, receives the data to be displayed from the cell and ʻUICellConfigurationState`, reflects it in the custom view, and returns the view to the cell. It is the most important. It is number 4.

Implementation image

Exquisitely put a star to make a difference from the previous article. ..

All chords

There are 6 files in total. SimpleCustomCell.storyboard SimpleCustomCellViewController.swift SimpleCustomCell.swift SimpleCustomCellConfiguration.swift SimpleCustomCellView.xib SimpleCustomCellView.swift

SimpleCustomCellViewController.swift

SimpleCustomCellViewController.swift


class SimpleCustomCellViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    enum Section: CaseIterable {
        case kanto
        case kyusyu
    }
    
    let data: [Section: [String]] = [
        .kanto: ["Kanto", "Tokyo", "Chiba"],
        .kyusyu: ["Kyusyu", "Fukuoka", "Miyazaki"]
    ]

    var collectionViewDataSource: UICollectionViewDiffableDataSource<Section, String>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var layoutConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
        layoutConfig.headerMode = UICollectionLayoutListConfiguration.HeaderMode.firstItemInSection

        let layout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
        collectionView.collectionViewLayout = layout
        
        collectionViewDataSource = createDataSource()
        reloadList()
    }

    func createDataSource() -> UICollectionViewDiffableDataSource<Section, String> {
        
        let normalCell = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, text) in
            var content = cell.defaultContentConfiguration()
            content.text = text
            cell.contentConfiguration = content
        }
        
        let customCell = UICollectionView.CellRegistration<SimpleCustomCell, String> { (cell, indexPath, text) in
            cell.title = text
        }
        
        return UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) {
            (collectionView, indexPath, text) -> UICollectionViewCell? in
            
            //A normal cell is used for the section title.
            if indexPath.row == 0 {
                return collectionView.dequeueConfiguredReusableCell(using: normalCell, for: indexPath, item: text)
            } else {
                return collectionView.dequeueConfiguredReusableCell(using: customCell, for: indexPath, item: text)
            }
        }
    }
    
    func reloadList(){
        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
        snapshot.appendSections(Section.allCases)
        snapshot.appendItems(data[.kanto]!, toSection: .kanto)
        snapshot.appendItems(data[.kyusyu]!, toSection: .kyusyu)
        collectionViewDataSource.apply(snapshot)
    }
}

Boil

This is the only place that is special. ʻWhen setting UICollectionViewListCell, I called the defaultContentConfiguration method to set content, but since the process has moved to SimpleCustomCell, I only pass title` here.

let customCell = UICollectionView.CellRegistration<SimpleCustomCell, String> { (cell, indexPath, text) in
	cell.title = text
}

SimpleCustomCell.swift

SimpleCustomCell.swift


class SimpleCustomCell: UICollectionViewListCell {

    var title: String!

    override func updateConfiguration(using state: UICellConfigurationState) {
        var newConfiguration = SimpleCustomCellConfiguration()
        newConfiguration.title = title
        contentConfiguration = newConfiguration
    }
}

Boil

ʻUpdateConfiguration is a method that is called when data is ʻapply to the collectionView and when the setNeedsUpdateConfiguration method is called and the layout needs to be updated. Here, the Configuration is initialized, and the data (title) is packed and set to the contentConfiguration of the cell itself. (The argument state is unused because it does not handle the state this time.)

SimpleCustomCellConfiguration.swift

SimpleCustomCellConfiguration.swift


struct SimpleCustomCellConfiguration: UIContentConfiguration, Hashable {

    var title: String?
    
    func makeContentView() -> UIView & UIContentView {
        return SimpleCustomCellView(configuration: self)
    }

    func updated(for state: UIConfigurationState) -> Self {
        return self
    }
}

Boil

The makeContentView method is a method that creates a view to display in a cell. Passing itself to the view as an argument. The ʻupdated` method is a method that is called when the state changes. It is unused.

SimpleCustomCellView.xib(.swift)

SimpleCustomCellView.swift


class SimpleCustomCellView: UIView, UIContentView {
    
    @IBOutlet var nameLabel: UILabel!

    var configuration: UIContentConfiguration
    
    init(configuration: SimpleCustomCellConfiguration) {
        self.configuration = configuration
        super.init(frame: .zero)
        loadNib()
        setUpUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func loadNib() {
        let nib = UINib(nibName: "\(SimpleCustomCellView.self)", bundle: nil)
        guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }
        view.frame = frame
        addSubview(view)
    }
    
    private func setUpUI() {
        guard let configuration = configuration as? SimpleCustomCellConfiguration else { return }
        nameLabel.text = configuration.title
    }
}

Boil

I'm loading xib and setting the data of the passed configuration to Label. There is nothing to explain in particular.

Summary

It seems that using Configuration makes it lighter and easier to reuse. Compared to the conventional methods using Delegate and DataSource, I didn't know where and which method was called, and I felt that the feeling of a black box increased. Will it not bother me once I get used to it? ..

Next, I will write an article using the state that I did not use this time. Please LGTM if you like because it will be bald \ (^ o ^) /

Click here for git! https://github.com/ymarui/iOS14_UICollectionView_sample

References

https://qiita.com/shiz/items/4227accc7d13ae439d1d#推奨される使用方法 https://swiftsenpai.com/development/collectionview-expandable-list-part2/

Recommended Posts

Implement UICollectionView of iOS14 with CustomCell.
Implement the UICollectionView of iOS14 with the minimum required code.
CarPlay starting with iOS14
Why implement with a singleton instead of a static method
Implement GraphQL with Spring Boot
iOS: File upload with SFTP
Implementation of XLPagerTabStrip with TabBarController
Implement jCaptcha reload with ajax
Implement search function with form_with