Make App Pie

Training for Developers and Artists

Swift Swift: Split View Controllers (Round Two) Part 1: The Master-Detail template

Back in July of 2014, I wrote one of this first posts on Split views, I said I would write another post on the subject. I ended up making this a section in chapter six of my Book I ended up making this section 2 of chapter six of my Book Swift Swift: UI View Controllers. The section came out as three projects, which I will post each as a separate post here. iOS8 makes split views universal, not just iPad. It does different things depending on class sizes. I thought I’d share with you a preview of that book. For more previews and interesting thoughts, sign up for my newsletter. –Steve

Unlike a desktop, there are no multiple windows on an iOS device. However there are times where this would be useful. For example the bookmarks and web page in Safari make for easy access to both the bookmarks and the web content. We could make one to take orders at a pizza restaurant.

Screenshot 2015-03-09 06.48.30

This is a split view controller. The left side with the bookmarks is called the master, and the right the detail. Split view controllers were originally designed for iPads only. Like popovers, view controllers would cause fatal errors in iOS7 and earlier on iPhones. In iOS8, a regular width class size displays a split view controller. With a compact width size, the master displays in a navigation controller with a segue to the detail.
Communication can happen from the master to the detail or from the detail to the master. We will discuss both as we go through two ways to set up a split view controller.

Data to the Master

There is a template for split view controllers. Make a new project using the Master-Detail Application template. Call it SwiftTemplateSplitView. Make it Universal using Swift. Open the storyboard. It appears a lot more complicated than most templates (detail in gray for visibility. ).

Screenshot 2015-03-12 10.49.19

On the left is the split view controller. To the right are two navigation controllers connected to the view controller by special segues. These segues set what is the detail and the controller. Open the document outline. Click the segue to the top controller. In the document outline you will see this:

Screenshot 2015-03-08 18.57.09

The two segues are different types: one for the master and one for the detail. Usually the top one is the master. Both segues lead to navigation controllers. The navigation controllers lead to a view controller. The template sets up the typical case: the master is a dynamic table view controller scene and the detail is a generic view controller scene. Masters are almost always table views.
In the storyboard, delete the detail scene, not the navigation controller. Drag a Table View Controller onto the storyboard. Control-drag from the detail navigation controller to the table view. and select root view controller as the Relationship Segue.
In the document outline, select the Table View in the Table View Controller scene. In the attributes inspector, change the Content to static cells. Select the Table View Section in the document outline. In the attributes inspector change the Rows to 1 and the Header Title to Pizza.
Select the Table View Cell. Change the Style to Basic. Static cells will copy current settings. Click the Pizza section header, and change Rows to 3. Select Table View. Change the Number of Sections to 3. Change the titles on the table to this:

Screenshot 2015-03-08 19.27.19

Drag a navigation item onto the top toolbar. Change the title to Menu.
Make a Cocoa Touch Class static table view controller by Pressing Command-N on the keyboard. Make a UIViewController named MenuTableViewController.
Change the class to one subclassing UITableViewController like this:

class MenuTableViewController: UITableViewController {
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        //grab the cell
        let cell = tableView.cellForRowAtIndexPath(indexPath)
        //get the title of the cell
        let myOrder = (cell?.textLabel?.text)!
        //display in the navigation bar
        navigationItem.title = myOrder
    }

}

Using the didSelectAtIndexpath method, we get the selected cell, then get the title string. Just so we know we selected something, we display the order on the navigation title.
Go back to the storyboard, and select the menu’s table view controller. In the identity inspector, set the class to MenuTableViewController. Set the simulator to iPad 2. Build and run. We get a table view with the menu.

Screenshot 2015-03-08 20.17.55

Select an item and it shows in the navigation bar. Rotate the device by pressing Command- Left Arrow. The master appears on the left.

Screenshot 2015-03-08 20.20.24

The Delegate Protocol

Quit the app in the simulator, and go back to the MenuTableViewController Class. Add above the class the following protocol:

protocol MenuTableViewControllerDelegate{
    func didSelectMenuItem(order:String)
}

Add the delegate to the MenuTableViewController Class

var delegate:MenuTableViewControllerDelegate! = nil

Add the delegate method to didSelectRowAtIndexPath:

delegate.didSelectMenuItem(myOrder)

We now have a delegate method set up on this side. We can use it in the master.

Change the Master

Go over to MasterViewController.Swift. This is part of the demo that loads with the split view controller. You’ll find a lot of code. As we’ll discuss later, it is a bad practice to use this code. We will replace this with a new table view controller. Press Command-N. and make a new Cocoa Touch Class named OrderMasterViewController subclassing UIViewController.
We will set up a simple table view controller. Remove the code that is there for the class and replace with this:

class OrderMasterViewController: UITableViewController {

    var orderList:[String] = ["a","b","c","d"]

    //MARK: Table View Data Source and Delegate
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return orderList.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
        cell.textLabel?.text = orderList[indexPath.row]
        if indexPath.row % 2 == 1 { //alternating row backgrounds
            cell.backgroundColor = UIColor.yellowColor()
        } else {
            cell.backgroundColor = tableView.backgroundColor
        }
        return cell
    }
}

This is a basic dynamic table view from the previous section. We made this class a subclass of UITableViewController for dynamic content. We added a very simple model of a string array, populated for the moment with some dummy data for testing. We added our three required methods. For our data source, we will have one section, and the size of the array will tell us how many rows in a section. In the table view cell, we will place on the textLabel the order item. We will also alternate colors for the rows on the table view.
In the storyboard, select the master table. Since this is already a table, click on its view controller icon, and head over to the identity inspector. Change to OrderMasterViewController.
Coordinate the cell reuse identifier. Using the document outline, select the table view cell. change the reuse identifier from Cell to cell. Select the Table view for the master. Change the background color in the view section of the attributes inspector to #AAAAFF by selecting the color swatch and entering the hex code into the color inspector. Build and run. The table view for the master is working:

Screenshot 2015-03-09 06.07.19

Add the Delegate

Much of the delegate setup is the same as in the table view version of the app. The difference is assigning the delegate in the master controller. The controllers are not separated by a single segue. Both are live at the same time, so there is no event that activates a segue. So there is no prepareForSegue this time.
Add the following code for the OrderMasterViewController method viewDidLoad.

override func viewDidLoad() {
    super.viewDidLoad()
    if let split = self.splitViewController {
        let controllers = split.viewControllers
        self.detailViewController =
            controllers[controllers.count-1].topViewController
            as? MenuTableViewController
    }
    self.detailViewController?.delegate = self
}

Instead of prepareForSegue we set up the delegate in viewDidLoad. The split view controller keeps an array of view controllers called viewControllers. After checking if we have a split view controller, we make a constant controller to get this array. This next line does most of the work getting the detail’s view controller:

self.detailViewController =
            controllers[controllers.count-1].topViewController
            as? MenuTableViewController

The first element in the array controllers is the master,the last the detail. First we grab the detail controller, which is a navigation controller. The top controller on the detail’s navigation stack is the one we present for the detail. We get it and downcast it to MenuTableViewController. We assign all of this to an additional property detailViewController. Since detail view controller points to the MenuTableViewController, we set the delegate from it.
Add below the model property declaration the following property for the detail:

var detailViewController: MenuTableViewController? = nil

Add the protocol to the class definition:

class OrderMasterViewController:
    UITableViewController,MenuTableViewControllerDelegate {
Add the required method didSelectMenuItem :
func didSelectMenuItem(order: String) {
        orderList.append(order)
        tableView.reloadData()
    }

Remove the sample data from the orderList array.

var orderList:[String] = []

Build and run. Select a few items from the menu. We get a list of ordered items on the master.

Screenshot 2015-03-09 06.48.30

In our next lesson, which you can find here,   we will make another split view controller from the storyboard, and make a split view controller compatible with iPhones using class sizes.

The Whole Code

OrderMasterViewController.swift

//
//  OrderMasterViewController.swift
//  SwiftTemplateSplitView
//
//  Created by Steven Lipton on 3/11/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class OrderMasterViewController: UITableViewController, MenuTableViewControllerDelegate {

    var orderList:[String] = []
    var detailViewController: MenuTableViewController? = nil

    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        if let split = self.splitViewController {
            let controllers = split.viewControllers
            self.detailViewController =
                controllers[controllers.count-1].topViewController
                as? MenuTableViewController
        }
        self.detailViewController?.delegate = self
    }
    //MARK: -  Delegates
    func didSelectMenuItem(order: String) {
        orderList.append(order)
        tableView.reloadData()
    }

    //MARK:  Table View Data Source and Delegate
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return orderList.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
        cell.textLabel?.text = orderList[indexPath.row]
        if indexPath.row % 2 == 1 { //alternating row backgrounds
            cell.backgroundColor = UIColor.yellowColor()
        } else {
            cell.backgroundColor = tableView.backgroundColor
        }
        return cell
    }

}

The detail — MenuTableViewController.swift

//
//  MenuTableViewController.swift
//  SwiftTemplateSplitView
//
//  Created by Steven Lipton on 3/11/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//
// the detail view using a Static table view controller
//

import UIKit

//protocol for the delegate
protocol MenuTableViewControllerDelegate{
    func didSelectMenuItem(order:String)
}

//Since this is static, we dont need a data source or anything else. Just selection.

class MenuTableViewController: UITableViewController {
    var delegate:MenuTableViewControllerDelegate! = nil

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        //grab the cell
        let cell = tableView.cellForRowAtIndexPath(indexPath)
        //get the title of the cell
        let myOrder = (cell?.textLabel?.text)!
        //display in the navigation bar
        navigationItem.title = myOrder
        delegate.didSelectMenuItem(myOrder)
    }

}

5 responses to “Swift Swift: Split View Controllers (Round Two) Part 1: The Master-Detail template”

  1. […] Swift Swift: Split View Controllers (Round Two) Part 1: The Master-Detail template […]

  2. […] In our first part, we move data using a delegate from detail to master. In this post we move the other direction. […]

  3. […] and updated version without ARC killing your detail’s outlets, go here to start the series: Swift Swift: Split View Controllers (Round Two) Part 1: The Master-Detail template For more previews and interesting thoughts, sign up for my newsletter. […]

  4. Hello, This example need to a few update. Thanks for good example.

    First, // MARK: – Split view

    func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let _ = secondaryAsNavController.topViewController as? MenuTableViewController else { return false }

    return true
    }

    Second ,

    override func viewDidLoad() {
    super.viewDidLoad()
    if let split = self.splitViewController {
    let controllers = split.viewControllers
    self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController

    as? MenuTableViewController
    }
    self.detailViewController?.delegate = self
    }

    1. I’m sure that is not the only one. all of chapter six is in need of an update. However, I cannot do so until next week as Apple is on Christmas break and not accepting submissions. I will be addressing all of this by the first week of January.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: