Modal views on iPhones and ipads are used for input of information that requires attention. One of the on the Apple Watch is a modal view. You cannot mix a page-based interface with a hierarchical (i.e. navigation) interface as we learned in previous lessons. You can use modal interfaces with either. In this series of lessons we’ll look at using the modal view.
Start by creating a new single-view project in XCode called SwiftWatchModalDemo. Use Swift as the language. Once loaded select Editor>Add Target… from the drop down menu. Add a Swift WatchKit Extension without a notification or glance.
Make a Model for the Demo
We’ll use a model for our demo that has a number and some color information in it, plus carry our delegate through the context. For strict MVC, the delegate does not belong in a model. Note this model is really a context model for passing between controllers. If I had a real model class, I would have a class for passing controllers contain an instance of my data model and the delegate or I’d use a dictionary or struct for the context. This delegate cannot do anything in the model in its current state, so this is not as much of a break from MVC as it appears.
Press Command-N to make a new Cocoa Touch Class. Make a class DemoModel, subclassing NSObject. Make sure to set group to WatchKit Extension. Add the following code:
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: 0.0, green: 0.0, blue: 1.0, alpha: 0.73) default: myColor = UIColor(white: 0.2, alpha: 0.73) myColorString = "Black" } } }
We have a Float
, a String
for a color name, and a UIColor
as data. We also have delegate
as AnyObject?
As we discussed with delegates for the navigation interface, we’ll downcast the delegate in the awakeFromContext
method of the destination controller.
I added a method to convert a string to a color with an alpha value of 0.73, which is the default alpha of a button in WatchKit. If the color does not match my list, I make it black.
WatchKit Modal Interfaces by Segue
Set up the Storyboard
Once we have our model, set up our view. Go to the Interface storyboard of the WatchKit app. Add a label and two buttons in that order. Leave them in their default positioning. Select the interface. In the attributes inspector, set the Identifier to IAmRoot and the Name to I am Root. Set the Width of the Label to Relative to Container and center justify the label. Set the title of one button to Color and the other button to Number like this.
Add another interface to the storyboard by dragging from the object library. Add three buttons. Title the interface Colors and set the Interface’s ID to ColorsModal. Set the title of the top button to Red and set the back of this button to red(#FF0000 alpha 0.73). Do the same for the two other buttons, labeling and coloring them green(#00FF00 alpha 0.73) and blue(#0000FF alpha: 0.73).
Control-Drag from the Color Button to the Colors interface.
Release the mouse button. You will get a choice of segues. Select modal.
Select the segue that appears and label it ColorsModal
Build and run. You will be able to press the Colors button and the modal will slide up to show the colors modal. You can tell a modal interface because they slide vertically. To dismiss, tap the colors button.
Note that modals do not have the time in the navigation bar like the controller in Root does.
Wire Up the Interface
Let’s make this do something useful. We will change the color of the color button in the root interface. Press Command-N to make a new Cocoa Touch Class. Make a class ColorsModalInterfaceController. Subclass WKInterfaceController
and make sure it is in the WatchKit Extension group before pressing Create in the file window. Once it loads, go back to the storyboard. select the Colors Interface and in the indentity inspector set the class to ColorsModalInterfaceController.
Open the assistant editor. Select the I am Root interface. Control-drag from the label to the code. Make an outlet named statusLabel. Control-drag from the Color Button to the code. Make an outlet named colorButton. Under the outlets, add var myModel = DemoModel()
to make an instance of our model. Your code should now have this:
//MARK: Outlets and Properties @IBOutlet weak var colorButton: WKInterfaceButton! @IBOutlet weak var statusLabel: WKInterfaceLabel! var myModel = DemoModel()
Select the Colors interface. Control-drag from the Red button and make an action redButtonPressed. Do the same for the Green and Blue buttons. You should have this in your code when done.
//MARK: - Action Methods @IBAction func redButtonPressed() { } @IBAction func greenButtonPressed() { } @IBAction func blueButtonPressed() { }
Set up the Colors Interface for a Delegate
For this modal, we will select a color, then change the background color of the button in the root interface using a delegate. In the code we just added ,change it to this:
//MARK: - Action Methods @IBAction func RedButtonPressed() { updateModel("Red") } @IBAction func GreenButtonPressed() { updateModel("Green") } @IBAction func BlueButtonPressed() { updateModel("Blue") }
We added a method updateModel
. In UIKit, I would have taken the titleLabel
property and would have written one action method for all three buttons. In WatchKit, we have no access to the properties. Instead, make a method that does the same, and change a parameter which has that title explicitly in it. Now add the updateModel
method:
//MARK: - Instance Methods func updateModel(color:String){ myModel.colorFromString(color) let newContext:AnyObject? = myModel delegate.colorDidFinish(newContext) }
The model takes the string and updates the model with the string and color information. We then create a new context using the updated model, and then use newContext
as the parameter of our delegate.
As we can tell from the errors, we need to set up the model, delegate and the protocol. Add this to your code, above the actions:
//MARK: Properties var myModel = DemoModel() var delegate:ColorModalDelegate! = nil
To make the delegate work, we need a protocol. Add the protocol between the ColorModalInterfaceController
class definition and below the import Foundation
lines:
protocol ColorModalDelegate{ func colorDidFinish(context:AnyObject?) }
We need to pass the values from the InterfaceController
to this controller. Change the awakeWithContext
method to this:
//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 }
There are two steps here. First we take the context and assign it to a model, downcasting AnyObject?
to DemoModel.
Then we take myModel
and assign the delegate
property to the class’ delegate, downcasting AnyObject?
to the protocol ColorModalDelegate.
Prepare the Segue
We will need to send data to the segue in the root.
Modal and Navigation controllers in WatchKit act alike in many ways. To pass data through a segue we override the same method contextForSegueWithIdentifier(segueIdentifier: String)
which returns the context for the destination controller, much like prepareForSegue
does in UIKit. Go to the InterfaceController
class. Add the following code.
//MARK: - Life Cycle override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? { if segueIdentifier == "ColorsModal" { myModel.delegate = self return myModel } return nil }
If the user presses the Colors button and activates the ColorsModal segue, we assign values to the context. In this case we prepare the delegate with myModel.delegate = self
.
Adopt the Protocol
Adopt the protocol by changing the class definition of InterfaceController
to this:
class InterfaceController: WKInterfaceController,ColorModalDelegate {
Now add the required method for the protocol:
//MARK: - Delegates func colorDidFinish(context: AnyObject?) { myModel = context as! DemoModel dismissController() updateDisplay() }
We change the context to the model for this controller, then dismiss the controller using the method dismissController.
Unlike its UIKit counterpart, you have no control over animation, so this method is a lot simpler. We finish our delegate method by updating the display. We could have put colorButton.setBackgroundColor(myModel.color)
in our code, but as we expand the application in the next lessons, you will find we end up repeating code. Add the following to the InterfaceController
code:
//MARK: - Instance Methods func updateDisplay(){ let statusText = String(format: "%1.2f", myModel.myNumber) statusLabel.setText(statusText) colorButton.setBackgroundColor(myModel.myColor) }
The other place that we can add updateDisplay
is the willActivate
method, which is the viewWillAppear
of WatchKit.
override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() updateDisplay() }
Build and Run. Tap the Colors button. Tap the Red Button. The Colors button is now red:
We now have a working modal from a segue. In the next part, we’ll present modals programmatically.
The Whole Code
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 { //MARK: Outlets and Properties @IBOutlet weak var statusLabel: WKInterfaceLabel! @IBOutlet weak var colorButton: WKInterfaceButton! var myModel = DemoModel() //MARK: - Delegates func colorDidFinish(context: AnyObject?) { myModel = context as! DemoModel dismissController() updateDisplay() } //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() } }
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