Skip to content

Instantly share code, notes, and snippets.

@OscarApeland
Last active September 22, 2018 08:42
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OscarApeland/76d655a9fc310579bde567c9787ac2b7 to your computer and use it in GitHub Desktop.
Save OscarApeland/76d655a9fc310579bde567c9787ac2b7 to your computer and use it in GitHub Desktop.
Swift 4 - Nibable

Introduction

UIKits API for registering UINibs to UICollectionViews looks bad. Just look at this.

sizeCollectionView.register(UINib(nibName: "SearchFilterSizeCell", bundle: nil), forCellWithReuseIdentifier: "sizeCell")

Using them again is even worse;

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sizeCell", for: indexPath) as! SearchFilterSizeCell

Ew.

So why not Swift it up and make it look nice? I couldn't find a reason not to, so here's a neat protocol.

The good stuff

/**
 A convenience protocol to register and instaniate cells from an XIB programatically cleaner than the native function calls.
 */
protocol Nibable: class where Self: UICollectionReusableView {
    /// The reuse identifier to register for / dequeue with
    static var id: String { get }
    
    /// The name of the .xib file. Must contains ONE top level UICollectionViewCell view with its reuseIdentifier set to equal the id provided to this protocol.
    static var nibName: String { get }
}

extension UICollectionView {
    /**
     Convenience function to dequeue a UICollectionViewCell from a subclass which conforms to Nibable.
     
     **Important**
     This function force unwraps the result and will crash if the cell class doesn't properly conform to Nibable.
     
     - parameter cellClass: Class of the cell to dequeue.
     - parameter ofKind: An optional parameter to indicate that we should dequeue a supplementary reusable view.
     - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the collection view.
     
     - returns: A dequeued cell cast to the Nibable conformer.
     */
    func dequeue<CellClass: Nibable>(_ cellClass: CellClass.Type, ofKind kind: String? = nil, for indexPath: IndexPath) -> CellClass {
        if let kind = kind {
            return dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: cellClass.id, for: indexPath) as! CellClass
        } else {
            return dequeueReusableCell(withReuseIdentifier: cellClass.id, for: indexPath) as! CellClass
        }
    }
    
    /**
     Convenience function to register a UICollectionViewCell subclass which conforms to Nibable.
    
     - parameter cellClass: Class of the cell to register.
     - parameter ofKind: An optional parameter to indicate that we should dequeue a supplementary reusable view.
     */
    func register<CellClass: Nibable>(_ cellClass: CellClass.Type, ofKind kind: String? = nil) {
        if let kind = kind {
            register(UINib(nibName: cellClass.nibName, bundle: nil),
                     forSupplementaryViewOfKind: kind,
                     withReuseIdentifier: cellClass.id)
        } else {
            register(UINib(nibName: cellClass.nibName, bundle: nil),
                     forCellWithReuseIdentifier: cellClass.id)
        }
    }
}

Usage

class Cell: UICollectionViewCell, Nibable {
    static let id: String = "cell"
    static let nibName: String = "Cell"
}

class ViewController {
    func viewDidLoad() {
        collectionView.register(Cell.self)
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return collectionView.dequeue(Cell.self, for: indexPath)
    }
    
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment