- Interface Controllers and Storyboard
- Navigating between Interface Controllers
- Summary
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.
- Using Xcode, create a Single View Application project and name it UINavigation.
- 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.
- In the UINavigation WatchKit App group, select the Interface.storyboard file to edit it in the Storyboard Editor.
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 Adding another Interface Controller to the storyboard
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 Adding a Button control to the first Interface Controller
Control-click the Next Screen button and drag and drop it over the second Interface Controller (see Figure 2.8).
Figure 2.8 Control-click the Button control and drag and drop it over the second Interface Controller
You will see a popup called Action Segue. Select push (see Figure 2.9).
Figure 2.9 Creating a push segue
A segue will now be created (see Figure 2.10), linking the first Interface Controller to the second.
Figure 2.10 The segue that is created after performing the action
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 Naming the Identifier for the segue
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 Adding a Label control to the second Interface Controller
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 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.
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 Adding another Button control to the first Interface Controller
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 Creating a modal segue connecting the two Interface Controllers
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 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.
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 Adding a new file to the project
Select the Cocoa Touch Class (see Figure 2.18) template and click Next.
Figure 2.18 Selecting the Cocoa Touch Class template
Name the Class SecondInterfaceController and make it a subclass of WKInterfaceController (see Figure 2.19). Click Next.
Figure 2.19 Naming the newly added class
- A file named SecondInterfaceController.swift will now be added to the UINavigation WatchKit Extension group of your project.
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 Setting the class of the second Interface Controller
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 Creating an outlet for the Label control
Create an outlet and name it label (see Figure 2.22).
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 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() } }
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.
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.
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 Displaying the data passed through the hierarchical navigation
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 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.
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") } } }
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 Displaying a string next to the chevron
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 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).
- Using Xcode, create a new Single View Application project and name it NavigateUsingCode.
- 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.
- Click the Interface.storyboard file located in the NavigateUsingCode WatchKit App group in your project to edit it using the Storyboard Editor.
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 Populating the two Interface Controllers
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 Setting the Identifier for the second Interface Controller
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() { }
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.
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 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.
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 Adding the third Interface Controller
Set the Identifier attribute of the third Interface Controller to thirdpage in the Attributes Inspector window (see Figure 2.31).
Figure 2.31 Setting the Identifier for the third Interface Controller
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.
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 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.
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 Adding the two Swift files to the project
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") } }
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") } }
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 Setting the class for the second Interface Controller
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 Both Interface Controllers fire the awakeWithContext method
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") }
- 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.