Create custom UICollectionViewCell in Swift

No matter how many times I need it, I had to go back, search for some old setup, gathering pieces of the puzzle then try to glue them together, again and again. And today is the day I tell myself to note it down, for my future absent-mind me.

UICollectionView is such an important UI component which has been used very often in iOS app. It's used to show collection of items in a defined layout.

UICollectionView comes with some protocols that we need to conform correctly in order to display data. And yet no matter how many times I need it, I had to go back, search for some old setup, gathering pieces of the puzzle then try to glue them together, again and again.

And today is the day I tell myself to note it down, for my future absent-mind me.

I myself am more familiar with implementing UI elements programmatically, so in this tutorial, I will:

  • Create a "Screen" which extends UIViewController, because the UICollectionView is not the only element in the screen, you might want to add UILabel to the top, or other component to the bottom, we'll start with a UIViewController as a blank canvas
  • Programmatically add a UICollectionView component to the screen
  • Create a custom Cell that extends UICollectionViewCell then use it in the UICollectionView

The final code look like this:

// file CollectionCell.swift
class CollectionCell: UICollectionViewCell {
    var imageView: UIImageView = {
       let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.isUserInteractionEnabled = true
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: Helper
extension CollectionCell {
    fileprivate func setup() {
        self.addSubview(imageView)
        imageView.anchor(top: self.topAnchor, left: self.leftAnchor,
                         bottom: nil, right: nil,
                         paddingTop: 0, paddingLeft: 0,
                         paddingBottom: 0, paddingRight: 0,
                         width: 0, height: 0)
    }
}
// file HomeViewController.swift
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    let cellId = "cellId"
    
    lazy var cv: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        return cv
    }()
    
    // Mock data
    let collections: [Collection] = [
        Collection(name: "Animals"),
        Collection(name: "Emotions"),
        Collection(name: "Tools")
    ]
}

// MARK: - Lifecyle 
extension HomeViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add other elements of the screen, in this case, the UICollectionView
        setupSubViews()
    }
}

// MARK: Helpers
extension HomeViewController {
    fileprivate func setupSubViews() {
        view.backgroundColor = UIColor.white
        view.addSubview(cv)

        cv.register(CollectionCell.self, forCellWithReuseIdentifier: cellId)
        cv.dataSource = self
        cv.delegate = self
        cv.anchor(top: view.bottomAnchor, left: view.leftAnchor,
                  bottom: view.bottomAnchor, right: view.rightAnchor,
                  paddingTop: 8, paddingLeft: 8,
                  paddingBottom: 8, paddingRight: 8,
                  width: 0, height: 0)
    }
}

// MARK: UICollectionViewDataSource
extension HomeViewController {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return collections.count 
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CollectionCell
        let collection = collections[indexPath.row]
        cell.imageView.image = UIImage.init(named: collection.getImage())
        return cell
    }
}