In our last lesson we set up navigation in the Storyboard and programmatically. We left off with passing data from one view controller to another using the context
parameter like this:
@IBAction func goButtonPressed() { //prepare context var myContext:Bool? = isDarkColor //logical navigtion if isGoingToGray { pushControllerWithName("Gray", context: myContext) }else{ pushControllerWithName("Color", context: myContext) } }
We made a context variable which we passed to the destination controller. We haven’t done anything yet with the value of the context. In this lesson, we’ll move the values between controllers
Open the code we worked with last time. Go to the GrayInterfaceController.swift file. You’ll find there the following method:
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. }
You can consider awakeWithContext
the viewDidLoad
of Watchkit. There is one major difference. The awakeWithContext
method has a parameter of context
. This is the same context we passed in pushControllerWithName
in the last lesson. When the method executes, we can take the passed data and set it up for this method.
We’d like to change the background color to light to dark. Unfortunately, there is no backgroundColor
property or setter – only an attribute on the storyboard. However we can work around this with a very large group. Go to the storyboard and in the object library find group:
Drag a group onto the gray interface, then drag a group to the Color interface.
For each of the groups, change the size to Relative to Container for both Width and Height.
The group now takes all available space. In the Attribute inspector, set the gray group’s color to Light Gray in the attributes inspector. Using the color picker in the background’s attribute inspector, set the color to the color #440088 we used in the last lesson.
Open the assistant editor. Select the Gray interface and control-drag from the interface to the GrayInterfaceController
class code:
Add an outlet named grayBackgroundGroup:
Do the same for the ColorInterfaceController,
naming it colorBackgroundGroup
.
Close the assistant editor. Go to the GrayInterfaceController
code again. Add the following three declarations above the outlet you just created:
var isDarkColor = false let darkColor = UIColor.darkGrayColor() let lightColor = UIColor.lightGrayColor()
We create a property we’ll transfer the context to. Change the awakeWithContext
code to this:
@IBOutlet weak var grayBackgroundGroup: WKInterfaceGroup! //MARK: - Life Cycle override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. isDarkColor = context as! Bool //change context into property if isDarkColor { grayBackgroundGroup.setBackgroundColor(darkColor) } else { grayBackgroundGroup.setBackgroundColor(lightColor) } }
This loads the context into the property, then does the test to find what color to use.
Build and run. You can now select the dark or light color in the gray interface.
Let’s Set up similar code in the color interface, but before we do, let’s set up a switch to let us change the color while in the interface. Start by going back to the storyboard and adding a switch to the Color Interface.
Open the assistant editor and add an outlet named colorSwitch and an action named colorSwitchDidChange. Once done, close up the assistant editor and go to the ColorInterfaceController
code. Like we did before, add the properties and constants for the controller:
var isDarkColor = false let darkColor = UIColor(red: 68.0/255.0, green: 0.0, blue: 136.0/255.0, alpha: 1.0) let lightColor = UIColor(red: 187.0/255.0, green: 1.0, blue: 0, alpha: 1.0)
This time we used two contrasting colors. Add this to awakeWithContext:
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. isDarkColor = context as! Bool refreshDisplay() }
We’ll use a refresh method to prevent duplication of code. Add this method:
func refreshDisplay(){ colorSwitch.setOn(isDarkColor) if isDarkColor { colorBackgroundGroup.setBackgroundColor(darkColor) colorSwitch.setTitle("Dark") } else { colorBackgroundGroup.setBackgroundColor(lightColor) colorSwitch.setTitle("Light") } }
Build and run. We get similar results but with the switch indicating dark or light.
Now add the following to the colorSwitchDidChange
@IBAction func colorSwitchDidChange(value: Bool) { isDarkColor = value refreshDisplay() }
Build and run. We can now change from dark to light when we are on the color interface.
Delegates and Contexts
When we are on the color interface, the result of the switch does not reflect back in the UPInterfaceController
. Like navigation controllers in UIKit, we’ll delegate the result back to the UpInterfaceController
. WatchKit has a wrinkle in our plans for doing so. There is no destinationViewController
property for a WKInterfaceController.
There is no way to set the properties of the destination view controller directly. We have passed a single parameter named context
to the destination we name in pushControllerWithName
like this:
pushControllerWithName("Color", context: myContext)
Context is of type AnyObject?
which means we can pass whatever we want through it. If we want multiple values, we can use tuples, dictionaries, structs or classes to pass as many values as we want in our pushController
method. In the awakeWithContext
in the destination, we break apart the context to distribute properties to the destination. In a separate lesson, we’ll discuss the power and costs of doing each. For this lesson, we’ll make a class and pass a class between the controllers. For example, add above the class declaration for UpInterfaceController
the following class:
class DarkOrLight:NSObject{ var isDarkColor = false var count = 0 }
Change the goButtonPressed
method to this:
@IBAction func goButtonPressed() { //prepare context var myContext:Bool? = isDarkColor //logical navigtion if isGoingToGray { var myContext:Bool? = isDarkColor pushControllerWithName("Gray", context: myContext) }else{ var myContext = DarkOrLight() myContext.isDarkColor = isDarkColor myContext.count = 2 pushControllerWithName("Color", context: myContext) } }
We did several things here. Depending on the destination interface, we have a different context. For the Gray interface, we have a context of a single Bool
value. For the Color interface, we pass a object of class DarkOrLight.
We assign values to the two properties of DarkOrLight
and then send it as the context. The awakeWithContext
on the destination controller will take the context and assign its vales to the correct properties. On the destination controller ColorInterfaceController
change awakeWithContext
to this
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //convert the context to usuable properties let myContext = context as! DarkOrLight println(myContext.count) isDarkColor = myContext.isDarkColor as Bool // update display refreshDisplay() }
First we cast the context to the correct type. Secondly, we assign each of the properties of DarkOrLight
to values in the ColorInterfaceController
. We have no real use for the count
in this example so we just printed it to the console to see that it works. If you build and run, you should see no difference between the two codes, except the number 2 showing up in the console.
While we will discuss more about the context in later lessons, our purpose is to use it to set up a delegate to send back values to a previous controller. If you haven’t worked with delegates before, I’d suggest reading my tutorial of them Why Do We Need Delegates?
To set up a delegate, we first make a protocol. Above the ColorInterfaceController
declaration add the following code to set up a protocol.
protocol ColorInterfaceDelegate{ func colorDidChange(value:Bool) }
In the ColorInterfaceController
add a delegate property with the protocol for a type.
var delegate:ColorInterfaceDelegate? = nil
In our action colorSwitchDidChange
, add the delegate calling the protocol’s method.
@IBAction func colorSwitchDidChange(value: Bool) { isDarkColor = value refreshDisplay() delegate?.colorDidChange(value) }
Go over to the UpInterfacecController
class. In the class declaration adopt the delegate
class UpInterfaceController: WKInterfaceController,ColorInterfaceDelegate {
Xcode almost immediately gives the error about the protocol not being implemented. Add the required method to your code:
func colorDidChange(value:Bool) { isDarkColor = value println(isDarkColor) updateColorSwitch() }
We used a new method updateColorSwitch
in our code to prevent repetition of code. We’ll need to add it and we’ll change our action method to use it too. Change your code to this:
func updateColorSwitch(){ colorSwitch.setOn(isDarkColor) if isDarkColor{ colorSwitch.setTitle("Dark") }else{ colorSwitch.setTitle("Light") } } @IBAction func colorSwitchDidChange(value: Bool) { isDarkColor = value updateColorSwitch() }
The last step is to assign the current view controller to the delegate. Here’s where we use the context. In the goButtonPressed
action, add myContext.delegate = self
just under the count
assignment.
@IBAction func goButtonPressed() { //prepare context var myContext:Bool? = isDarkColor //logical navigtion if isGoingToGray { var myContext:Bool? = isDarkColor pushControllerWithName("Gray", context: myContext) }else{ var myContext = DarkOrLight() myContext.isDarkColor = isDarkColor myContext.count = 2 myContext.delegate = self // <-- add the delegate pushControllerWithName("Color", context: myContext) } }
Now we have to assign it to the property in the destination. add the line delegate = myContext.delegate as? ColorInterfaceDelegate
to the awakeWithContext
code in the ColorInterfaceController.
override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //convert the context to usuable properties let myContext = context as! DarkOrLight delegate = myContext.delegate as? ColorInterfaceDelegate //<-- add delegate let count = myContext.count isDarkColor = myContext.isDarkColor as Bool // update display refreshDisplay() }
The way things are set up, we need to cast the delegate to ColorInterfaceDelegate
to make this work.
You could run the app now. However it will look like it isn’t working. The console does tell us that the delegate is changing the values. If we were only passing values to the original controller, this would be all we need to do. However we changed the user interface by changing the position and title of the switch. In WatchKit, you have only one method willActivate
to update the view before presentation. This is the equivalent of the viewWillAppear
in UIKit. Change the willActivate
code in the UpViewController
to this:
override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() updateColorSwitch() }
Build and run. When we change colors in the color interface, then go back to Up, it reflects in the switches.
This is great if you are just updating from a programmatic button. We still don’t know how to do so with a segue or how to dismiss an interface on the press of a button. We’ll cover this in our next lesson.
The Whole Code
UpInterfaceController.swift
// // UpInterfaceController.swift // SwiftNavigationDemo // // Created by Steven Lipton on 5/21/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation class DarkOrLight:NSObject{ var isDarkColor = false var count = 0 var delegate:AnyObject? = nil } class UpInterfaceController: WKInterfaceController,ColorInterfaceDelegate { //MARK: - Outlets var isGoingToGray = true var isDarkColor = true @IBOutlet weak var colorSwitch: WKInterfaceSwitch! @IBOutlet weak var navigationSwitch: WKInterfaceSwitch! //MARK: - Actions @IBAction func navigationSwitchDidChange(value: Bool) { if value{ navigationSwitch.setTitle("Gray") }else{ navigationSwitch.setTitle("Color") } isGoingToGray = value } func colorDidChange(value:Bool) { isDarkColor = value print(isDarkColor) updateColorSwitch() //popController() } func updateColorSwitch(){ colorSwitch.setOn(isDarkColor) if isDarkColor{ colorSwitch.setTitle("Dark") }else{ colorSwitch.setTitle("Light") } } @IBAction func colorSwitchDidChange(value: Bool) { isDarkColor = value updateColorSwitch() } @IBAction func goButtonPressed() { //prepare context var myContext:Bool? = isDarkColor //logical navigtion if isGoingToGray { var myContext:Bool? = isDarkColor pushControllerWithName("Gray", context: myContext) }else{ var myContext = DarkOrLight() myContext.isDarkColor = isDarkColor myContext.count = 2 myContext.delegate = self // <-- add the delegate pushControllerWithName("Color", context: myContext) } } //MARK: - Life Cycle 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() updateColorSwitch() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
GrayInterfaceController.swift
// // GrayInterfaceController.swift // SwiftNavigationDemo // // Created by Steven Lipton on 5/21/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation class GrayInterfaceController: WKInterfaceController { //MARK: - Properties, Constants and Outlets var isDarkColor = false let darkColor = UIColor.darkGrayColor() let lightColor = UIColor.lightGrayColor() @IBOutlet weak var grayBackgroundGroup: WKInterfaceGroup! //MARK: - Life Cycle override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. isDarkColor = context as! Bool //change context into property if isDarkColor { grayBackgroundGroup.setBackgroundColor(darkColor) } else { grayBackgroundGroup.setBackgroundColor(lightColor) } } 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() } }
ColorInterfaceController.swift
// // ColorInterfaceController.swift // SwiftNavigationDemo // // Created by Steven Lipton on 5/21/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import WatchKit import Foundation protocol ColorInterfaceDelegate{ func colorDidChange(value:Bool) } class ColorInterfaceController: WKInterfaceController { var isDarkColor = false let darkColor = UIColor(red: 68.0/255.0, green: 0.0, blue: 136.0/255.0, alpha: 1.0) let lightColor = UIColor(red: 187.0/255.0, green: 1.0, blue: 0, alpha: 1.0) var delegate:ColorInterfaceDelegate? = nil @IBOutlet weak var colorSwitch: WKInterfaceSwitch! @IBOutlet weak var colorBackgroundGroup: WKInterfaceGroup! @IBAction func colorSwitchDidChange(value: Bool) { isDarkColor = value refreshDisplay() delegate?.colorDidChange(value) } func refreshDisplay(){ colorSwitch.setOn(isDarkColor) if isDarkColor { colorBackgroundGroup.setBackgroundColor(darkColor) colorSwitch.setTitle("Dark") } else { colorBackgroundGroup.setBackgroundColor(lightColor) colorSwitch.setTitle("Light") } } override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. //convert the context to usuable properties let myContext = context as! DarkOrLight delegate = myContext.delegate as? ColorInterfaceDelegate //<-- add delegate let count = myContext.count isDarkColor = myContext.isDarkColor as Bool // update display refreshDisplay() } 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