Apple’s documentation for WatchKit is quite clear, even when it is lying. The documentation states you can have hierarchical navigation or page navigation but not both. Here’s is where it lies: you can have a page-based navigation as part of a hierarchical navigation scheme. There is a special modal view version of page views, which you can use in a hierarchical scheme. In the conclusion of this lesson on modal views, we’ll look at using the modal page view.
In the last part, we learned we can call a modal view programmatically using the method presentControllerWithName(_name:, context:)
. For example, we had this code for presenting the number controller modally:
@IBAction func numberButtonPressed() { //myModel.myNumber = 3.14 myModel.delegate = self //prepare the context let context: AnyObject? = myModel //present the controller presentControllerWithName("Number", context: context) }
For a pages control, we just make everything in the modal control plural. The method is presentControllerWithNames(_names:, contexts:)
. For a modal page controller, use arrays instead of a single values for the parameters. In the code from the previous lessons add this under the numberButtonPressed
action.
@IBAction func pagesButtonPressed() { let pageNames = ["RunPage","WalkPage","PizzaPage"] let pageContexts:[AnyObject]? = [myModel.myNumber,myModel.myColorString,myModel] presentControllerWithNames(pageNames, contexts: pageContexts) }
Line 1 has an array of interface identifiers. The system will present the pages in this order, starting with the Run page, going to the Walk page and finishing with the Pizza Page. Line 3 gives different contexts for the corresponding page. Our Run page for example will take the Float
value myNumber
from the model. Similarly, the Walk page will take a String.
Let’s design a storyboard then write some code to see how this would work.
Adding the Pages Button
Go to the watch app storyboard. On the I am Root interface, add another button, which will snap itself to the bottom. Label it Pages.
Open the assistant editor. Next to the action pageButtonPressed
is the open circle. Drag from the circle to the Pages button to connect the button.
Adding the Three Page Controllers.
Press Command-N and make a new controller subclassing WKInterfaceController
named RunPageInterfaceController. Be sure to group it in the WatchKit extension. Do the same to make two more interface controllers named WalkPageInterfaceController and PizzaPageInterfaceController.
Go back to the storyboard. Add a new interface to the storyboard. Add a group on the interface.
Select the group. In the attribute inspector, set the Height to Relative to Container. Change the color of the group to Light Gray. Change the Layout to Vertical.
Add two labels to the group
Select the bottom label. Set the Position to Horizontal: Center, Vertical:Center. Change the font to 50 point System Centered.
Select the controller icon on this interface. Press Command-C to copy the interface. Click on the white background of the storyboard to deselect everything. Press Command-V to paste a copy of the interface. The pasted interface is on top of the current interface. Drag the interface and you will now have two.
Deselect everything again and press Commmand-V. Drag again and you will have three controllers
In the identity inspector, Make the ID of one Controller RunPage and its name Run. Make the ID of the second controller WalkPage, and the name Walk. Make the ID of the third PizzaPage and the name Pizza. Using emoji you can find at Control-Command-Space, set up the three interfaces to look like this:
Go to the identity inspector. Give RunPage a class of RunPageInterfaceController,
WalkPage a class of WalkPageInterfaceController,
and PizzaPage a class of PizzaPageInterfaceController.
Build and run. Press the Pages button and the Run interface shows. Swipe to the left and you get the walk page, another swipe give you the Pizza Page. Tap on the word Pizza on the dark background. The modal dismisses.
Adding Contexts
While you can add delegates to each page. You don’t have to. Modal pages very likely won’t interact, but just show information, like glances. This time, we’ll transmit information through the context and use it differently in each page.
Wire up the Page controllers. With the assistant editor open, select the RunPage interface. Control-Drag the smaller label to the code, just under the class declaration. Add an outlet statusLabel . After the outlet, add a property:
var number:Float = 0.0
In the code, add the following to awakeWithContext
:
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //Update the status label with the context. number = context as! Float }
The RunPage is the first element of the context array. We set the context there to the Float
value of myModel.myNumber
. We only have to unwrap the Float
value to use it. We’ll display our result in willActivate
. Change that method to this:
override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() let displayString = String(format:"Run %0.2f minutes",number) statusLabel.setText(displayString) }
The code changes the number to a String
, then displays the string on the label.
We’ve finished our first controller. With the assistant editor open, click on the WalkPage in the storyboard. The WalkPageController
should be in the assistant editor. In this controller, we take a String
with a color for our context. We’ll instantiate a new instance of the model, then add that string to the model to compute a UIColor
for the string. We’ll use that model to change the color of the group’s background and to tell us what color we have.
We need two outlets for this. Control-drag from the group to the code in the assistant editor. Add an outlet named backgroundGroup. Control-drag from the label to the code. Add an outlet named statusLabel. Finally add an instance of DemoModel
named myModel. You should have just added this code:
@IBOutlet weak var backgroundGroup: WKInterfaceGroup! @IBOutlet weak var statusLabel: WKInterfaceLabel! let myModel = DemoModel()
We’ll cast the context’s AnyObject?
to type String
, then add to the model with the colorFromString
method on the model we used in the first lesson on modals. Change awakeWithContext
to the following code:
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //Context is a String, but we need a string and color //so we use a new model. let colorString = context as! String myModel.colorFromString(colorString) }
Once again, set up the display in the willActivate
code:
override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() let displayString = myModel.myColorString + " Walking" statusLabel.setText(displayString) backgroundGroup.setBackgroundColor(myModel.myColor) }
Click on the pizza interface to select it and bring up the PizzaPageInterfaceController
code in the assistant editor. This time we pass DemoModel
as the context. Wire up the controller by first adding the following outlets and actions to the code:
var myModel = DemoModel() @IBOutlet weak var backgroundGroup: WKInterfaceGroup! @IBOutlet weak var statusLabel: WKInterfaceLabel!
Drag from the empty circle to the left of the backgroundGroup
to a spot on the Pizza interface. Release the button. Do the same between statuslabel
and the label.
We have only one line to add to awakeToContext.
We’ll need to cast the context to a DemoModel
object.
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. myModel = context as! DemoModel }
Since we are pizza themed in this page, let’s have a different pizza based on color. Change willActivate
to the following:
override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() var pizzaType = "Cheese" switch myModel.myColorString{ case "Red": pizzaType = "Pepperoni" case "Green": pizzaType = "Veggie" case "Blue": pizzaType = "Gorgonzola" default: pizzaType = "Cheese" } let displayString = pizzaType + " Pizza!!" statusLabel.setText(displayString) backgroundGroup.setBackgroundColor(myModel.myColor) }
We created a variable pizzaType
. Using a switch
statement, we mapped the colors to appropriate types of pizzas (Red meat, Green vegetables and Blue cheese), assign the pizza type in pizzaType
. Instead of displaying the color, we display the pizza.
Build and run. Assuming the simulator behaves itself. you should have your three buttons.
Set the number to 3.5 by pressing the number button and using the slider, then pressing Done.
Set the color to Blue in the Color modal.
When done your watch looks like this:
Tap Pages. The Run Page appears:
You’ll notice our number is in the label. Swipe left to get the walk page and our selected color: .
Swipe left again to get a pizza based on the blue color:
Our modal controllers work perfectly. Modals are meant for interrupted use of a more active view. Page modals are best used for viewing data in a glance fashion. Actually glances are a type of modal controller. Modals are the last of the basic types of controllers in Watchkit. In future lessons we’ll use these with a few very handy complex controllers. In the next lesson, we’ll look at one of these: Text input when you don’t have room for a keyboard.
The Whole Code
Storyboard
InterfaceController.swift
// // InterfaceController.swift // SwiftWatchModalDemo WatchKit Extension // // Created by Steven Lipton on 6/3/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation class InterfaceController: WKInterfaceController,ColorModalDelegate, NumberInterfaceDelegate { //MARK: Outlets and Properties @IBOutlet weak var statusLabel: WKInterfaceLabel! @IBOutlet weak var colorButton: WKInterfaceButton! var myModel = DemoModel() //MARK: - Actions @IBAction func numberButtonPressed() { //myModel.myNumber = 3.14 myModel.delegate = self //prepare the context let context: AnyObject? = myModel //present the controller presentControllerWithName("Number", context: context) } @IBAction func pagesButtonPressed() { let pageNames = ["RunPage","WalkPage","PizzaPage"] let pageContexts:[AnyObject]? = [myModel.myNumber,myModel.myColorString,myModel] presentControllerWithNames(pageNames, contexts: pageContexts) } //MARK: - Delegates func colorDidFinish(context: AnyObject?) { myModel = context as! DemoModel dismissController() updateDisplay() } func numberDidFinish(context:Float){ myModel.myNumber = context updateDisplay() dismissController() } //MARK: - Instance Methods func updateDisplay(){ let statusText = String(format: "%1.2f", myModel.myNumber) statusLabel.setText(statusText) colorButton.setBackgroundColor(myModel.myColor) } //MARK: - Life Cycle override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? { if segueIdentifier == "ColorsModal" { myModel.delegate = self return myModel } 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() updateDisplay() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
DemoModel.swift
// // DemoModel.swift // SwiftWatchModalDemo // // Created by Steven Lipton on 6/3/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import UIKit class DemoModel:NSObject{ var myNumber:Float = 0.0 var myColorString = "Black" var myColor = UIColor(white: 0.2, alpha: 0.73) var delegate:AnyObject? = nil func colorFromString(colorString:String){ myColorString = colorString switch colorString { case "Red": myColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.73) case "Green":myColor = UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 0.73) case "Blue":myColor = UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.73) default: myColor = UIColor(white: 0.2, alpha: 0.73) myColorString = "Black" } } }
WalkPageInterfaceController.swift
// // WalkPageInterfaceController.swift // SwiftWatchModalDemo // // Created by Steven Lipton on 6/11/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation class WalkPageInterfaceController: WKInterfaceController { @IBOutlet weak var backgroundGroup: WKInterfaceGroup! @IBOutlet weak var statusLabel: WKInterfaceLabel! let myModel = DemoModel() override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //Context is a String, but we need a string and color //so we use a new model. let colorString = context as! String myModel.colorFromString(colorString) } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() let displayString = myModel.myColorString + " Walking" statusLabel.setText(displayString) backgroundGroup.setBackgroundColor(myModel.myColor) } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
RunPageInterfaceController.swift
// // RunPageInterfaceController.swift // SwiftWatchModalDemo // // Created by Steven Lipton on 6/11/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation class RunPageInterfaceController: WKInterfaceController { var number:Float = 0.0 @IBOutlet weak var statusLabel: WKInterfaceLabel! override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //Update the status Label with the context. number = context as! Float } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() let displayString = String(format:"Run %0.2f minutes",number) statusLabel.setText(displayString) } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
PizzaPageInterfaceController.swift
// // PizzaPageInterfaceController.swift // SwiftWatchModalDemo // // Created by Steven Lipton on 6/11/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation class PizzaPageInterfaceController: WKInterfaceController { var myModel = DemoModel() @IBOutlet weak var backgroundGroup: WKInterfaceGroup! @IBOutlet weak var statusLabel: WKInterfaceLabel! override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. myModel = context as! DemoModel } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() var pizzaType = "Cheese" switch myModel.myColorString{ case "Red": pizzaType = "Pepperoni" case "Green": pizzaType = "Veggie" case "Blue": pizzaType = "Gorgonzola" default: pizzaType = "Cheese" } let displayString = pizzaType + " Pizza!!" statusLabel.setText(displayString) backgroundGroup.setBackgroundColor(myModel.myColor) } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
ColorsModalInterfaceController.swift
// // ColorsModalInterfaceController.swift // SwiftWatchModalDemo // // Created by Steven Lipton on 6/3/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation protocol ColorModalDelegate{ func colorDidFinish(context:AnyObject?) } class ColorsModalInterfaceController: WKInterfaceController { //MARK: Properties var myModel = DemoModel() var delegate:ColorModalDelegate! = nil //MARK: - Action Methods @IBAction func redButtonPressed() { updateModel("Red") } @IBAction func greenButtonPressed() { updateModel("Green") } @IBAction func blueButtonPressed() { updateModel("Blue") } //MARK: - Instance Methods func updateModel(color:String){ myModel.colorFromString(color) let newContext:AnyObject? = myModel delegate.colorDidFinish(newContext) } //MARK: - Life Cycle override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. myModel = context as! DemoModel //make the model delegate = myModel.delegate as? ColorModalDelegate //unbundle the delegate } 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() } }
NumberInterfaceController.swift
// // NumberInterfaceController.swift // SwiftWatchModalDemo // // Created by Steven Lipton on 6/5/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation protocol NumberInterfaceDelegate{ func numberDidFinish(context:Float) } class NumberInterfaceController: WKInterfaceController { //MARK: Outlets and properties var number:Float = 0.0 var delegate:NumberInterfaceDelegate! = nil @IBOutlet weak var statusLabel: WKInterfaceLabel! //MARK: - Actions @IBAction func sliderDidChange(value: Float) { number = value updateDisplay() } @IBAction func doneButtonPressed() { delegate.numberDidFinish(number) } //MARK: - Instance Methods func updateDisplay(){ let displayString = String(format:"%0.1f",number) statusLabel.setText(displayString) } //MARK: - Life Cycle override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. // unwrap the context let myModel = context as! DemoModel //make the model number = myModel.myNumber delegate = myModel.delegate as? NumberInterfaceDelegate updateDisplay() } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() updateDisplay() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
Leave a Reply