The best way to create reusable views for contemporary assortment views?

0
17


A fast intro to fashionable assortment views utilizing compositional format, diffable knowledge supply and reusable view parts.

UIKit

Reusable views inside a generic cell


All of us like to create customized views for constructing numerous consumer interface parts, proper? We additionally love to make use of assortment views to show knowledge utilizing a grid or an inventory format. Assortment view cells are customized views, however what if you would like to make use of the very same cell as a view?


Seems you could present your individual UIContentConfiguration, identical to the built-in ones that you should utilize to setup cells to seem like checklist objects. When you check out the fashionable assortment views pattern code, which I extremely advocate, you may see methods to implement customized content material configurations to be able to create your individual cell sorts. There are some things that I do not like about this method. 😕


To begin with, your view has to adapt to the UIContentView protocol, so you must deal with further config associated stuff contained in the view. I favor the MVVM sample, so this feels a bit unusual. The second factor that you simply want is a customized cell subclass, the place you additionally should maintain the configuration updates. What if there was another method?


Let’s begin our setup by creating a brand new subclass for our future cell object, we’re merely going to supply the same old initialize methodology that I all the time use for my subclasses. Apple usually calls this methodology configure of their samples, however they’re kind of the identical. 😅



import UIKit

open class CollectionViewCell: UICollectionViewCell {
        
    @out there(*, unavailable)
    personal override init(body: CGRect) {
        tremendous.init(body: body)
        
        self.initialize()
    }

    @out there(*, unavailable)
    public required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder) isn not out there")
    }
    
    open func initialize() {
        
    }

}


All proper, that is only a fundamental subclass so we do not have to cope with the init strategies anymore. Let’s create yet one more subclass based mostly on this object. The ReusableCell sort goes to be a generic sort, it may have a view property, which goes to be added as a subview to the contentView and we additionally pin the constraints to the content material view.


import UIKit

open class ReusableCell<View: UIView>: CollectionViewCell {
    
    var view: View!

    open override func initialize() {
        tremendous.initialize()

        let view = View()
        view.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(view)
        self.view = view
        
        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: contentView.topAnchor),
            view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
        ])
    }
}


By utilizing this reusable cell sort, it may be attainable so as to add a customized view to the cell. We simply have to create a brand new customized view, however that is fairly a straightforward job to do. ✅


import UIKit

extension UIColor {

    static var random: UIColor {
        .init(purple: .random(in: 0...1),
              inexperienced: .random(in: 0...1),
              blue: .random(in: 0...1),
              alpha: 1)
    }
}

class CustomView: View {

    let label = UILabel(body: .zero)

    override func initialize() {
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        addSubview(label)
        
        
        backgroundColor = .random

        NSLayoutConstraint.activate([
            
            label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
            label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
            label.topAnchor.constraint(equalTo: topAnchor, constant: 8),
            label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
        ])
    }

}


This tradition view has a label, which we will pin to the superview with some additional padding. You possibly can retailer all of your subviews as sturdy properties, since Apple goes to maintain the deinit, though the addSubview creates a powerful reference, you do not have to fret about it anymore.


If you wish to create a cell that helps dynamic peak, you must merely pin the sting format constraints, however if you would like to make use of a hard and fast peak cell you may add your individual peak anchor constraint with a continuing worth. It’s important to set a customized precedence for the peak constraint this fashion the auto format system will not break and it is going to have the ability to fulfill all the mandatory constraints.



Compositional format fundamentals


The UICollectionViewCompositionalLayout class is a extremely adaptive and versatile format software that you should utilize to construct fashionable assortment view layouts. It has three primary parts you could configure to show your customized consumer interface parts in many alternative methods.


You mix the parts by build up from objects into a bunch, from teams into a bit, and at last right into a full format,
like on this instance of a fundamental checklist format:


There are many nice assets and tutorials about this subject, so I will not get an excessive amount of into the small print now, however we will create a easy format that may show full width (fractional format dimension) objects in a full width group, by utilizing and estimated peak to help dynamic cell sizes. I suppose that is fairly a standard use-case for many people. We are able to create an extension on the UICollectionViewLayout object to instantiate a brand new checklist format. 🙉



extension UICollectionViewLayout {
    static func createListLayout() -> UICollectionViewLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
        let merchandise = NSCollectionLayoutItem(layoutSize: itemSize)
      
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        let part = NSCollectionLayoutSection(group: group)

        let format = UICollectionViewCompositionalLayout(part: part)
        return format
    }
}


Now it’s attainable so as to add a collectionView to our view hierarchy contained in the view controller.


class ViewController: UIViewController {

    let collectionView = UICollectionView(body: .zero, collectionViewLayout: .createListLayout())

    override func loadView() {
        tremendous.loadView()

        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: collectionView.topAnchor),
            view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
            view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
        ])
    }

    override func viewDidLoad() {
        tremendous.viewDidLoad()

    }
}


You too can create your individual auto format helper extensions, or use SnapKit to rapidly setup your format constraints. It’s comparatively simple to work with anchors, you must learn my different tutorial about mastering auto format anchors if you do not know a lot about them.



Cell registration and diffable knowledge supply


Apple has a new set of APIs to register and dequeue cells for contemporary assortment views. It’s price to say that just about every part we speak about this tutorials is just out there on iOS14+ so in case you are planning to help an older model you will not have the ability to use these options.


If you wish to be taught extra concerning the subject, I might wish to advocate an article by Donny Wals and there’s a nice, however a bit longer publish by John Sundell about fashionable assortment views. I am utilizing the identical helper extension to get a cell supplier utilizing a cell registration object, to make the method extra easy, plus we will want some random sentences, so let’s add a couple of helpers. 💡


extension String {
    static func randomWord() -> String {
        (0..<Int.random(in: 1...10)).map { _ in String(format: "%c", Int.random(in: 97..<123)) }.joined(separator: "")
    }

    static func randomSentence() -> String {
        (0...50).map { _ in randomWord() }.joined(separator: " ")
    }
}


extension UICollectionView.CellRegistration {

    var cellProvider: (UICollectionView, IndexPath, Merchandise) -> Cell {
        { collectionView, indexPath, product in
            collectionView.dequeueConfiguredReusableCell(utilizing: self, for: indexPath, merchandise: product)
        }
    }
}


Now we will use the brand new UICollectionViewDiffableData class to specify our sections and objects inside the gathering view. You possibly can outline your sections as an enum, and on this case we will use a String sort as our objects. There’s a nice tutorial by AppCoda about diffable knowledge sources.


Lengthy story quick, you must make a brand new cell configuration the place now you should utilize the ReusableCell with a CustomView, then it’s attainable to setup the diffable knowledge supply with the cellProvider on the cellRegistration object. Lastly we will apply an preliminary snapshot by appending a brand new part and our objects to the snapshot. You possibly can replace the information supply with the snapshot and the great factor about is it you could additionally animate the modifications if you need. 😍


enum Part {
    case `default`
}

class ViewController: UIViewController {

    let collectionView = UICollectionView(body: .zero, collectionViewLayout: .createListLayout())
    var dataSource: UICollectionViewDiffableDataSource<Part, String>!
    let knowledge: [String] = (0..<10).map { _ in String.randomSentence() }

    override func loadView() {
        tremendous.loadView()

        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: collectionView.topAnchor),
            view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
            view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
        ])
    }

    override func viewDidLoad() {
        tremendous.viewDidLoad()

        collectionView.delegate = self

        createDataSource()
        applyInitialSnapshot()
    }

    func createDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<ReusableCell<CustomView>, String> { cell, indexPath, mannequin in
            cell.view.label.textual content = mannequin
        }

        dataSource = UICollectionViewDiffableDataSource<Part, String>(collectionView: collectionView,
                                                                         cellProvider: cellRegistration.cellProvider)
    }
    
    func applyInitialSnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Part, String>()
        snapshot.appendSections([.default])
        snapshot.appendItems(knowledge)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

extension ViewController: UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let merchandise = dataSource.itemIdentifier(for: indexPath)

        print(merchandise ?? "n/a")
    }
}


You continue to should implement a delegate methodology if you would like to deal with cell choice, however happily the diffable knowledge supply has an itemIdentifier methodology to search for parts inside the information supply.


As you may see it is fairly simple to give you a generic cell that can be utilized to render a customized view inside a group view. I imagine that the “official” cell configuration based mostly method is a little more difficult, plus you must write numerous code if it involves fashionable assortment views.


I will replace my unique assortment view framework with these new strategies for positive. The brand new compositional format is far more highly effective in comparison with common circulate layouts, diffable knowledge sources are additionally wonderful and the brand new cell registration API can be good. I imagine that the gathering view staff at Apple did an incredible job through the years, it is nonetheless one in every of my favourite parts if it involves UIKit growth. I extremely advocate studying these fashionable strategies. 👍




LEAVE A REPLY

Please enter your comment!
Please enter your name here