Home > Articles > Operating Systems, Server > MAC OS X/Other

📄 Contents

  1. Interface Controllers and Storyboard
  2. Navigating between Interface Controllers
  3. Summary
  • Print
  • + Share This
This chapter is from the book

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 where you need to spread your UI across multiple Interface Controllers. In Apple Watch, there are two ways to navigate between Interface Controllers:

  • 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.
  • 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 Controller. You can also use this model to display a series of Interface Controllers, which the user can select by swiping the screen.

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 a Single View Application project and name it UINavigation.
  2. Add a WatchKit App target to the project. Uncheck the option Include Notification Scene so that we can keep the WatchKit project to a bare minimum.
  3. In the UINavigation WatchKit App group, select the Interface.storyboard file to edit it in the Storyboard Editor.
  4. Drag and drop another Interface Controller object onto the editor, as shown in Figure 2.6. You should now have two Interface Controllers.

    Figure 2.6

    Figure 2.6 Adding another Interface Controller to the storyboard

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

    Figure 2.7

    Figure 2.7 Adding a Button control to the first Interface Controller

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

    Figure 2.8

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

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

    Figure 2.9

    Figure 2.9 Creating a push segue

  8. A segue will now be created (see Figure 2.10), linking the first Interface Controller to the second.

    Figure 2.10

    Figure 2.10 The segue that is created after performing the action

  9. 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.

    Figure 2.11

    Figure 2.11 Naming the Identifier for the segue

  10. 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).

    Figure 2.12

    Figure 2.12 Adding a Label control to the second Interface Controller

  11. You are now ready to test the application. Run the application on the iPhone 6 Simulator and, in 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.

    Figure 2.13

    Figure 2.13 Navigating to another Interface Controller using hierarchical navigation

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.

    Figure 2.14

    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 Action Segue popup that appears, select modal. Set the Identifier of the newly created segue to pagebased (see Figure 2.15).

    Figure 2.15

    Figure 2.15 Creating a modal segue connecting the two Interface Controllers

  3. Run the application on the iPhone 6 Simulator and, in 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.

    Figure 2.16

    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 group and select New File... (see Figure 2.17).

    Figure 2.17

    Figure 2.17 Adding a new file to the project

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

    Figure 2.18

    Figure 2.18 Selecting the Cocoa Touch Class template

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

    Figure 2.19

    Figure 2.19 Naming the newly added class

  4. A file named SecondInterfaceController.swift will now be added to the UINavigation WatchKit Extension group of your project.
  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).

    Figure 2.20

    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 and drop it onto the Code Editor (as shown in Figure 2.21).

    Figure 2.21

    Figure 2.21 Creating an outlet for the Label control

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

    Figure 2.22

    Figure 2.22 Naming the outlet for the Label control

  8. An outlet is now added to the code:

    
    import WatchKit
    import Foundation
    
    class SecondInterfaceController: WKInterfaceController {
    
        @IBOutlet weak var label: WKInterfaceLabel!
    
        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()
        }
    }
  9. 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? {
                if segueIdentifier == "hierarchical" {
                    return ["segue": "hierarchical",
                        "data":"Passed through hierarchical navigation"]
                } else if segueIdentifier == "pagebased" {
                    return ["segue": "pagebased",
                        "data": "Passed through page-based navigation"]
                } else {
                    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.

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

    import WatchKit
    import Foundation
    
    class SecondInterfaceController: WKInterfaceController {
    
        @IBOutlet weak var label: WKInterfaceLabel!
    
        override func awakeWithContext(context: AnyObject?) {
            super.awakeWithContext(context)
    
            // Configure interface objects here.
            var dict = context as? NSDictionary
            if dict != nil {
                var segue = dict!["segue"] as! String
                var data = dict!["data"] as! String
                self.label.setText(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. Since the first Interface Controller passes in a dictionary, you can typecast it into an NSDictionary 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.

  11. Run the application on the iPhone 6 Simulator and in the Apple Watch Simulator, click the Next Screen button, and observe the string displayed in the second Interface Controller (see Figure 2.23).

    Figure 2.23

    Figure 2.23 Displaying the data passed through the hierarchical navigation

  12. 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).

    Figure 2.24

    Figure 2.24 Displaying the data passed through the 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 weak var label: WKInterfaceLabel!
    
        override func awakeWithContext(context: AnyObject?) {
            super.awakeWithContext(context)
    
            // Configure interface objects here.
            var dict = context as? NSDictionary
            if dict != nil {
                var segue = dict!["segue"] as! String
                var data = dict!["data"] as! String
                self.label.setText(data)
                if segue == "pagebased" {
                    self.setTitle("Close")
                } else {
                    self.setTitle("Back")
                }
            }
        }
  2. Run the application on the iPhone 6 Simulator and in the Apple Watch Simulator, click the Next Screen button, and observe the string displayed next to the chevron (see Figure 2.25).

    Figure 2.25

    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).

    Figure 2.26

    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 Single View Application project and name it NavigateUsingCode.
  2. Add a WatchKit App target to the project. Uncheck the option Include Notification Scene so that we can keep the WatchKit project to a bare minimum.
  3. Click the Interface.storyboard file located in the NavigateUsingCode WatchKit App group in your project to edit it using the Storyboard Editor.
  4. 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.

    Figure 2.27

    Figure 2.27 Populating the two Interface Controllers

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

    Figure 2.28

    Figure 2.28 Setting the Identifier for the second Interface Controller

  6. In the first Interface Controller, create two actions (one for each button) and name them as shown here 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() {
        }
    
  7. 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 5). The context argument allows you to pass data to the target Interface Controller, which in this case we simply set to nil. For the second button, we use the presentControllerWithName:context: method to perform a 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.

  8. Run the application on the iPhone 6 Simulator. Clicking either button brings you to the second Interface Controller (see Figure 2.29).

    Figure 2.29

    Figure 2.29 Navigating the Interface Controllers programmatically

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).

    Figure 2.30

    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).

    Figure 2.31

    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. Run the application on the iPhone 6 Simulator and 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).

    Figure 2.32

    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 WKInterfaceController classes to the NavigateUsingCode WatchKit Extension group of the project and name them SecondInterfaceController.swift and ThirdInterfaceController.swift, respectively. Figure 2.33 shows the location of the files.

    Figure 2.33

    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.
            println("SecondInterfaceController - awakeWithContext")
        }
    
        override func willActivate() {
            // This method is called when watch view controller is about to be
            // visible to user
            super.willActivate()
            println("SecondInterfaceController - willActivate")
        }
    
        override func didDeactivate() {
            // This method is called when watch view controller is no longer
            // visible
            super.didDeactivate()
            println("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.
            println("ThirdInterfaceController - awakeWithContext")
        }
    
        override func willActivate() {
            // This method is called when watch view controller is about to be
            // visible to user
            super.willActivate()
            println("ThirdInterfaceController - willActivate")
        }
    
        override func didDeactivate() {
            // This method is called when watch view controller is no longer
            // visible
            super.didDeactivate()
            println("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.

    Figure 2.34

    Figure 2.34 Setting the class for the second Interface Controller

  5. Run the application on the iPhone 6 Simulator and 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.

    Figure 2.35

    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. Hence, add the following statement in bold to the ThirdInterfaceController.swift file:

    
        override func awakeWithContext(context: AnyObject?) {
            super.awakeWithContext(context)
    
            // Configure interface objects here.
            becomeCurrentPage()
            println("ThirdInterfaceController - awakeWithContext")
        }
  7. Run the application on the iPhone 6 Simulator and click the Display Screen button on the Apple Watch simulator. This time, you see that after the second Interface Controller is displayed, it will automatically scroll to the third one.
  • + Share This
  • 🔖 Save To Your Account