In the first part in this series we implemented a modal interface in WatchKit with a segue. In this part we’ll present the modal programmatically and once again set up a delegate and context for moving data between controllers.
Open the project from the last lesson. Press Command-N and add a new Cocoa Touch class named NumberInterfaceController. Subclass WKInterfaceController
, and make sure you set it to the Watchkit Extension Group
.
Go to the Watchkit App storyboard. Drag a new Interface Controller from the object library to the storyboard. Select the interface. In the identity inspector set the class to NumberInterfaceController. In the attributes inspector, set the Identifier and Name to Numbers.
Drag a label, slider and button to the interface. Select the Label. Position the label Vertical: Top and Horizontal: Left. Set the width of the label to Relative to Container. Change the font to System 32 point centered.
Select the slider. Position the Slider Horizontal: left, and Vertical: Center. Set the minimum to 0, maximum to 10 and steps to 20.
Select the button. Label the Button Done. Change the button background color to green (#00ff00 alpha 0.73). Position the button Horizontal: Left and Vertical: Bottom. Your finished interface should look like this:
Open the assistant editor. Select the Number button on the I am Root interface. Control-drag from the button to the code. Add an action named numberButtonPressed and a //MARK:
comment above it
//MARK: - Actions @IBAction func numberButtonPressed() { }
Select the Number interface. Control-drag from the label to the code. Make a property statusLabel. Control-drag from the slider to the code. Make an action sliderDidChange. Control-drag from the Done Button to the code. Make an action doneButtonPressed. Add the appropriate //MARK:
tag. You should now have the following in your code:
//MARK: Outlets and Properties @IBOutlet weak var statusLabel: WKInterfaceLabel! //MARK: - Actions @IBAction func sliderDidChange(value: Float) { } @IBAction func doneButtonPressed() { }
We are now ready to code.
Presenting the Modal Controller
Presenting modal controllers in WatchKit is pretty similar to presenting controllers in UIKit, with two exceptions. First, we do not have any choices about animation in WatchKit. Secondly we pass as a parameter our context, as we have with the hierarchical controllers.
Close the assistant editor. Go to the InterfaceController.swift code. Change the code to this:
@IBAction func numberButtonPressed() { presentControllerWithName("Number", context: nil) }
The method presentControllerWithName
takes our destination controller’s storyboard ID and a context for parameters. For the moment we set the context to nil
.
Build and run. You should be able to present the Number interface when you press the number button.
Adding the Context
As in the first part of the modal interface lesson, we’ll use the class context we made to transfer data to the modal controller. Change the numberButtonPressed
code to this:
@IBAction func numberButtonPressed() { myModel.myNumber = 3.14 //test data //prepare the context let context: AnyObject? = myModel //present the controller presentControllerWithName("Number", context: context) }
We added the context as AnyObject?
, then present it to the controller. We also included some test data of 3.14
.
Go to the NumberInterfaceController
. Above the outlet, Add the following code to add the number property.
var number:Float = 0.0
Add the following code to awakeWithContext
:
//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 //grab the data from the model. updateDisplay() }
When we awake the controller, we pass it our model. The method first casts the AnyObject?
context back to a usable model, then we grab the number in the model to make it the number in the display. Once we have number
, we update the display.
We’ll need an updateDisplay
method. Add the following below the action methods:
//MARK: - Instance Methods func updateDisplay(){ let displayString = String(format:"%0.1f",number) statusLabel.setText(displayString) }
Build and run. When we press the Number button, we now get 3.1 in the display.
Add the Delegate
It’s nice to be able to send data to a view, but even more important to send it back with a delegate. Start making your delegate by adding the following above the NumberInterfaceController
class declaration:
protocol NumberInterfaceDelegate{ func numberDidFinish(context:Float) }
Unlike the delegate we did last lesson, we made this one simpler. Instead of passing the model back to InterfaceController
, we’ll pass just the number. For the color, we passed both a String
and a UIColor
to describe a color, we needed a class. This time, the only important data is a single Float
, which simplifies everything.
Add a delegate declaration to the NumberInterfaceController
under the declaration for number
.
var delegate:NumberInterfaceDelegate! = nil
Change doneButtonPressed
to this to call the delegate method
@IBAction func doneButtonPressed() { delegate.numberDidFinish(number) }
Go to the InterfaceController.swift file. In the class declaration, adopt the protocol.
class InterfaceController: WKInterfaceController,ColorModalDelegate, NumberInterfaceDelegate {
Xcode will give you an error that the delegate isn’t implemented. Add the following method under the colorDidFinish
method
func numberDidFinish(context:Float){ myModel.myNumber = context updateDisplay() dismissController() }
Here’s where sending back a simple float pays off. With a simple type, we do not have to cast when we assign it back to myModel.mynumber
. We update the display and dismiss the modal controller.
We’ll need to assign the delegate to myModel
to pass it to the destination controller. Add myModel.delegate = self
to numberButtonPressed
. While there, comment out the test data line.
@IBAction func numberButtonPressed() { //let myModel.number = 3.14 //test data myModel.delegate = self //prepare the context let context: AnyObject? = myModel //present the controller presentControllerWithName("Number", context: context) }
We also have to assign the delegate in NumberInterfaceController
to the delegate property there. Go the awakeWithContext
method in NumberInterfaceController
and add delegate = myModel.delegate as? NumberInterfaceDelegate
to make it look like this:
//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 //transfer the number delegate = myModel.delegate as? NumberInterfaceDelegate //transfer the delegate updateDisplay() }
Build and run. You will be able to change the slider, press Done and the number will appear in the root display.
We’ve posted a single modal interface to the watch. There is one more type of modal interface: A page based one. In the conclusion to our lessons in modal interfaces, we’ll work with the page based interface.
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.delegate = self //prepare the context let context: AnyObject? = myModel //present the controller presentControllerWithName("Number", context: context) } //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() } }
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() } }
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" } } }
ColorsModalController.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() } }
Leave a Reply