A minimum viable tableView in Swift

July 20, 2014

This GitHub repo is a minimum viable implementation of a UITableView in Swift. Here’s a swift (badum, tish) tutorial on creating a UITableView using the new language.

This code has been updated for Swift 1.0 and Xcode 6.0.1

The project consists consists of a single storyboard with a table view control, and a view controller written in Swift:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    let cellIdentifier = "cellIdentifier"
    var tableData = [String]()
    
    @IBOutlet var tableView: UITableView?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Register the UITableViewCell class with the tableView
        
        
        self.tableView?.registerClass(UITableViewCell.self, forCellReuseIdentifier: self.cellIdentifier)
        
        // Setup table data
        for index in 0...100 {
            self.tableData.append("Item \(index)")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // UITableViewDataSource methods
    
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier) as UITableViewCell
        
        cell.textLabel?.text = self.tableData[indexPath.row]
        
        return cell
    }
    
    // UITableViewDelegate methods
    
    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {

        let alert = UIAlertController(title: "Item selected", message: "You selected item \(indexPath.row)", preferredStyle: UIAlertControllerStyle.Alert)
    
        alert.addAction(UIAlertAction(title: "OK",
                                      style: UIAlertActionStyle.Default,
                                    handler: {
                                        (alert: UIAlertAction!) in println("An alert of type \(alert.style.hashValue) was tapped!")
                                    }))
        
        self.presentViewController(alert, animated: true, completion: nil)
        
    }

}

Housekeeping

There are three upfront pieces of housekeeping that we need to do:

  • create a String constant as a cell identifier (Swift is clever enough to infer the fact that it’s a String when we define the constant):

    let cellIdentifier = "cellIdentifier"

  • define an Array variable to contain the table’s data. This is an Array of Strings, and we initialize this as empty as we declare it:

    var tableData = [String]()

  • define a UITableView outlet to connect the table in the Storyboard:

    @IBOutlet var tableView: UITableView?

    The outlet’s defined as an optional, because it won’t exist until the view has been instantiated (that’s a similar approach to declaring outlets as weak with Objective-C).

You’ll also need to setup the Storyboard with a UITableView object that connects with the view controller as dataSource and delegate, and connects the view controller’s tableView outlet with the table view.

Registering a UITableViewCell class with the table

So that the table can use UITableViewCell objects, we’ll need to register the UITableViewCell class for use with the cellIdentifier that we created in the stage above. The most logical place to do this is in the viewDidLoad method:

self.tableView?.registerClass(UITableViewCell.self, forCellReuseIdentifier: self.cellIdentifier)

Swift’s way of returning the class name for use with the registerClass method is subtly different from Objective-C - rather than using something along the lines of [UICollectionViewCell class], in Swift you use UICollectionViewCell.self.

Creating some table data

Next, we need to create some data for the table to work with. This is going to be held in the tableData array:

for index in 0...100 {
    self.tableData.append("Item \(index)")
}

This uses Swift’s for-in loop to create 100 Strings and append them to the tableData array. The \(...) format is a huge improvement on the legacy NSString stringWithFormat: style…

Telling the table about sections and rows

The UITableView datasource methods haven’t changed in iOS8 - you still need to inform the table how many sections and rows it will have.

(Technically, the table view will assume that it has one section unless you tell it otherwise, but I usually implement the numberOfSectionsInTableview method out of sheer habit):

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

There’s no practical difference between the Swift and Objective-C versions, other than the syntactic differences - the method has am external parameter name of tableView, and returns an Int.

The same is also true of the tableView:numberOfRowsInSection: method:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableData.count
}

Creating and returning cells

Now that the table view knows how many sections and rows it has, the final step in a minimal implementation is to create and return cells on demand:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    var cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier) as UITableViewCell
    cell.textLabel.text = self.tableData[indexPath.row]
    return cell
}

Once again, this is very straight-forward: we dequeue a cell with the appropriate identifier, configure its contents, and return it to the table view. The as operator is force-downcasting whatever is returned from the tableView into a UITableViewCell.

Cell selection

Although that’s all that’s needed to get the table view up and running, here’s how a simple UITableViewDelegate method is implemented:

func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
    let alert = UIAlertController(title: "Item selected", message: "You selected item \(indexPath.row)", preferredStyle: UIAlertControllerStyle.Alert)
    alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))
    self.presentViewController(alert, animated: true, completion: nil)
}

This method is fired when a cell is selected - no change there. Here, we’re creating an instance of UIAlertController which is the iOS8 replacement for the venerable UIAlert control:

let alert = UIAlertController(title: "Item selected", message: "You selected item \(indexPath.row)", preferredStyle: UIAlertControllerStyle.Alert)

A vanilla UIAlertController doesn’t have any user controls, so we need to add a UIAlertAction to it:

alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))

And then finally present the controller:

self.presentViewController(alert, animated: true, completion: nil)

You’ll notice that the last handler parameter of the UIAlertAction takes a block: this means that you can implement whatever actions you want to occur once the button has been tapped in-place, rather than having to link the alert controller to the view controller through a delegate.

That makes for much less code, and also keeps the action together with UIAlertController (helping to make the code more readable).

If you wanted to log something to the console, say, you could implement this:

    alert.addAction(UIAlertAction(title: "OK",
                                  style: UIAlertActionStyle.Default,
                                handler: {
                                    (alert: UIAlertAction!) in println("An alert of type \(alert.style.hashValue) was tapped!")
                                }))

It’s compact, certainly, but this is an example of something that does make me wonder why quite so many people rave about Swift as being more readable than Objective-C - I’m really not sure this is actually so much of an improvement.

Summary

One of the killer features of Swift is the backwards-compatibility of it with the existing legacy of Objective-C and the iOS frameworks. This is a good example of how the basic design patterns of the controls haven’t changed at all - moving to Swift is just a case of getting to grips with the new syntax and language features.