featured

By Bill RichardsOctober 21, 2014

Tech Tip: Clean up your code with Lighter View Controllers in Swift

As your app grows in size and functionality, you might find yourself working with bloated view controllers. Even if you separate all your data sources and delegates with nice clean pragma marks, you still end up with large amounts of code. What if there is an easier way?

Is there a simple way to separate some common tasks, such as the UITableViewDataSource protocol? Enter "Lighter View Controllers,” a concept by Chris Edihof.

In the article, Chris describes a nice way to separate your UITableViewDataSource. You can apply this to UICollectionViewDataSource or any other common protocol. This can significantly clean up your view controller’s code base and simplify your development process.

The concept is simple: create a reusable NSObject subclass to handle any protocol that you’d like to separate out. From there, it’s just as simple as setting the datasource to point to the new class. In the case of UITableViewDatasource and UICollectionViewDatasource, you’ll also use a block (or closure in Swift) to handle the configuring of the cell. This can be reused in all of your view controllers that need a data source. The article is written in Objective-C, so what might this look like in Swift? Let’s find out.

Lighter View Controller (UICollectionViewDatasource) in Swift

CollectionViewDataSource

import Foundation

typealias CollectionViewCellConfigureBlock = (cell:UICollectionViewCell, item:AnyObject?) -> ()

class CollectionViewDataSource: NSObject, UICollectionViewDataSource {

    var items:NSArray = []
    var itemIdentifier:String?
    var configureCellBlock:CollectionViewCellConfigureBlock?

    init(items: NSArray, cellIdentifier: String, configureBlock: CollectionViewCellConfigureBlock) {
        self.items = items
        self.itemIdentifier = cellIdentifier
        self.configureCellBlock = configureBlock
        super.init()
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(self.itemIdentifier!, forIndexPath: indexPath) as UICollectionViewCell
        let item: AnyObject = self.itemAtIndexPath(indexPath)
    
        if (self.configureCellBlock != nil) {
            self.configureCellBlock!(cell: cell, item: item)
        }
    
        return cell
    }

    func itemAtIndexPath(indexPath: NSIndexPath) -> AnyObject {
        return self.items[indexPath.row]
    }
}

CustomUICollectionViewCell

import UIKit

class CustomUICollectionViewCell: UICollectionViewCell {

    //Example cell configuration
    func configureForItem(item:AnyObject) {
        //do something with the cell...
    }

}

ViewController

import UIKit

class ViewController: UIViewController {

    //variable to hold reference to the datasource
    var dataSource:CollectionViewDataSource?

    override func viewDidLoad() {
        super.viewDidLoad()

        //Init our datasource and setup the closure to handle our cell
        //modify 'AnyObject' to match your model
        self.dataSource = CollectionViewDataSource(items: self.items, cellIdentifier: "Cell", configureBlock: { (cell, item) -> () in
            if let actualCell = cell as? CustomUICollectionViewCell {
                if let actualItem = item as? AnyObject {
                    actualCell.configureForItem(actualItem)
                }
            }
        })

        //finally, set the collectionview datasource
        self.collectionView.dataSource = self.dataSource

    }

}

View on GitHub

As you can see above, this can really reduce the amount of code stuffed inside of your view controllers.

Questions or comments? Follow me on Twitter.