Final UICollectionView information with iOS examples written in Swift


Anatomy of the UICollectionView class

If you happen to’re not aware of UICollectionView, I would counsel to get aware of this class instantly. They’re the essential constructing blocks for a lot of apps supplied by Apple and different third get together builders. It is like UITableView on steroids. Here’s a fast intro about how one can work with them by way of IB and Swift code. 💻

You might need seen that I’ve a love for steel music. On this tutorial we will construct an Apple Music catalog like look from floor zero utilizing solely the mighty UICollectionView class. Headers, horizontal and vertical scrolling, round photos, so principally nearly all the things that you’re going to ever must construct nice consumer interfaces. 🤘🏻

The right way to make a UICollectionView utilizing Interface Builder (IB) in Xcode?

The quick & sincere reply: you should not use IB!

If you happen to nonetheless wish to use IB, here’s a actual fast tutorial for completely newcomers:

The primary steps of making your first UICollectionView primarily based display screen are these:

  • Drag a UICollectionView object to your view controller
  • Set correct constraints on the gathering view
  • Set dataSource & delegate of the gathering view
  • Prototype your cell structure contained in the controller
  • Add constraints to your views contained in the cell
  • Set prototype cell class & reuse identifier
  • Do some coding:
import UIKit

class MyCell: UICollectionViewCell {
    @IBOutlet weak var textLabel: UILabel!
}

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    override func viewDidLayoutSubviews() {
        tremendous.viewDidLayoutSubviews()

        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.itemSize = CGSize(
                width: collectionView.bounds.width,
                peak: 120
            )
        }
    }
}

extension ViewController: UICollectionViewDataSource {

    func numberOfSections(
        in collectionView: UICollectionView
    ) -> Int {
        1
    }

    func collectionView(
        _ collectionView: UICollectionView, 
        numberOfItemsInSection part: Int
    ) -> Int {
        10
    }

    func collectionView(
        _ collectionView: UICollectionView, 
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "MyCell", 
            for: indexPath
        ) as! MyCell

        cell.textLabel.textual content = String(indexPath.row + 1)
        return cell
    }
}

extension ViewController: UICollectionViewDelegate {

    func collectionView(
        _ collectionView: UICollectionView, 
        didSelectItemAt indexPath: IndexPath
    ) {
        print(indexPath.merchandise + 1)
    }
}

In a nutshell, the information supply will present all of the required information about how one can populate the gathering view, and the delegate will deal with consumer occasions, akin to tapping on a cell. It is best to have a transparent understanding concerning the information supply and delegate strategies, so be at liberty to play with them for a short while. ⌨️

The right way to setup a UICollectionView primarily based display screen programmatically?

As you might need seen cells are the core parts of a group view. They’re derived from reusable views, which means that when you’ve got a listing of 1000 parts, there will not be a thousand cells created for each factor, however just a few that fills the scale of the display screen and while you scroll down the record these things are going to be reused to show your parts. That is solely due to reminiscence concerns, so in contrast to UIScrollView the UICollectionView (and UITableView) class is a extremely sensible and environment friendly one, however that is additionally the rationale why it’s important to put together (reset the contents of) the cell each time earlier than you show your precise information. 😉

Initialization can also be dealt with by the system, nevertheless it’s price to say that if you’re working with Interface Builder, it is best to do your customization contained in the awakeFromNib technique, however if you’re utilizing code, init(body:) is your house.

import UIKit

class MyCell: UICollectionViewCell {

    weak var textLabel: UILabel!

    override init(body: CGRect) {
        tremendous.init(body: body)

        let textLabel = UILabel(body: .zero)
        textLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(textLabel)
        NSLayoutConstraint.activate([
            textLabel.topAnchor.constraint(
                equalTo: contentView.topAnchor
            ),
            textLabel.bottomAnchor.constraint(
                equalTo: contentView.bottomAnchor
            ),
            textLabel.leadingAnchor.constraint(
                equalTo: contentView.leadingAnchor
            ),
            textLabel.trailingAnchor.constraint(
                equalTo: contentView.trailingAnchor
            ),
        ])
        self.textLabel = textLabel

        contentView.backgroundColor = .lightGray
        textLabel.textAlignment = .heart
    }

    required init?(coder aDecoder: NSCoder) {
        tremendous.init(coder: aDecoder)

        fatalError("Interface Builder will not be supported!")
    }

    override func awakeFromNib() {
        tremendous.awakeFromNib()

        fatalError("Interface Builder will not be supported!")
    }

    override func prepareForReuse() {
        tremendous.prepareForReuse()

        textLabel.textual content = nil
    }
}

Subsequent we’ve got to implement the view controller which is accountable for managing the gathering view, we’re not utilizing IB so we’ve got to create it manually by utilizing Auto Format anchors – like for the textLabel within the cell – contained in the loadView technique. After the view hierarchy is able to rock, we additionally set the information supply and delegate plus register our cell class for additional reuse. Word that that is achieved robotically by the system if you’re utilizing IB, however for those who desire code it’s important to do it by calling the correct registration technique. You may register each nibs and lessons.

import UIKit

class ViewController: UIViewController {

    weak var collectionView: UICollectionView!

    override func loadView() {
        tremendous.loadView()

        let collectionView = UICollectionView(
            body: .zero, 
            collectionViewLayout: UICollectionViewFlowLayout()
        )
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(
                equalTo: view.topAnchor
            ),
            collectionView.bottomAnchor.constraint(
                equalTo: view.bottomAnchor
            ),
            collectionView.leadingAnchor.constraint(
                equalTo: view.leadingAnchor
            ),
            collectionView.trailingAnchor.constraint(
                equalTo: view.trailingAnchor
            ),
        ])
        self.collectionView = collectionView
    }

    override func viewDidLoad() {
        tremendous.viewDidLoad()

        collectionView.backgroundColor = .white
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(
            MyCell.self,
            forCellWithReuseIdentifier: "MyCell"
        )
    }
}

extension ViewController: UICollectionViewDataSource {

    func numberOfSections(
        in collectionView: UICollectionView
    ) -> Int {
        1
    }

    func collectionView(
        _ collectionView: UICollectionView, 
        numberOfItemsInSection part: Int
    ) -> Int {
        10
    }

    func collectionView(
        _ collectionView: UICollectionView, 
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "MyCell", 
            for: indexPath
        ) as! MyCell

        cell.textLabel.textual content = String(indexPath.row + 1)
        return cell
    }
}

extension ViewController: UICollectionViewDelegate {

    func collectionView(
        _ collectionView: UICollectionView, 
        didSelectItemAt indexPath: IndexPath
    ) {
        print(indexPath.row + 1)
    }
}

extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(
        _ collectionView: UICollectionView,
        structure collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        .init(
            width: collectionView.bounds.dimension.width - 16, 
            peak: 120
        )
    }
    func collectionView(
        _ collectionView: UICollectionView,
        structure collectionViewLayout: UICollectionViewLayout,
        minimumLineSpacingForSectionAt part: Int
    ) -> CGFloat {
        8
    }

    func collectionView(
        _ collectionView: UICollectionView,
        structure collectionViewLayout: UICollectionViewLayout,
        minimumInteritemSpacingForSectionAt part: Int
    ) -> CGFloat {
        0
    }

    func collectionView(
        _ collectionView: UICollectionView,
        structure collectionViewLayout: UICollectionViewLayout,
        insetForSectionAt part: Int
    ) -> UIEdgeInsets {
        .init(high: 8, left: 8, backside: 8, proper: 8)
    }
}

This time it is best to pay some consideration on the circulation structure delegate strategies. You should utilize these strategies to supply metrics for the structure system. The circulation structure will show all of the cells primarily based on these numbers and sizes. sizeForItemAt is accountable for the cell dimension, minimumInteritemSpacingForSectionAt is the horizontal padding, minimumLineSpacingForSectionAt is the vertical padding, and insetForSectionAt is for the margin of the gathering view part.

Utilizing supplementary parts (part headers and footers)

So on this part I will each use storyboards, nibs and a few Swift code. That is my ordinary method for a number of causes. Though I like making constraints from code, most individuals desire visible editors, so all of the cells are created inside nibs. Why nibs? As a result of when you’ve got a number of assortment views that is “nearly” the one good approach to share cells between them.

You may create part footers precisely the identical method as you do headers, in order that’s why this time I am solely going to concentrate on headers, as a result of actually you solely have to alter one phrase with a view to use footers. ⚽️

You simply must create two xib information, one for the cell and one for the header. Please be aware that you would use the very same assortment view cell to show content material within the part header, however this can be a demo so let’s simply go together with two distinct objects. You do not even must set the reuse identifier from IB, as a result of we’ve got to register our reusable views contained in the supply code, so simply set the cell class and join your retailers.

Cell and supplementary factor registration is barely completely different for nibs.

let cellNib = UINib(nibName: "Cell", bundle: nil)
self.collectionView.register(
    cellNib, 
    forCellWithReuseIdentifier: "Cell"
)

let sectionNib = UINib(nibName: "Part", bundle: nil)
self.collectionView.register(
    sectionNib, 
    forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, 
    withReuseIdentifier: "Part"
)

Implementing the information supply for the part header seems like this.

func collectionView(
    _ collectionView: UICollectionView,
    viewForSupplementaryElementOfKind type: String,
    at indexPath: IndexPath
) -> UICollectionReusableView {

    guard type == UICollectionView.elementKindSectionHeader else {
        return UICollectionReusableView()
    }
    let view = collectionView.dequeueReusableSupplementaryView(
        ofKind: type, 
        withReuseIdentifier: "Part", 
        for: indexPath
    ) as! Part

    view.textLabel.textual content = String(indexPath.part + 1)
    return view
}

Offering the scale for the circulation structure delegate can also be fairly simple, nevertheless typically I do not actually get the naming conventions by Apple. As soon as it’s important to swap a form, and the opposite time there are precise strategies for particular varieties. 🤷‍♂️

func collectionView(
    _ collectionView: UICollectionView,
    structure collectionViewLayout: UICollectionViewLayout,
    referenceSizeForHeaderInSection part: Int
) -> CGSize {
    .init(
        width: collectionView.bounds.dimension.width, 
        peak: 64
    )
}

Ranging from iOS9 part headers and footers may be pinned to the highest or backside of the seen bounds of the gathering view.

if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
    flowLayout.sectionHeadersPinToVisibleBounds = true
}

That is it, now you know the way to construct fundamental layouts with assortment view.

What about complicated circumstances, like utilizing a number of sorts of cells in the identical assortment view? Issues can get fairly messy with index paths, in order that’s why I re-invented one thing higher primarily based on a method how one can construct superior consumer interfaces with assortment views showcased by Apple again at WWDC 2014.

My CollectionView primarily based UI framework

Now you recognize the fundamentals, so why do not we get straight to the purpose? I am going to present you my greatest apply of constructing nice consumer interfaces by utilizing my MVVM structure primarily based CollectionView micro framework.

CollectionView + ViewModel sample = ❤️ .

I am going to clarify the parts actual fast and after that you’re going to learn to use them to construct up the Apple music-ish structure that I used to be speaking about at first. 🎶

Grid system

The primary drawback with assortment views is the scale calculation. You must present the scale (width & peak) for every cell inside your assortment view.

  • if all the things has a hard and fast dimension inside your assortment view, you may simply set the scale properties on the circulation structure itself
  • for those who want dynamic sizes per merchandise, you may implement the circulation structure delegate aka. UICollectionViewDelegateFlowLayout (why is the delegate phrase in the midst of the identify???) and return the precise sizes for the structure system
  • for those who want much more management you may create a brand new structure subclass derived from CollectionView(Stream)Format and do all the scale calculations there

Thats good, however nonetheless it’s important to mess with index paths, trait collections, frames and plenty of extra with a view to have a easy 2, 4, n column structure that adapts on each system. That is the rationale why I’ve created a extremely fundamental grid system for dimension calculation. With my grid class you may simply set the variety of columns and get again the scale for x quantity of columns, “similar to” in net primarily based css grid methods. 🕸

Cell reuse

Registering and reusing cells ought to and may be automated in a kind secure method. You simply wish to use the cell, and also you should not care about reuse identifiers and cell registration in any respect. I’ve made a pair helper strategies with a view to make the progress extra nice. Reuse identifiers are derived from the identify of the cell lessons, so that you dont’t have to fret about anymore. This can be a apply that many of the builders use.

View mannequin

view mannequin = cell (view) + information (mannequin)

Filling up “template” cell with actual information needs to be the duty of a view mannequin. That is the place MVVM comes into play. I’ve made a generic base view mannequin class, that it is best to subclass. With the assistance of a protocol, you should utilize numerous cells in a single assortment view with out going loopy of the row & part calculations and you’ll concentrate on one easy activity: connecting view with fashions. 😛

Part

part = header + footer + cells

I am attempting to emphasise that you do not wish to mess with index paths, you simply wish to put your information collectively and that is it. Previously I’ve struggled greater than sufficient with “pointless index path math”, so I’ve made the part object as a easy container to wrap headers, footers and all of the objects within the part. The outcome? Generic information supply class that can be utilized with a number of cells with none row or part index calculations. 👏👏👏

Supply

So with a view to make all of the issues I’ve talked about above work, I wanted to implement the gathering view delegate, information supply, and circulation structure delegate strategies. That is how my supply class was born. Every part is applied right here, and I am utilizing sections, view fashions the grid system to construct up assortment views. However hey, sufficient from this principle, let’s examine it in apply. 👓

CollectionView framework instance software

The right way to make a any record or grid structure trouble free? Properly, as a primary step simply add my CollectionView framework as a dependency. Don’t fret you will not remorse it, plus it helps Xcode 11 already, so you should utilize the Swift Bundle Supervisor, straight from the file menu to combine this package deal.

Tip: simply add the @_exported import CollectionView line within the AppDelegate file, then you definitely I haven’t got to fret about importing the framework file-by-file.

Step 1. Make the cell.

This step is an identical with the common setup, besides that your cell must be a subclass of my Cell class. Add your individual cell and do all the things as you’ll do usually.

import UIKit

class AlbumCell: Cell {

    @IBOutlet weak var textLabel: UILabel!
    @IBOutlet weak var detailTextLabel: UILabel!
    @IBOutlet weak var imageView: UIImageView!

    override func awakeFromNib() {
        tremendous.awakeFromNib()

        self.textLabel.font = UIFont.systemFont(ofSize: 12, weight: .daring)
        self.textLabel.textColor = .black

        self.detailTextLabel.font = UIFont.systemFont(ofSize: 12, weight: .daring)
        self.detailTextLabel.textColor = .darkGray

        self.imageView.layer.cornerRadius = 8
        self.imageView.layer.masksToBounds = true
    }

    override func reset() {
        tremendous.reset()

        self.textLabel.textual content = nil
        self.detailTextLabel.textual content = nil
        self.imageView.picture = nil
    }
}

Step 2. Make a mannequin

Simply decide a mannequin object. It may be something, however my method is to make a brand new struct or class with a Mannequin suffix. This fashion I do know that fashions are referencing the gathering view fashions inside my reusable parts folder.

import Basis

struct AlbumModel {
    let artist: String
    let identify: String
    let picture: String
}

Step 3. Make the view mannequin.

Now as an alternative of configuring the cell contained in the delegate, or in a configure technique someplace, let’s make an actual view mannequin for the cell & the information mannequin that is going to be represented by way of the view.

import UIKit

class AlbumViewModel: ViewModel<AlbumCell, AlbumModel> {

    override func updateView() {
        self.view?.textLabel.textual content = self.mannequin.artist
        self.view?.detailTextLabel.textual content = self.mannequin.identify
        self.view?.imageView.picture = UIImage(named: self.mannequin.picture)
    }

    override func dimension(grid: Grid) -> CGSize {
        if
            (self.collectionView.traitCollection.userInterfaceIdiom == .telephone &&
             self.collectionView.traitCollection.verticalSizeClass == .compact) ||
            self.collectionView?.traitCollection.userInterfaceIdiom == .pad
        {
            return grid.dimension(
                for: self.collectionView, 
                ratio: 1.2, 
                objects: grid.columns / 4, 
                gaps: grid.columns - 1
            )
        }
        if grid.columns == 1 {
            return grid.dimension(for: self.collectionView, ratio: 1.1)
        }
        return grid.dimension(
            for: self.collectionView, 
            ratio: 1.2, 
            objects: grid.columns / 2,
            gaps: grid.columns - 1
        )
    }
}

Step 4. Setup your information supply.

Now, use your actual information and populate your assortment view utilizing the view fashions.

let grid = Grid(columns: 1, margin: UIEdgeInsets(all: 8))
self.collectionView.supply = .init(grid: grid, [
    [
        HeaderViewModel(.init(title: "Albums"))
        AlbumViewModel(self.album)
    ],
])
self.collectionView.reloadData()

Step 5. 🍺🤘🏻🎸

Congratulations you are achieved along with your first assortment view. With just some traces of code you could have a ROCK SOLID code that may allow you to out in many of the conditions! 😎

That is simply the tip of the iceberg! 🚢

Horizontal scrolling inside vertical scrolling

What if we make a cell that accommodates a group view and we use the identical technique like above? A set view containing a group view… UICollectionViewception!!! 😂

It is fully attainable, and very easy to do, the information that feeds the view mannequin will likely be a group view supply object, and also you’re achieved. Easy, magical and tremendous good to implement, additionally included within the instance app.

Sections with artists & round photos

A number of sections? No drawback, round photos? That is additionally a chunk of cake, for those who had learn my earlier tutorial about round assortment view cells, you will know how one can do it, however please try the supply code from GitLab and see it for your self in motion.

Callbacks and actions

Person occasions may be dealt with very straightforward, as a result of view fashions can have delegates or callback blocks, it solely relies on you which of them one you favor. The instance accommodates an onSelect handler, which is tremendous good and built-in to the framework. 😎

Dynamic cell sizing re-imagined

I additionally had a tutorial about assortment view self sizing cell help, however to be sincere I am not an enormous fan of Apple’s official technique. After I’ve made the grid system and began utilizing view fashions, it was easier to calculate cell heights on my own, with about 2 traces of additional code. I consider that is price it, as a result of self sizing cells are a little bit buggy if it involves auto rotation.

Rotation help, adaptivity

Don’t fret about that an excessive amount of, you may merely change the grid or verify trait collections contained in the view mannequin if you would like. I would say nearly all the things may be achieved proper out of the field. My assortment view micro framework is only a light-weight wrapper across the official assortment view APIs. That is the fantastic thing about it, be at liberty to do no matter you need and use it in a method that YOU personally desire. 📦

Now go, seize the pattern code and hearken to some steel! 🤘🏻

What if I advised you… yet another factor: SwiftUI

These are some unique quotes of mine again from April, 2018:

If you happen to like this technique that is cool, however what if I advised you that there’s extra? Do you wish to use the identical sample in every single place? I imply on iOS, tvOS, macOS and even watchOS. Performed deal! I’ve created all the things contained in the CoreKit framework. UITableViews, WKInterfaceTables are supported as properly.

Properly, I am a visionary, however SwiftUI was late 1 yr, it arrived in 2019:

I actually consider that Apple this yr will method the following era UIKit / AppKit / UXKit frameworks (written in Swift after all) considerably like this. I am not speaking concerning the view mannequin sample, however about the identical API on each platform pondering. Anyway, who is aware of this for sue, we’ll see… #wwdc18 🤔

If somebody from Apple reads this, please clarify me why the hell is SwiftUI nonetheless an abstraction layer above UIKit/ AppKit as an alternative of a refactored AppleKit UI framework that lastly unifies each single API? For actual, why? Nonetheless do not get it. 🤷‍♂️

Anyway, we’re stepping into to the identical path guys, year-by-year I delete an increasing number of self-written “Third-party” code, so that you’re doing nice progress there! 🍎

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox