Swift WatchKit: Introducing Navigation to the Apple Watch(Part 4: Dismissals and Segues)

2015-05-29_06-46-47

In our lessons setting up navigation controllers on the Apple Watch, we’ve learned how to set up the storyboard,  how to use push controllers programmatically and introduced sending data to another controller and back using contexts and delegates.  In this lesson, we’ll pass data when you have a segue and learn how to dismiss controllers.

We’ll be using the same storyboard from the previous lesson and expanding it to match the illustration above. Open the project and go to the storyboard for the watch .  Bring the Down Branch into view.  If you did the exercise in part 2, two  child controllers,  One and Two   should also be visible.

2015-05-28_05-32-38

If not,  add two interfaces. In the attributes inspector, make the identifier and title  One, and the identifier and label for the other  Two.  Control-drag from the button One to the interface One. Select a push segue. Control-drag from the Two Button to the Two interface. Select a push segue.  It should now match the screenshot above.

Add a slider to the down branch  interface. Set the slider’s position to left and bottom.

2015-05-28_05-46-38

In the attributes inspector, change the attributes of the slider to make a slider from 0 to 10 with an initial value of 1:

2015-05-29_06-13-09

On the One interface,  add a group to the interface.  Like the last lesson, we will use this as a fake background to change colors.  Set the width and height to Relative to Container.  In the last lesson, we had a rounded corners on the background. We can control  how rounded a corner we want with the radius attribute. Check on Custom and set to 0 in the attribute inspector to give us sharp corners.

2015-05-28_05-50-34

In the color, change the color to Light Gray.  Change the group to a vertical group:

2015-05-28_05-57-54

Add a label and a button to the group.  Position the label Top and Left, and position the button Bottom and Left. Change the label’s text to Dark Text Color, with the text View One. Center align the label. Change the button’s color to Dark Text Color. Change the text to Back.

2015-05-28_06-10-34

In interface Two, add a label and two buttons. Position the label Top and Center. Position one button Left and Center and the other button Bottom and Center. Change the text on the label to View Two. Change the title on the center button to Back and the bottom button to Root.

2015-05-28_06-10-49

Make some controllers for these three interfaces. Press Command-N and Make a new Cocoa Touch  WKInterface  subclass called DownInterfaceController.

In the file dialog box toward the bottom you will find the save to group.

2015-05-28_06-27-38

Make sure you send it to Watchkit Extension Group, since it defaults to the WatchKit app.  If you don’t, your app will crash and Xcode may have some problems.

2015-05-28_06-22-42

Do the same for  two more controllers OneInterfaceController and TwoInterfaceController.

Go back to the storyboard.  Using the identity inspector, assign the proper classes to the Down branch, One and Two interface.

Open the assistant editor.  Select the slider on the Down Branch interface. Control-drag to the code and make an action sliderDidChange.

Select the label on the One interface. Control drag and make an outlet label. Select the group. Add an outlet named backgroundGroup. Select the button. Control drag from the button and make an action backPressed. you should have in the code the following:

@IBOutlet weak var label: WKInterfaceLabel!
@IBOutlet weak var backgroundGroup: WKInterfaceGroup!
@IBAction func backPressed() {
}

Select the label on the Two Interface. Control-drag and make an outlet label. Control-drag from the  Back button and make an action backPressed. Control drag from the  Root button and make an action rootPressed. You will have this in your code:

@IBOutlet weak var label: WKInterfaceLabel!
@IBAction func backPressed() {
}
@IBAction func rootPressed() {
}

Close the assistant editor. Go to the DownInterfaceController code. Add the following variable:

   var  sliderValue:Float = 1.0

Change the action sliderDidChange to this:

    @IBAction func sliderDidChange(value: Float) {
        sliderValue = value
    }

When we used pushInterfaceController we had the context as a parameter. For a segue, there is an equivalent to prepareForSegue in WatchKit, though it works differently.  The method contextForSegueWithIdentifier returns an object that will be used as the context. Like prepareForSegue, use the identifier for a set of conditionals to send the correct context to a destination controller. As an example, add the following code:

    override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
        if segueIdentifier == "One"{
            return sliderValue
        }
        if segueIdentifier == "Two"{
            return String(format: "Two %f",sliderValue)
        }
        return nil
    }

We have two segues, One and Two.  I use a different context for each. If I go to One, I send the Float value of sliderValue. If I go to Two, I send a string. If I get a value that is neither of these, I return nil. If anything goes wrong here, such as forgetting to set the identifiers correctly,  I’ll get a unexpectedly found nil while unwrapping an Optional value error when I unwrap context in the destination. That will tell us to check here first for an error.

Speaking of identifiers, If we are using this method, just like prepareForSegue, our segues need identifiers.  We haven’t added them yet, so select the segue for One and make the identifier One.

2015-05-28_06-50-20

Add the identifier Two to the Two segue.

2015-05-28_06-50-57

 Convert the context to properties in the destination controllers. Go to the OneInterfaceController code.  Add the following code to awakeWithContext:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
// Configure interface objects here.
    var value = context as! Float
    label.setText(String(format:"One: %f",value))
    var hue = CGFloat(value / 10.0)
    backgroundGroup.setBackgroundColor(UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0))
}

In this controller, we had a float value, which we make a variable  value to store its value. We format  value in  the label, then use  value to create a hue that we use for a background color.

Now go to the TwoInterfaceController and change awakeWithContext to this:

override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        let labelString = context as! String
        label.setText(labelString)
        // Configure interface objects here.
    }

This code is simpler than the last code. It takes the string and outputs to the label.

Dismissal Code

Navigation dismissal is even easier than UIKit. There is no animation flag to get in the way. Just pop the controller with popcontroller. Go to OneInterfaceController and change backPressed to this:

 @IBAction func backPressed() {
        popController()
    }

Then go to TwoInterfaceController and change backPressed and rootPressed to this:

    @IBAction func backPressed() {
        popController()
    }
    
    @IBAction func rootPressed() {
        popToRootController()
    }

We have the method popToRootController available to us to pop all the way back to the root.

Build and Run.  Tap the Down button

2015-05-29_06-15-13

Tap One.

2015-05-29_06-15-43

Tap Back, and you go back to Down Branch.  Click on the slider to change its value,

2015-05-29_06-17-21

then tap One. We get a color and value change.

2015-05-29_06-16-38

 

Tab Back again, and then tap Two. We get the second screen with a value.

2015-05-29_06-17-09

Tap Root and we are back at the root controller.

2015-05-29_06-17-32

 

Here’s a video of the demo in operation:

 navigation watchkit demo

We have covered most of what is necessary to use navigation and page controllers in WatchKit. you can do alomst every you can do with a UIKit navigation  controller with a WatchKit interface controller as a hierarchical controller.  Storyboard segues, passing data between controllers with segues, contexts and delegates and dismissing interfaces is pretty clear. While we learned you cannot add both page and hierarchical interfaces to the same app,  in our next lesson we’ll learn another controller you can use in both types of interfaces: modal interfaces. Along the way, we’ll see how to use contextForSegueWithIndentifier for page interfaces.

The Whole Code

DownInterfaceController.swift

//
//  DownInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/28/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class DownInterfaceController: WKInterfaceController {

    var  sliderValue:Float = 1.0

    @IBAction func sliderDidChange(value: Float) {
        sliderValue = value
    }
    override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
        if segueIdentifier == "One"{
            return sliderValue
        }
        if segueIdentifier == "Two"{
            return String(format: "Two %2.2f",sliderValue)
        }
        return nil
    }
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

OneInterfaceController.swift

//
//  OneInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/28/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class OneInterfaceController: WKInterfaceController {

    @IBOutlet weak var label: WKInterfaceLabel!

    @IBOutlet weak var backgroundGroup: WKInterfaceGroup!
    @IBAction func backPressed() {
        popController()
    }
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        var value = context as! Float
        label.setText(String(format:"One: %f",value))
        var hue = CGFloat(value / 10.0)
        backgroundGroup.setBackgroundColor(UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0))
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

TwoInterfaceController.swift

//
//  TwoInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/28/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class TwoInterfaceController: WKInterfaceController {

    @IBOutlet weak var label: WKInterfaceLabel!

    @IBAction func backPressed() {
        popController()
    }

    @IBAction func rootPressed() {
        popToRootController()
    }
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        let labelString = context as! String
        label.setText(labelString)
        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

UpInterfaceController.swift

//
//  UpInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/21/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class DarkOrLight:NSObject{
    var isDarkColor = false
    var count = 0
    var delegate:AnyObject? = nil
}

class UpInterfaceController: WKInterfaceController,ColorInterfaceDelegate {

    //MARK: - Outlets
    var isGoingToGray = true
    var isDarkColor = true

    @IBOutlet weak var colorSwitch: WKInterfaceSwitch!
    @IBOutlet weak var navigationSwitch: WKInterfaceSwitch!
    //MARK: -  Actions

    @IBAction func navigationSwitchDidChange(value: Bool) {
        if value{
            navigationSwitch.setTitle("Gray")
        }else{
            navigationSwitch.setTitle("Color")
        }
        isGoingToGray = value
    }

    func colorDidChange(value:Bool) {
        isDarkColor = value
        print(isDarkColor)
        updateColorSwitch()
        //popController()

    }

    func updateColorSwitch(){
        colorSwitch.setOn(isDarkColor)
        if isDarkColor{
            colorSwitch.setTitle("Dark")
        }else{
            colorSwitch.setTitle("Light")
        }
    }

    @IBAction func colorSwitchDidChange(value: Bool) {
        isDarkColor = value
        updateColorSwitch()
    }

    @IBAction func goButtonPressed() {
        //prepare context
        var myContext:Bool? = isDarkColor
        //logical navigtion
        if isGoingToGray {
            var myContext:Bool? = isDarkColor
            pushControllerWithName("Gray", context: myContext)
        }else{
            var myContext = DarkOrLight()
            myContext.isDarkColor = isDarkColor
            myContext.count = 2
            myContext.delegate = self  // <-- add the delegate
            pushControllerWithName("Color", context: myContext)
        }

    }

    //MARK: - Life Cycle
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.

    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        updateColorSwitch()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

GrayInterfaceController.swift

//
//  GrayInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/21/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class GrayInterfaceController: WKInterfaceController {

    //MARK: -  Properties, Constants and Outlets
    var isDarkColor = false
    let darkColor = UIColor.darkGrayColor()
    let lightColor = UIColor.lightGrayColor()

    @IBOutlet weak var grayBackgroundGroup: WKInterfaceGroup!
    //MARK: -  Life Cycle
        override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        // Configure interface objects here.
            isDarkColor = context as! Bool //change context into property
        if isDarkColor {
            grayBackgroundGroup.setBackgroundColor(darkColor)
        } else {
            grayBackgroundGroup.setBackgroundColor(lightColor)
        }

    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

ColorInterfaceController.swift

//
//  ColorInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/21/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

protocol ColorInterfaceDelegate{
    func colorDidChange(value:Bool)
}

class ColorInterfaceController: WKInterfaceController {

    var isDarkColor = false
    let darkColor = UIColor(red: 68.0/255.0, green: 0.0, blue: 136.0/255.0, alpha: 1.0)
    let lightColor = UIColor(red: 187.0/255.0, green: 1.0, blue: 0, alpha: 1.0)
    var delegate:ColorInterfaceDelegate? = nil

    @IBOutlet weak var colorSwitch: WKInterfaceSwitch!
    @IBOutlet weak var colorBackgroundGroup: WKInterfaceGroup!

    @IBAction func colorSwitchDidChange(value: Bool) {
        isDarkColor = value
        refreshDisplay()
        delegate?.colorDidChange(value)
    }

    func refreshDisplay(){
        colorSwitch.setOn(isDarkColor)
        if isDarkColor {
            colorBackgroundGroup.setBackgroundColor(darkColor)
            colorSwitch.setTitle("Dark")
        } else {
            colorBackgroundGroup.setBackgroundColor(lightColor)
            colorSwitch.setTitle("Light")
        }
    }

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        // Configure interface objects here.
        //convert the context to usuable properties
        let myContext = context as! DarkOrLight
        delegate = myContext.delegate as? ColorInterfaceDelegate //<-- add delegate
        let count = myContext.count
        isDarkColor = myContext.isDarkColor as Bool
        // update display
        refreshDisplay()
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

One Reply to “Swift WatchKit: Introducing Navigation to the Apple Watch(Part 4: Dismissals and Segues)”

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