2. Apple Watch Interface Navigation

It’s really hard to design products by focus groups. A lot of times, people don’t know what they want until you show it to them.

Steve Jobs

In Chapter 1, “Getting Started with WatchKit Programming,” you learned about the various specifications and features of the Apple Watch. You also had the chance to use Xcode to create a simple iPhone project that supports the Apple Watch. You then used the Apple Watch Simulator to test the application. In this chapter, you dive into how your Apple Watch application navigates between multiple screens.

Interface Controllers and Storyboard

As you learned in Chapter 1, the user interface of your Apple Watch application is encapsulated in a storyboard file. Within the storyboard file, you have an Interface Controller that represents a screen on the Apple Watch. In this section, let’s create a project so that we can examine the storyboard in more detail:

1. Using Xcode, create an iOS App with WatchKit App project and name it LifeCycle. Uncheck the option Include Notification Scene so that we can keep the WatchKit project to a bare minimum.

2. Select the Interface.storyboard file located within the LifeCycle WatchKit App group (see Figure 2.1). This opens the file using the Storyboard Editor.

Image

Figure 2.1 Editing the storyboard file

3. Select the Interface Controller and view its Identity Inspector window (see Figure 2.2). The Class is set to InterfaceController, which means that it is represented by a Swift class named InterfaceController.

Image

Figure 2.2 The Interface Controller is represented by a Swift class named InterfaceController

4. View its Attributes Inspector window and observe that the Is Initial Controller attribute is checked (see Figure 2.3). This attribute indicates that, when the application is loaded, this is the default Interface Controller that will be displayed.

Image

Figure 2.3 The Is Initial Controller attribute indicates that the current InterfaceController will be displayed when the application loads

Lifecycle of an Interface Controller

As you have seen in the previous section and in Chapter 1, an Interface Controller is connected to a Swift class located in the WatchKit Extension group of the project. In this example, this Swift class is named InterfaceController.swift. It has the following content:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    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()
    }

}

Specifically, it has three key methods:

Image awakeWithContext:—The system calls this method at initialization time, passing it any contextual data from a previous Interface Controller. You should use this method to initialize and to prepare your UI for display, as well as to obtain any data that is passed to it from another Interface Controller (you will learn how this is done in the later section on passing data).

Image willActivate—This method is called by the system when the Interface Controller is about to be displayed. You should use this method to make some last-minute changes to your UI and refrain from performing any tasks that initialize the UI—these should be done in the awakeWithContext method.

Image didDeactivate—This method is called when the Interface Controller is no longer onscreen. You should use this method to perform cleanup operations on your Interface Controller, such as invalidating timers or saving state-related information.

Besides the three methods just discussed, you can also add an initializer to the Interface Controller class:

    override init() {
        super.init()
    }

You can also perform initialization for your Interface Controller in this initializer, but you should leave the bulk of the UI initialization to the awakeWithContext: method.

Let’s try an example to better understand the use of the various methods:

1. Add the following statements in bold to the InterfaceController.swift file:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    override init() {
        super.init()
        print("In the init initializer")
    }

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        print("In the awakeWithContext event")
    }

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

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

}

2. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. When the application is loaded onto the Apple Watch Simulator, you should see the statements printed out in the Output window in Xcode, as shown in Figure 2.4. Observe that the init, awakeWithContext:, and willActivate methods are fired when the Interface Controller is loaded.

Image

Figure 2.4 Examining the events that are fired when an Interface Controller is loaded


Note

If you are not able to see the Output window, press Command-Shift-C in Xcode.


3. With the Apple Watch Simulator selected, select Hardware | Lock to lock the Apple Watch. Observe the output in the Output window (see Figure 2.5). Observe that the didDeactivate method is now executed.

Image

Figure 2.5 Examining the event that is fired when an Interface Controller is deactivated


Note

The didDeactivate method is also fired when an Interface Controller transits to another Interface Controller.



Note

To unlock the Apple Watch Simulator, unlock the iPhone Simulator by selecting Hardware | Home.


Navigating between Interface Controllers

The basic unit of display for an Apple Watch app is represented by an Interface Controller (of type WKInterfaceController). Depending on the type of application you are working on, there are times when you need to spread your UI across multiple Interface Controllers. In Apple Watch, there are two ways to navigate between Interface Controllers:

Image Hierarchical: Pushes another Interface Controller on the screen. This model is usually used when you want the user to follow a series of related steps in order to perform a particular action.

Image Page-Based: Displays another Interface Controller on top of the current Interface Controller. This model is usually used if the information displayed on each Interface Controller is not closely related to other Interface Controllers. You can also use this model to display a series of Interface Controllers, which the user can select by swiping the screen.


Similarities to iPhone Development

The page-based navigation method is similar to presenting a modal View Controller in iPhone, whereas the hierarchical navigation method is similar to using a navigation controller in iPhone.


Hierarchical Navigation

A hierarchical interface always starts with a root Interface Controller. It then pushes additional Interface Controllers when a button or a control in a screen is tapped.

1. Using Xcode, create an iOS App with WatchKit App project and name it UINavigations. Uncheck the option Include Notification Scene so that we can keep the WatchKit project to a bare minimum.

2. In the UINavigation WatchKit App group, select the Interface.storyboard file to edit it in the Storyboard Editor.

3. Drag and drop another Interface Controller object onto the editor, as shown in Figure 2.6. You should now have two Interface Controllers.

Image

Figure 2.6 Adding another Interface Controller to the storyboard

4. In the original Interface Controller, add a Button control (see Figure 2.7) and change its title (by double-clicking it) to Next Screen.

Image

Figure 2.7 Adding a Button control to the first Interface Controller

5. Control-click the Next Screen button and drag it over the second Interface Controller (see Figure 2.8).

Image

Figure 2.8 Control-click the Button control and drag it over the second Interface Controller

6. You see a popup called Action Segue. Select push (see Figure 2.9).

Image

Figure 2.9 Creating a push segue

A segue has been created (see Figure 2.10), linking the first Interface Controller to the second.

Image

Figure 2.10 The segue that is created after performing the action

7. Select the segue and set its Identifier to hierarchical in the Attributes Inspector window (see Figure 2.11). This identifier allows us to identify it programmatically in our code later.

Image

Figure 2.11 Naming the Identifier for the segue

8. Add a Label control to the second Interface Controller, as shown in Figure 2.12. Set the Lines attribute of the Label control to 0 in the Attributes Inspector window so that the label can wrap around long text (used later in this chapter).

Image

Figure 2.12 Adding a Label control to the second Interface Controller

9. You are now ready to test the application. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Click the Next Screen button and observe that the application navigates to the second Interface Controller containing the Label control (see Figure 2.13). Also, observe that the second Interface Controller has a < icon (known as a chevron) displayed in the top-left corner. Clicking it returns the application to the first Interface Controller.

Image

Figure 2.13 Navigating to another Interface Controller using hierarchical navigation


Note

At this point, the Label control on the second Interface Controller is still displaying the default text “Label.” In later sections in this chapter, you learn how to pass data from the first Interface Controller to the second and then how to display the data in the Label control.


Page-Based Navigation

You can also display an Interface Controller modally. This is useful if you want to obtain some information from the user or get the user to confirm an action.

1. Using the same project created in the previous section, add another Button control to the first Interface Controller, as shown in Figure 2.14. Change the title of the button to Display Screen.

Image

Figure 2.14 Adding another Button control to the first Interface Controller

2. Create a segue connecting the Display Screen button to the second Interface Controller. In the popup that appears, select modal. Set the Identifier of the newly created segue to pagebased (see Figure 2.15).

Image

Figure 2.15 Creating a modal segue connecting the two Interface Controllers

3. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Click the Display Screen button and observe that the second Interface Controller appears from the bottom of the screen. Also, observe that the second Interface Controller now has a Cancel button displayed in the top-left corner (see Figure 2.16). Clicking it hides the second Interface Controller.

Image

Figure 2.16 Displaying another Interface Controller modally

Passing Data between Interface Controllers

In the previous sections, you saw how to make your Apple Watch application transit from one Interface Controller to another, using either the hierarchical or page-based navigation method. One commonly performed task is to pass data from one Interface Controller to another. In this section, you do just that.

1. Using the UINavigation project that you used in the previous section, right-click the UINavigation WatchKit Extension and select New File... (see Figure 2.17).

Image

Figure 2.17 Adding a new file to the project

2. Select the WatchKit Class (see Figure 2.18) template and click Next.

Image

Figure 2.18 Selecting the WatchKit Class template

3. Name the Class SecondInterfaceController and make it a subclass of WKInterfaceController (see Figure 2.19). Click Next.

Image

Figure 2.19 Naming the newly added class

4. A file named SecondInterfaceController.swift is now added to the UINavigation WatchKit Extension.

5. Back in the Storyboard Editor, select the second Interface Controller and set its Class (in the Identity Inspector window) to SecondInterfaceController (see Figure 2.20).

Image

Figure 2.20 Setting the class of the second Interface Controller

6. Select the View | Assistant Editor | Show Assistant Editor menu item to show the Assistant Editor. Control-click the Label control and drag it onto the Code Editor (as shown in Figure 2.21).

Image

Figure 2.21 Creating an outlet for the Label control

7. Create an outlet and name it label (see Figure 2.22).

Image

Figure 2.22 Naming the outlet for the Label control

An outlet is now added to the code:

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {

    @IBOutlet var label: WKInterfaceLabel!

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
    }

8. Add the following statements in bold to the InterfaceController.swift file:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    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()
    }

    override func contextForSegueWithIdentifier(segueIdentifier: String) ->
    AnyObject? {
        switch segueIdentifier {
            case "hierarchical":return [
                "segue": "hierarchical",
                "data" : "Passed through hierarchical navigation"
                ]
            case "pagebased":return [
                "segue": "pagebased",
                "data" : "Passed through page-based navigation"
                ]
            default:return [
                "segue": "",
                "data" : ""
                ]
        }
    }
}

The contextForSegueWithIdentifier: method is fired before any of the segues fire (when the user taps on one of the Button controls). Here, you check the identifier of the segue (through the segueIdentifier argument). Specifically, you return a dictionary containing two keys: segue and data. The data you returned here will be passed to the target Interface Controller.

9. Add the following statements in bold to the SecondInterfaceController.swift file:

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {

    @IBOutlet var label: WKInterfaceLabel!
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        if let dict = context as? [String : String] {
            let segue = dict["segue"]
            let data = dict["data"]
            self.label.setText(segue! + " - " + data!)
        }
    }

When the second Interface Controller is loaded, you retrieve the data that is passed into it in the awakeWithContext: method through the context argument. Because the first Interface Controller passes in a dictionary, you can typecast it into a dictionary object and then retrieve the value of the segue and data keys. The value of the data key is then displayed in the Label control.

10. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Click the Next Screen button, and observe the string displayed in the second Interface Controller (see Figure 2.23).

Image

Figure 2.23 Displaying the data passed through the hierarchical navigation

11. Click the < chevron to return to the first Interface Controller and click the Display Screen button. Observe the string displayed in the second Interface Controller (see Figure 2.24).

Image

Figure 2.24 Displaying the data passed through page-based navigation

Customizing the Title of the Chevron or Cancel Button

As you have seen in the previous section, a chevron is displayed when you push an Interface Controller using the hierarchical navigation method. A default Cancel button is displayed when you display an Interface Controller modally. However, the chevron or Cancel button can be customized.

1. Add the following statements in bold to the SecondInterfaceController.swift file:

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {

    @IBOutlet var label: WKInterfaceLabel!
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        if let dict = context as? [String : String] {
            let segue = dict["segue"]
            let data = dict["data"]
            self.label.setText(segue! + " - " + data!)

            if segue == "pagebased" {
                self.setTitle("Close")
            } else {
                self.setTitle("Back")
            }
        }
    }

2. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Click the Next Screen button, and observe the string displayed next to the chevron (see Figure 2.25).

Image

Figure 2.25 Displaying a string next to the chevron

3. Click the <Back chevron to return to the first Interface Controller and click the Display Screen button. Observe that the Cancel button is now displayed as Close (see Figure 2.26).

Image

Figure 2.26 Modifying the button for a modal Interface Controller

Navigating Using Code

Although you can link up Interface Controllers by creating segues in your storyboard, it is not versatile. In a real-life application, the flow of your application may depend on certain conditions being met, and hence, you need to be able to decide during runtime which Interface Controller to navigate to (or display modally).

1. Using Xcode, create a new iOS App with WatchKit App project and name it NavigateUsingCode. Uncheck the option Include Notification Scene so that we can keep the WatchKit project to a bare minimum.

2. Click the Interface.storyboard file located in the NavigateUsingCode WatchKit App group in your project to edit it using the Storyboard Editor.

3. Add two Button controls to the first Interface Controller and then add another Interface Controller to the storyboard. In the second Interface Controller, add a Label control, as shown in Figure 2.27.

Image

Figure 2.27 Populating the two Interface Controllers

4. Select the second Interface Controller and set its Identifier attribute (in the Attributes Inspector window) to secondpage, as shown in Figure 2.28.

Image

Figure 2.28 Setting the Identifier attribute for the second Interface Controller

5. In the first Interface Controller, create two actions (one for each button) and name them as shown below in the InterfaceController.swift file. You should create the actions by control-dragging them from the storyboard onto the Code Editor.

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    @IBAction func btnNextScreen() {
    }

    @IBAction func btnDisplayScreen() {
    }

6. Add the following statements to the two actions in the InterfaceController.swift file:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    @IBAction func btnNextScreen() {
        pushControllerWithName("secondpage", context: nil)
    }

    @IBAction func btnDisplayScreen() {
        presentControllerWithName("secondpage", context: nil)
    }

Observe that the first button uses the pushControllerWithName:context: method to perform a hierarchical navigation. The first argument to this method takes in the identifier of the Interface Controller to navigate to (which we had earlier set in Step 4). The context argument allows you to pass data to the target Interface Controller (discussed in the previous section), which in this case we simply set to nil. For the second button, we use the presentControllerWithName:context: method to perform page-based navigation. Like the pushControllerWithName:context: method, the first argument is the identifier of the Interface Controller to display, whereas the second argument allows you to pass data to the target Interface Controller.

7. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Clicking either button brings you to the second Interface Controller (see Figure 2.29).

Image

Figure 2.29 Navigating the Interface Controllers programmatically


Returning to the Previous Screen

Although you can return to the previous screen by tapping either the chevron or the Cancel button, you can also programmatically return to the previous screen. If you navigate to an Interface Controller using the pushControllerWithName:context: method, you can programmatically return to the Interface Controller using the corresponding popController method. If you display an Interface Controller using the presentControllerWithName:context: method, you can dismiss the current Interface Controller using the corresponding dismissController method.


Presenting a Series of Pages

For page-based applications, you can display more than one single Interface Controller modally—you can display a series of them:

1. Using the same project created in the previous section, add a third Interface Controller to the storyboard and add a Label control to it. Set the label text to Third Page (see Figure 2.30).

Image

Figure 2.30 Adding the third Interface Controller

2. Set the Identifier attribute of the third Interface Controller to thirdpage in the Attributes Inspector window (see Figure 2.31).

Image

Figure 2.31 Setting the Identifier for the third Interface Controller

3. Add the following statements in bold to the InterfaceController.swift file:

    @IBAction func btnDisplayScreen() {
        // presentControllerWithName("secondpage", context: nil)
        presentControllerWithNames(["secondpage", "thirdpage"], contexts: nil)
    }

Instead of using the presentControllerWithName:context: method, we now use the presentControllerWithNames:context: method. The only difference between the two methods is that the latter takes in an array of string in the first argument. This array of string contains the identifiers of Interface Controllers that you want to display.

4. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Click the Display Screen button on the Apple Watch Simulator. This time, you see that the second Interface Controller is displayed with two dots at the bottom of the screen. Swiping from right to left reveals the third Interface Controller (see Figure 2.32).

Image

Figure 2.32 The user can slide between the two Interface Controllers

Changing the Current Page to Display

In the previous section, you saw that you could display a series of Interface Controllers that the user can swipe through. What if you want to programmatically jump to a particular page? In this case, what if you want to display the Third Page instead of the Second Page? Let’s see how this can be done:

1. Add two WatchKit Class files (make them subclasses of WKInterfaceController) to the WatchKit Extension and name them SecondInterfaceController.swift and ThirdInterfaceController.swift, respectively. Figure 2.33 shows the location of the files.

Image

Figure 2.33 Adding the two Swift files to the project

2. Populate the SecondInterfaceController.swift file as follows:

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        print("SecondInterfaceController - awakeWithContext")
    }

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

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

}

3. Populate the ThirdInterfaceController.swift file as follows:

import WatchKit
import Foundation

class ThirdInterfaceController: WKInterfaceController {

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        print("ThirdInterfaceController - awakeWithContext")
    }

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

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

}

4. In the Interface.storyboard file, set the Class property of the second Interface Controller to SecondInterfaceController (see Figure 2.34). Likewise, set the Class property of the third Interface Controller to ThirdInterfaceController.

Image

Figure 2.34 Setting the class for the second Interface Controller

5. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Click the Display Screen button on the Apple Watch Simulator. Observe the statements printed in the Output window (see Figure 2.35). As you can see, the awakeWithContext: method is fired for both the second and third Interface Controllers, even though only the second Interface Controller is visible initially.

Image

Figure 2.35 Both Interface Controllers fire the awakeWithContext: method

6. If you want the third Interface Controller to load instead of the second, you can use the becomeCurrentPage method. Calling this method in an Interface Controller brings it into view. Because both the second and third Interface Controllers fire the awakeWithContext: method when you click the Display Screen button, you can call the becomeCurrentPage method in the awakeWithContext: method. Add the following statement in bold to the ThirdInterfaceController.swift file:

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        becomeCurrentPage()
        print("ThirdInterfaceController - awakeWithContext")
    }

7. Run the application on the Apple Watch Simulator and click the Display Screen button. This time, you see that after the second Interface Controller is displayed, it will automatically scroll to the third one.

Returning Data from an Interface Controller

Earlier in this chapter, you saw that you can easily pass data to another Interface Controller. However, what about the reverse? Very often, the target Interface Controller may need to return data back to the calling Interface Controller. For example, the user may click a button in an Interface Controller and the app will navigate to another Interface Controller that displays a list of items from which to select. Once an item is selected, the app returns it back to the original Interface Controller.

To return data back to the calling Interface Controller, there are two main techniques:

Image Use the Delegation design pattern where the target Interface Controller defines a protocol that the calling Interface Controller must implement. Once the target Interface Controller is closed, it calls the method that is implemented in the calling Interface Controller and passes it the data that it needs to return. However, this method is not really feasible in the WatchKit framework as both methods of navigation (programmatically or using the storyboard) do not provide an instance of the target Interface Controller. Instead, you navigate using the identifier of the segue or Interface Controller.

Image Use the NSNotificationCenter to send notifications to other parts of the app so that they can be notified of changes. In the WatchKit framework, this is the easiest to implement. (Note, however, that this method is harder to maintain as the project grows.) The calling Interface Controller would need to add an observer to a notification that will be fired by the target Interface Controller. The target Interface Controller would then pass the data through the notification.

In the following example, you learn how to return data from an Interface Controller using NSNotificationCenter:

1. Using Xcode, create a new iOS App with WatchKit App project and name it ReturningValues. Uncheck the option Include Notification Scene so that we can keep the WatchKit project to a bare minimum.

2. Click the Interface.storyboard file located in the WatchKit App in your project to edit it using the Storyboard Editor.

3. Add a Button control and a Label control to the first Interface Controller, and then add another Interface Controller to the storyboard. In the second Interface Controller, add a Picker and a Button control, as shown in Figure 2.36.

Image

Figure 2.36 Populating the two Interface Controllers

When the user clicks the Choose Location button, the app displays the second Interface Controller so that the user can select a location from the Picker control. Clicking the Done button will bring the user back to the first Interface Controller, where the location selected will be displayed in the Label control.

4. Set the Identifier attribute of the second Interface Controller to secondpage (see Figure 2.37).

Image

Figure 2.37 Setting the Identifier attribute of the second Interface Controller

5. Add a WatchKit Class file (make it a subclass of WKInterfaceController) to the WatchKit Extension and name it SecondInterfaceController.swift.

6. In the Interface.storyboard file, set the Class property of the second Interface Controller to SecondInterfaceController (see Figure 2.38).

Image

Figure 2.38 Setting the Class property of the second Interface Controller

7. In the first Interface Controller, create an action for the button and an outlet for the Label control and name them as shown below in the InterfaceController.swift file. You should create the actions by control-dragging them from the storyboard onto the Code Editor.

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    @IBOutlet var label: WKInterfaceLabel!

    @IBAction func btnChooseLocation() {
    }

8. Add the following statements in bold to the InterfaceController.swift file:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    var location = ""

    @IBOutlet var label: WKInterfaceLabel!

    @IBAction func btnChooseLocation() {
        presentControllerWithName(
            "secondpage",
            context: ["location":location])
    }

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        NSNotificationCenter.defaultCenter().addObserver(
            self,
            selector: "locationSelected:",
            name: "LOCATION_SELECTED",
            object: nil)
    }

    //---when a NSNotification is received---
    func locationSelected(notification: NSNotification) {
       let data = notification.object as! [String:String]
       location = data["location"]!
    }

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

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

}

When the user clicks the Choose Location button, you display the second Interface Controller and pass it a dictionary containing the value of the location variable.

In the awakeWithContext: method, you added a notification observer, with the notification name LOCATION_SELECTED. This is the notification name that will be sent by the second Interface Controller when the user is done selecting a location. Once this notification name is received, it calls the locationSelected: method. The argument of this method has a dictionary with the location key containing the location set as its value. You then update the Label control with this location in the willActivate method.

9. In the second Interface Controller, create two actions (one for the button and one for the Picker) and an outlet for the Picker and name them as shown below in the SecondInterfaceController.swift file. You should create the actions and outlet by control-dragging them from the storyboard onto the Code Editor.

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {

    @IBOutlet var picker: WKInterfacePicker!

    @IBAction func btnPickerSelected(value: Int) {
    }

    @IBAction func btnDone() {
    }

10. Add the following statements in bold to the SecondInterfaceController.swift file:

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {

    var pickerSelectedIndex = 0
    let locations = [
        "Singapore", "Oslo", "Tokyo", "Denmark", "Copenhagen",
        "Bangkok", "Manila"
    ]

    @IBOutlet var picker: WKInterfacePicker!

    @IBAction func btnPickerSelected(value: Int) {
        //---index of the selected item in the picker---
        pickerSelectedIndex = value
    }

    @IBAction func btnDone() {
        //---post a NSNotification---
        NSNotificationCenter.defaultCenter().postNotificationName(
            "LOCATION_SELECTED",
            object: ["location":locations[pickerSelectedIndex]])

        //---close the current interface controller---
        dismissController()
    }

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        var previousLocation = ""
        if let dict = context as? [String : String] {
            previousLocation = dict["location"]!
        }

        //---find the index of the location that should appear
        // selected in the picker--
        if let index = locations.indexOf(previousLocation) {
            pickerSelectedIndex = index
        }

        //---array of WKPickerItem objects---
        var items = [WKPickerItem]()

        for location in locations {
            let item = WKPickerItem()
            item.title = location
            items.append(item)
        }

        //---display all the locations in the picker---
        picker.setItems(items)
    }

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

        //---display the location previously selected---
        picker.setSelectedItemIndex(pickerSelectedIndex)
    }

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

}

In the awakeWithContext: method, you first obtain the location that is passed in. This location represents the location that was previously selected. Its index is in the locations array. Knowing this index allows you to later move the Picker to display the previously selected location.

You then populate the Picker control with an array of WKPickerItem objects representing a list of locations. In the willActivate method, you make the Picker control display the location that was previously selected. When the user clicks the Done button, you post a notification with the name LOCATION_SELECTED, together with the name of the location selected.


Note

The Picker control is new in watchOS 2 and will be covered in more detail in Chapter 4, “Displaying and Gathering Information.”


11. Select the WatchKit app scheme and run the application on the Apple Watch Simulator. Figure 2.39 shows that when a location is selected in the second Interface Controller, the location name is passed back to the first Interface Controller.

Image

Figure 2.39 The location selected in the Picker control is passed back to the calling Interface Controller

Summary

In this chapter, you delved deeper into how Interface Controllers work in your Apple Watch application. You learned

Image The lifecycle of an Interface Controller

Image How to navigate between Interface Controllers

Image The different methods of displaying an Interface Controller

Image How to programmatically display an Interface Controller

Image How to display a series of Interface Controllers

Image How to pass data between Interface Controllers

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.180.113