Make App Pie

Training for Developers and Artists

The Swift Swift Tutorial: How to Use Split View on iPad (Round 1)

Back in July of 2014, I wrote this first post on using UISplitViewController in Swift. I gave how to use Apple’s template, which has a few interesting quirks, such as ARC killing@IBOutlets at the drop of a hat. I said I would write another post on the subject. I ended up making this section 2 of Chapter six of my Book Swift Swift: UI View Controllers . 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.It came out as three projects, which if you want 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. –Steve

Two and a half weeks ago I had a request from a reader to cover split views and split view controllers in Swift. My apologies for taking this long, but now I know the reason for this user’s request. For those not familiar with split view controllers, they are a view controller meant for the iPad. They take two view controllers and place them in the same window. On the left  is the master, and is almost always a table view controller. The other view controller is known as the detail, which is a standard UIView controller.

Portrait Split Image View
Portrait Split Image View

I will admit I had a lot of problems implementing split views in Swift. I finally did get it to work. There is a change in Swift’s implementation that requires a bit of getting used to. This will be the first round of split views. In this post, I’ll show how to use the master-detail template to get them to work. After I cover a few other topics which will be useful in a more advanced version, I’ll revisit split views in Swift.

The Problem with the Detail Controller

The way I learned to make a split view with Objective-C was by dragging out a split view controller on a blank storyboard, and writing everything myself. That doesn’t seem to work too well and I was getting one of Swift’s most common error messages: fatal error: Can't unwrap Optional.None This is the run-time error when you unwrap a nil value from an optional.  This has to do with very weak references. The error happens in @IBOutlets in the DetailViewController . Outlets are by definition weak, and by Swift’s definition anything weak is also an optional. According to Apple’s book Using Swift with Cocoa and Objective-C. but @IBOutlet var name:Type is all we need to write. The compiler assumes the rest for us and defines an outlet as @IBOutlet weak var name: Type! = nil

Outlets in the detail controller cannot be directly accessed like an outlet hooked up anywhere else on a storyboard. In Swift, outlets are so weak in this case they are usually nil. Somehow the meager connections to the split view controller are not enough to keep them alive, though with the equivalent code in Objective-C there are enough strong pointers. In Swift, if you try something simple like this in the detail:

pizzaLabel.text = "Veggie"

That run-time error fatal error: Can't unwrap Optional.None. appears.

Apple’s Master-Detail template does work however.  There is a way to do this. In this lesson, we’ll set up the template to make a split view for PizzaDemo. We’ll cut and paste a lot of code from our last post on table views, and add a few new parts for the Split controller. As an iPad application, we will change our UI a bit from the iPhone. We’ll make our master the list of pizza types, and replace the segmented control with the master. In this session, we will not change the prices, but just get the data displayed in the model.

Get the template

In Xcode start a new project on the welcome screen, press command-shift-N or go to File>New>Project… Select in the iOS Application the Master-Detail Application. Name the app SwiftSplitPizzaDemo select Swift for the language and iPad for the device. Keep Core Data unchecked.

Select your file location and click Create. In the files created click the story board and look at what Xcode loaded for you.

The split view controller splits into two navigation controllers. One navigation controller has a UITableViewController subclass found in MasterViewController.swift. The other navigation controller has a UIViewController  subclass, found in DetailViewController.swift.

Xcode provides some sample code in the template to run an small application. Change to the iPad2 simulator, since it will fit on any screen nicely. Build and run. You get a pretty blank screen with a label on it. On your keyboard press the command key and the left or right arrow keys together a few times to rotate the ipad. As you change the orientation, the screen changes. Go back to portrait, and swipe right. the master view slides out. Press the plus button in the corner of the master view to add to the table. Rotate to a landscape view and hit plus again. Now select one of your two selections in the table view. The label in the detail changes.

We know what is there works. Now to replace this code with our own. Go to the storyboard. Start by selecting the dynamic table cell, changing the table cell to tableCell in the storyboard.  Also change the cell style to right detail.

Add the Model

Open the PizzaDemo application with the table view in it from our last lesson, if you want to copy and paste from there. If you do not have it, you can download it from github or you can copy what I have below.

We first need a Model. if you have the PizzaDemo code open, drag the Pizza.swift file over to SwiftSplitPizzaDemo. make sure Copy items if needed has a check next to it, and hit Finish. If you do not have the PizzaDemo open and don’t want to load it, hit command-N on the keyboard, and make a Cocoa Touch class called Pizza in Swift which subclasses NSObject. In the new Pizza.swift file, add this code:

import Foundation
/* --------

Our model for MVC
keeps data  and calculations
about pizzas

------------*/

class Pizza {
    var pizzaPricePerInSq = ["Cheese": 0.03 , "Sausage": 0.06 , "Pepperoni": 0.05 , "Veggie": 0.04]
    var typeList:Array {  //computed property 7/7/14
    get{
        return Array(pizzaPricePerInSq.keys)
    }
    }

    let pi = 3.1415926

    var pizzaDiameter = 0.0
    let maxPizza = 24.0
    var pizzaType = "Cheese"

    var radius : Double {  //computed property
    get{   //must define a getter
        return pizzaDiameter/2.0
    }
    set(newRadius){ //optionally define a setter
        pizzaDiameter = newRadius * 2.0
    }
    }

    var area :  Double {
    get{
        return pizzaArea()
    }
    }

    func pizzaArea() -> Double{
        return radius * radius * pi
    }

    func unitPrice() ->Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if unitPrice != nil {
            return unitPrice!
        }                               //optional type ?Double checking for nil
        else {
            return 0.0
        }
    }

    func pizzaPrice() ->Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if unitPrice != nil {                                   //optional type ?Double checking for nil
            return pizzaArea() * unitPrice!             //unwrapping the optional type
        }
        return 0.0
    }

    func diameterFromString(aString:NSString) -> Double {
        switch aString {
        case "Personal":
            return 8.0
        case "10\"":
            return 10.0
        case "12\"":
            return 12.0
        case "16\"","15\"":
            return 16.0
        case "18\"":
            return 18.0
        case "24\"":
            return 24.0
        default:
            return 0.0
        }
    }

}

Add the Master

Go to the MasterViewController.swift file. Go into the MasterViewController class and comment out the following line:

var objects = NSMutableArray()

This will produce a lot of error messages. We will need to change the code in each place there is an error message. To add our model, just under where you commented out, objects add:

var pizza = Pizza()

Add the TableView Code

Scroll down to where the table view data source is:

   //MARK:  - Table View

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

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

        let object = objects[indexPath.row] as NSDate
        cell.textLabel.text = object.description
        return cell
    }

Replace it with the following code.

//MARK: -Table View
    override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
        // Return the number of sections.
        return 1
    }

    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        // Return the number of rows in the section.
        return pizza.pizzaPricePerInSq.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell
        let row = indexPath!.row   //get the array index from the index path
        let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as UITableViewCell  //make the cell
        let myRowKey = pizza.typeList[row]  //the dictionary key
        cell.textLabel!.text = myRowKey
        let myRowData = pizza.pizzaPricePerInSq[myRowKey]  //the dictionary value
        cell.detailTextLabel!.text = String(format: "%6.3f",myRowData!)
        return cell
    }

    override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
        return 44.0
    }

We will not be editing the table. Comment out this method for insertion:

/*
    func insertNewObject(sender: AnyObject) {
        if objects == nil {
            objects = NSMutableArray()
        }
        objects.insertObject(NSDate.date(), atIndex: 0)
        let indexPath = NSIndexPath(forRow: 0, inSection: 0)
        self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
    }
*/

Also comment out this method for deleting rows:

/*
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            objects.removeObjectAtIndex(indexPath.row)
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
*/

Disable editing of the table by returning false here:

    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return false
    }

We just made a table. We covered how to do that last time. Here we begin to see something new. Change tableview(didSelectRowAtIndexpath:indexPath:) to this:

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        //let object = objects[indexPath.row] as NSDate
        //self.detailViewController!.detailItem = object
        pizza.pizzaType = pizza.typeList[indexPath.row] //set to the selected pizza
        if (detailViewController != nil){
            self.detailViewController!.detailItem = pizza //send the model to the detailItem
        }

    }

Looking at the top of our code the master view controller makes an instance of the DetailViewController class. Here we set the detailItem property of the detail view controller, whose type is AnyObject!. This is key to everything, but we’ll discuss the property more in a few minutes. We set the pizzaType property to the selected pizza, and then send the model to the detail controller via this property.

To be consistent, handle an ios8 issue, and to get rid of the last error, change prepareForSegue() like this:

 override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "showDetail" {
            let indexPath = self.tableView.indexPathForSelectedRow()
            //    let object = objects[indexPath.row] as NSDate
            //((segue.destinationViewController as UINavigationController).topViewController as DetailViewController).detailItem = object
            pizza.pizzaType = pizza.typeList[indexPath!.row] //set to the selected pizza
            ((segue.destinationViewController as UINavigationController).topViewController as DetailViewController).detailItem = pizza
        }
    }

Line 7 will not make a lot of sense if you are unfamiliar with split view controllers. Take a look at the storyboard:

The storyboard
The storyboard

One of a split view controller’s properties is an array called viewControllers. At most it has two objects,  navigation controllers for the master and the detail views. Embedded in each of these navigation controllers is a master and detail view. For the segue, we go from the master controller to the navigation controller, which is the segue’s destination. The first view controller in the destination navigation view controller’s array of controllers is the detail view. We access that by ((segue.destinationViewController as UINavigationController).topViewController Once we have the detail view controller, we assign the model to the detailItem property.

Now change viewDidLoad to this:

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
/* Removed code for editing and use of bar buttons.
        self.navigationItem.leftBarButtonItem = self.editButtonItem()

        let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
        self.navigationItem.rightBarButtonItem = addButton
*/
        let controllers = self.splitViewController!.viewControllers
        self.detailViewController = controllers[controllers.endIndex-1].topViewController as? DetailViewController

        }

Lines 10 and 11 set up the detail view controller in the master. It takes the detail view from the viewControllers array we assigned to controllers We also removed the bar button item navigation bar stuff since we will not be using it in this tutorial.

We now have a completed master view controller. Essentially it is a table view with some odd connections to a property in the detail view controller. Build and run. In portrait swipe from left to right. Try rotating the iPad and see what happens.

Add the Detail

The next step is to design the detail view controller. We will be showing a lot more information on this scene, so a good layout is important.

Design and Connect the Scene

Go into the storyboard and go to the detail view controller there. Add five labels  with the text Pizza:, Size:, Anchovy, 12″, and Pizza Info And Price. Add one button with a blue background and white foreground labeled 10″. Copy the button five times. Resizing as necessary, lay out and label everything something like this:

Layout for the detail view

I did use auto layout here since we will be rotating the iPad.

Open the assistant editor to the detail view controller and add the following outlets by control dragging from the Anchovy, 12″, and Pizza Info And Price labels on the storyboard.

    @IBOutlet var pizzaSizeLabel: UILabel!
    @IBOutlet var pizzaTypeLabel: UILabel!
    @IBOutlet var pizzaPriceLabel: UILabel!

Adding Code for the DetailViewController

first, add the model to the view

var pizza = Pizza()

Next, add this code for the size buttons.

@IBAction func pizzaSizeButton(sender: UIButton) {
pizza.pizzaDiameter = pizza.diameterFromString(sender.titleLabel.text)
configureView()
}

Drag from the circle to each of the buttons to connect them all to this method.

Take a look at this code:

    var detailItem: AnyObject? {
        didSet {
            // Update the view.
            self.configureView()
//comment out this if clause  if you want the master to not disappear.
            if self.masterPopoverController != nil {
                self.masterPopoverController!.dismissPopoverAnimated(true)
            }
        }
    }

Lines 1-4 are at the heart of the way we get outlets to work. The MasterViewController instantiated a detailViewController. The master then set this property detailItem with the model, which is the data we wanted to pass to the detail controller. While just displaying is not strong enough a connection to keep the outlets alive, this connection is. To use an outlet we refer to detailItem. When set, detailItem calls configureView(), which looks like this:

    func configureView() {
        // Update the user interface for the detail item.
        if let detail: AnyObject = self.detailItem {
            if let label = self.detailDescriptionLabel {
                label.text = detail.description
            }
        }
    }
  

This is Apple’s template code to put text on a label. In line 2 we do two things at once. First we assign detailItem to a new constant detail. If that is successful, the app moves on to assigning another variable label to a UILabel. If that is successful, the app uses the assigned label to change the label properties. We sneakily assigned a strong pointer to a bunch of weak ones, at least for the life of the method. This is the way to access IBOutlets. Replace this code with the following:

     func configureView() {
        // Update the user interface for the detail item.
        if let detail = self.detailItem as? Pizza {     //if we have an object in the detail item
            pizza = detail
            if let label = self.pizzaTypeLabel {
                label.text = detail.pizzaType
            }
            let pizzaSizeString = NSString(format:"%6.1fin Pizza",detail.pizzaDiameter)
            let priceString = NSString(format:"%6.2f sq in at $%6.2f is $%6.2f",detail.pizzaArea(),detail.unitPrice(),detail.pizzaPrice())
            if let label = self.pizzaSizeLabel{
                label.text = pizzaSizeString
            }
            if let label = self.pizzaPriceLabel{
                label.text = priceString
            }

        }

    }

A few things changed  in configureView() for this example. In line 2, since there is a single detail view,  detailItem receives only the model.  I cast this directly to a instance of Pizza. I assign this to the model in the detail controller. While the outlets can be set here in configureView() everything else won’t be. If we want the properties from the buttons to work, we need to set the model here. After setting detail, we follow the if let label= pattern for updating our labels. That’s the trick to getting outlets to work in split views.

The Model initializes to the same values on both controllers, head over to the master view controller’s viewDidLoad() and add the following line:

pizza.pizzaPricePerInSq["Pepperoni"] = 9.99

This places a marker to prove our connections work.
Build and run. Play around with the app in different orientations.
splitview demo 4

There’s Always One More Bug

If you try to change the size of a pizza before selecting one in the master view, you’ll find nothing happens. We need to run configureView() in the detailViewController to get it to work. Go back to the MasterViewController‘s viewDidLoad() and add the following:

//send the model to the detailItem
        if (detailViewController){
            self.detailViewController!.detailItem = pizza

The pizza = detail assignment buried in line 4 of configureView() connects up enough to get the buttons to work.

This code works, and using the master-detail template is easy, though cumbersome, once you know the trick I showed here. There are still a lot of unanswered questions. We’ll explore some of those in the upcoming installments.

The Whole Code

Here is the code for the model, master view controller and detail view controller for copying and review. You can find a copy of the completed  project at Github as well.

Pizza.Swift

//
//  Pizza.swift
//  pizzaDemo
//
//  Created by Steven Lipton on 7/1/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

import Foundation
/* --------

Our model for MVC
keeps data  and calcualtions
about pizzas

------------*/

class Pizza {
    var pizzaPricePerInSq = ["Cheese": 0.03 , "Sausage": 0.06 , "Pepperoni": 0.05 , "Veggie": 0.04]
    var typeList:Array {  //computed property 7/7/14
    get{
        return Array(pizzaPricePerInSq.keys)
    }
    }

    let pi = 3.1415926

    var pizzaDiameter = 0.0
    let maxPizza = 24.0
    var pizzaType = "Cheese"

    var radius : Double {  //computed property
    get{   //must define a getter
        return pizzaDiameter/2.0
    }
    set(newRadius){ //optionally define a setter
        pizzaDiameter = newRadius * 2.0
    }
    }

    var area :  Double {
    get{
        return pizzaArea()
    }
    }

    func pizzaArea() -> Double{
        return radius * radius * pi
    }

    func unitPrice() ->Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if (unitPrice != nil) {
            return unitPrice!
        }                               //optional type ?Double checking for nil
        else {
            return 0.0
        }
    }

    func pizzaPrice() ->Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if (unitPrice != nil) {                         //optional type ?Double checking for nil
            return pizzaArea() * unitPrice!             //unwrapping the optional type
        }
        return 0.0
    }

    func diameterFromString(aString:NSString) -> Double {
        switch aString {
        case "Personal":
            return 8.0
        case "10\"":
            return 10.0
        case "12\"":
            return 12.0
        case "16\"","15\"":
            return 16.0
        case "18\"":
            return 18.0
        case "24\"":
            return 24.0
        default:
            return 0.0
        }
    }

}

MasterViewController.swift

//
//  MasterViewController.swift
//  SwiftSplitPizzaDemo
//
//  Created by Steven Lipton on 7/17/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

import UIKit

class MasterViewController: UITableViewController {

//MARK: Life Cycle
    var detailViewController: DetailViewController? = nil
    //var objects = NSMutableArray()
    var pizza = Pizza()

    override func awakeFromNib() {
        super.awakeFromNib()
        self.clearsSelectionOnViewWillAppear = false
        self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
/* Removed code for editing and use of bar buttons.
        self.navigationItem.leftBarButtonItem = self.editButtonItem()

        let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
        self.navigationItem.rightBarButtonItem = addButton
*/
        let controllers = self.splitViewController!.viewControllers
        self.detailViewController = controllers[controllers.endIndex-1].topViewController as? DetailViewController

        pizza.pizzaPricePerInSq["Pepperoni"] = 9.99

        //send the model to the detailItem
        if (detailViewController != nil ){
            self.detailViewController!.detailItem = pizza
        }

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
/*
    func insertNewObject(sender: AnyObject) {
        if objects == nil {
            objects = NSMutableArray()
        }
        objects.insertObject(NSDate.date(), atIndex: 0)
        let indexPath = NSIndexPath(forRow: 0, inSection: 0)
        self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
    }
*/
//MARK: - Segues

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "showDetail" {
            let indexPath = self.tableView.indexPathForSelectedRow()
            //    let object = objects[indexPath.row] as NSDate
            //((segue.destinationViewController as UINavigationController).topViewController as DetailViewController).detailItem = object
            pizza.pizzaType = pizza.typeList[indexPath!.row] //set to the selected pizza
            ((segue.destinationViewController as UINavigationController).topViewController as DetailViewController).detailItem = pizza
        }
    }
//MARK:  - Table View Delegates
    override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 1
    }

    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete method implementation.
        // Return the number of rows in the section.
        return pizza.pizzaPricePerInSq.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
        //note I did not check for nil values. Something has to be really broken for these to be nil.
        let row = indexPath!.row   //get the array index from the index path
        let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath!) as UITableViewCell  //make the cell
        let myRowKey = pizza.typeList[row]  //the dictionary key
        cell.textLabel!.text = myRowKey
        let myRowData = pizza.pizzaPricePerInSq[myRowKey]  //the dictionary value
        cell.detailTextLabel!.text = String(format: "%6.3f",myRowData!)
        return cell
    }

    override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return 44.0
    }

    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return false
    }
/*
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            objects.removeObjectAtIndex(indexPath.row)
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
*/
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        //let object = objects[indexPath.row] as NSDate
        //self.detailViewController!.detailItem = object
        pizza.pizzaType = pizza.typeList[indexPath.row] //set to the selected pizza
        if (detailViewController != nil){
            self.detailViewController!.detailItem = pizza //send the model to the detailItem
        }

    }

}

DetailViewController.Swift

//
//  DetailViewController.swift
//  SwiftSplitPizzaDemo
//
//  Created by Steven Lipton on 7/17/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

import UIKit

class DetailViewController: UIViewController, UISplitViewControllerDelegate {

    @IBOutlet var pizzaSizeLabel: UILabel!
    @IBOutlet var pizzaTypeLabel: UILabel!
    @IBOutlet var pizzaPriceLabel: UILabel!
    @IBOutlet var detailDescriptionLabel: UILabel!
    var pizza = Pizza()

    var masterPopoverController: UIPopoverController? = nil

    var detailItem: AnyObject? {
        didSet {
            // Update the view.
            self.configureView()

            //Comment out this if clause if you don't want the Master to disappear.
             if self.masterPopoverController != nil {
                self.masterPopoverController!.dismissPopoverAnimated(true)
            }
        }
    }

    @IBAction func pizzaSizeButton(sender: UIButton) {
         pizza.pizzaDiameter = pizza.diameterFromString(sender.titleLabel!.text!)
        configureView()
    }

    func configureView() {
        // Update the user interface for the detail item.
        if let detail = self.detailItem as? Pizza {     //if we have an object in the detail item
            pizza = detail
            if let label = self.pizzaTypeLabel {
                label.text = detail.pizzaType
            }
            let pizzaSizeString = NSString(format:"%6.1fin Pizza",detail.pizzaDiameter)
            let priceString = NSString(format:"%6.2f sq in at $%6.2f is $%6.2f",detail.pizzaArea(),detail.unitPrice(),detail.pizzaPrice()) //added 6/29/14
            if let label = self.pizzaSizeLabel{
                label.text = pizzaSizeString
            }
            if let label = self.pizzaPriceLabel{
                label.text = priceString //added 6/29/14
            }

        }

    }

    //MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.configureView()
    }

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

    //MARK: - Split View

    func splitViewController(splitController: UISplitViewController, willHideViewController viewController: UIViewController, withBarButtonItem barButtonItem: UIBarButtonItem, forPopoverController popoverController: UIPopoverController) {
        barButtonItem.title = "Master" // NSLocalizedString(@"Master", @"Master")
        self.navigationItem.setLeftBarButtonItem(barButtonItem, animated: true)
        self.masterPopoverController = popoverController
    }

    func splitViewController(splitController: UISplitViewController, willShowViewController viewController: UIViewController, invalidatingBarButtonItem barButtonItem: UIBarButtonItem) {
        // Called when the view is shown again in the split view, invalidating the button and popover controller.
        self.navigationItem.setLeftBarButtonItem(nil, animated: true)
        self.masterPopoverController = nil
    }

}

17 responses to “The Swift Swift Tutorial: How to Use Split View on iPad (Round 1)”

  1. […] some weak references, it is the only way you can get the values. Here’s an example from my recent post on Split View Controllers. The only way to access the outlets in the detail  view is through optional […]

  2. Nice! I have build a similar demo, but was looking for a way to show/hide the (Master) navigation panel. Any idea how to do that?

    1. We will be getting there soon. I needed to cover popovers before I did that, and that will be coming up in few posts, then the post after popovers will cover exactly this issue.

      1. Excellent tutorial. Did you ever show how to show/hide the Master navigation panel? Particularly hide it after didselectrowatindexpath is called?

      2. No, I haven’t yet. I’m trying to finish my book, and that is in the book’s chapter on split view controllers.

  3. Does this tutorial follow the code provided on git? It seems like it isn’t?

    1. Chris,
      It is another case of the current changes to Swift I documented in my post The Joys of Beta Swift: More with Optionals I’m fixing the code right now and the git should be correct in a few hours, and then I’ll edit the post to those changes. I’ll let you know when it is correct. Thank you very much for noticing it.

      1. The git has been updated and tested. I am proofreading the tutorial now and should have that up shortly.

      2. Everything should work now and be the same, except for a few comments.

  4. […] in July of 2014, I wrote one of the first posts about Split View controllers in Swift, and I gave how to use Apple’s template, which has a few interesting quirks, such as ARC […]

  5. […] in July of 2014, I wrote one of the first posts about Split View controllers in Swift, and I gave how to use Apple’s template, which has a few interesting quirks, such as ARC […]

  6. […] The Swift Swift Tutorial: How To Use Split View On iPad (Round 1)(makeapppie.com) […]

  7. Thanks for the tutorial, Steven. I tried adding another VC at the front, say like a Sign In VC, before getting to the Split View upon successful sign in and it didn’t seem to work (I think it’s to do with the Split VC code being put in the AppDidFinishLaunchingWithOptions App Delegate… Is there a way to make this work? Your help would be very much appreciated.

    1. It’s pretty much an all or nothing thing. Given the way the view controller gets set up, it’s really hard to set up the view controller for the template. Check out the round two version, which might work better for that., or might let you hide the master so it looks like a single controller. Links are above.

  8. […] The Swift Swift Tutorial: How to Use Split View on iPad (Round 1) […]

  9. […] into too much detail,  the variable a may be too weak to withstand ARC in certain situations, (see here for the classic example) and gets cleaned up before it can pass its value to […]

  10. I had done split view controller in swift 3 but here I got a problem that in iPad screen it was not displaying the selected item when I rotate the screen in landscape it was not working and master view controller also not displaying again I rotate then it was working fine and displaying all the selected items on details view controller any help how to fix this issue ?

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: