Swift Swift: Split View Controllers (Round Two) Part 2: Master to Detail

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

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

Moving Data from Master to Detail

Our next part is to move data from the master to the detail. We’ll make a new demo for this to choose a color and display its values in different color identification systems. We use colors since it is easy to make a model, instead of a more complicated order model.

New Project from the Storyboard.

Open a new project with Command-Shift-N. Select a Single View template. Name the project SwiftSplitColorDemo, as a Universal app with Swift as the language. Once saved, go to the storyboard, and delete the view controller there. Drag a split view controller out on to the storyboard from the object library. Click on the split view controller and check on the Is Initial View Controller in the attributes inspector. The storyboard looks slightly different from the template:

Screenshot 2015-03-09 07.20.43

The detail is a view controller, not a view controller embedded in a navigation controller as the template. Add three labels, and set up the view controller to look like this with white backgrounds on the labels, and a light gray background for the superview:

Screenshot 2015-03-09 08.40.31

Add auto layout by , pinning the Hue: label 50 points up, 0 left and 0 right with a height of 50 points. Pin the Red label 10 points up, 0 left and 0 right with a height of 50 points Pin the Hex RGB label 10 points up, 0 left and 0 right with a height of 50 points. Update the frames.

The Colorful Detail View Controller

Press Command-N and make a new Cocoa Touch Class named ColorViewController subclassing UIViewController with Swift as the language. Add the following code to the controller:

class ColorViewController: UIViewController {

    @IBOutlet weak var hsbLabel: UILabel!
    @IBOutlet weak var rgbLabel: UILabel!
    @IBOutlet weak var hexLabel: UILabel!

func displayColor(color:UIColor){
    view.backgroundColor = color

    var red:CGFloat = 0.0,green:CGFloat = 0.0,blue:CGFloat = 0.0
    var hue:CGFloat = 0.0, sat:CGFloat = 0.0,bright:CGFloat = 0.0
    var alpha:CGFloat = 0.0
    let hueFormat = "H:%5.2f  S:%5.2f  B:%5.2f"
    let rgbFormat = "R:%0.2f  G:%0.2f  B:%0.2f"
    let hexFormat = "Hex: %02X%02X%02X"
    if color.getHue(&hue,saturation: &sat,brightness: &bright,alpha: &alpha){
        hsbLabel.text = String(format:hueFormat,
             Double(hue),Double(sat),Double(bright) )
     }
     if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha){
         rgbLabel.text = String(format: rgbFormat,
             Double(red),Double(green),Double(blue))
         red = red * 255
         green = green * 255
         blue = blue * 255
         hexLabel.text = String(format:hexFormat,Int(red),Int(green),Int(blue))
     }
}

}

We have three outlets for our label in this controller. We have one function with a parameter color. The method first sets the background color of the view We do some initialization for the getRed and getHue methods. These two methods use some very evil parameter types. We do this setup to make the safest use. Both methods return a Boolean value, but we set up the parameters with & to make the RGB and HSB values pass-through parameters and thus we can read their values after the method executes. We check if we did convert a color in each case, and if we did, make a string with the results.

Make the Master View Controller

Press Command-N. Make a Cocoa Touch Class named ColorTableViewController, subclassing UIViewController. We’ll use our simplified dynamic table view again. Change the class code to this:

class ColorTableViewController: UITableViewController {
    let hueCount = 5
    let saturationCount = 10
}

This changes the class to a UITableViewController, and adds two constants. These two constants are the number of hues we will have and the number of saturation samples from that hue. We will tell the table view this through our two data source methods. Add the following code:

   override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return hueCount
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return saturationCount
    }

Next we will use a shortened version of the same background color technique we used in the table view section to make a list of colors .

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let hue = CGFloat(indexPath.section) / CGFloat(hueCount)
    let saturation = 1.0 - CGFloat(indexPath.row) / CGFloat(saturationCount)
    let color = UIColor(hue: hue, saturation: saturation,
        brightness: 1.0, alpha: 1.0)

    let cell = tableView.dequeueReusableCellWithIdentifier(
    "cell",
        forIndexPath: indexPath) as UITableViewCell
    cell.backgroundColor = color
    return cell
    }

Finally, we select the colors. We need to access the detail, and we access it through the controllers property as before. Add the following code:

override func tableView(tableView: UITableView,
    didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell = tableView.cellForRowAtIndexPath(indexPath)
    if let split = self.splitViewController {
        let controllers = split.viewControllers
        let detailViewController = controllers[controllers.count-1]
            as? ColorViewController
        detailViewController?.displayColor((cell?.backgroundColor)!)
    }
}

In this case, we have no navigation controller. So the controller in the last spot on controllers is the controller we want. We don’t need topViewController.

Add to the Storyboard

Go to the storyboard. Select the Root View Controller in the outline. In the identity inspector make its class ColorTableViewController. Select the table view’s cell. In the attributes inspector, change the identifier to cell. Drag a navigation item onto the top toolbar. Select the navigation item title and change the title to Master.
Select the view controller that is our detail. In the identity inspector, change the class to ColorViewController. Open the assistant editor. From the circle on each of the outlets drag to the appropriate label.
Select an iPad 2 in the simulator. Build and run. If the device is not in landscape, rotate the device with Command-Left Arrow. The master and detail will be side by side. Scroll through the colors and select a color on the master, and the info shows up in the detail.

Screenshot 2015-03-09 11.10.15

Adding a Navigation Controller

Many times we will need a navigation controller for detail, since we may need to access other scenes from this root scene. In the storyboard, select the detail view controller scene. Select Editor>Embed In>Navigation controller. A navigation controller shows up between the split view controller and the view controller.

Screenshot 2015-03-09 11.16.12

On the detail view, add a navigation item to to the space for the navigation bar. Title it Color Details.
In the didSelectrowAtIndexPath,change

let detailViewController = controllers[controllers.count-1]
    as? ColorViewController

to

let detailViewController = controllers[controllers.count-1].topViewController
    as? ColorViewController

Now we have a navigation controller and want the top view controller. Build and run again. It still works the same.

Screenshot 2015-03-11 05.47.09

Rotate back to portrait. It seems we cannot access the master. Swipe from left to right and it appears:

Screenshot 2015-03-11 05.47.31

Swipe right to left to dismiss it. Stop the simulator and change over to an iPhone 6. Run again:

Screenshot 2015-03-11 08.15.06

we get a screen with the toolbar for navigating to the master. Press the button and we get the master

Screenshot 2015-03-11 08.15.21

However we get stuck. There is no way back.

Adding a Segue

We’ve used didSelectRowAtIndexPath to select our information. That works for split view controllers on regular width devices. We will add segue from the table view cells to bring us to the detail. This will work on both compact and regular width devices. Go to the storyboard, and open up the document outline. Find the detail navigation controller and the cell for the table view.

Screenshot 2015-03-11 08.31.48

Control-drag from cell to Navigation Controller. Select a Show Detail selection segue. In the attributes inspector, name it detail. Build and run. You will find that selecting a cell sends us back to the detail, though nothing happens.

Using prepareForSegue with Split Views

With the segue operational, the didSelectRowAtIndexPath code gets eclipsed. What we did in that delegate we need to move over to a prepareForeSegue method. Open the ColorTableViewController.swift file. Add to the ColorTableViewController code the following:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "detail"{
        let detailNavigationController = segue.destinationViewController as?
            UINavigationController
    }
}

This is the standard start to this method. We assign the proper view controller to the destination. Our destination is a navigation controller, not a view controller. We need the top view controller from the navigation controller. Add this below the detailNavigationController assignment:

let detailViewController = detailNavigationController?.topViewController as
    ColorViewController
We have the correct controller.  Add this :
let indexPath = (tableView.indexPathForSelectedRow())!
let cell = tableView.cellForRowAtIndexPath(indexPath)
detailViewController.displayColor((cell?.backgroundColor)!)

In didSelectRowforIndexPath we had a parameter indexPath which is the index for the selected cell. In prepareForSegue, we get the index path from the indexPathForSelectedRow method. Once we have an index path, we get the cell as we did before. We then call the detail’s method displayColor with the background color of the cell to display the information in the detail. Build and run. Go to the master and select a color:

Screenshot 2015-03-11 09.07.39

We now have colors.

Hiding and Showing the Master

Hiding and showing the master needs to be more under user control. Add to the end of the prepareforSegue method this code:

detailViewController.navigationItem.leftBarButtonItem =
    self.splitViewController?.displayModeButtonItem()
detailViewController.navigationItem.leftItemsSupplementBackButton = true

The split view controller has a special bar button method displayModeButtonItem. This method creates a special bar button for controlling the master. While it works in viewDidLoad, it causes a crash on auto-rotation. It seems to work correctly only when added here. Using an iPhone 5s simulator, build and run. Look at the detail in portrait and landscape

Screenshot 2015-03-11 09.45.23 Screenshot 2015-03-11 09.45.37
Now try an iPad 2:

Screenshot 2015-03-11 09.46.23 Screenshot 2015-03-11 09.46.40
And finally a iPhone 6 plus

Screenshot 2015-03-11 09.48.01 Screenshot 2015-03-11 09.48.13 Screenshot 2015-03-11 09.48.22
The system makes a different button for each device in each orientation. It uses the most appropriate button look for that device.

In our last part about split view controllers, we’ll cover material from my original post — the disappearing outlet and what to do about it.

One Reply to “Swift Swift: Split View Controllers (Round Two) Part 2: Master to Detail”

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 )

Google+ photo

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

Connecting to %s