Swift Watchkit: Working with Modal Views Part 1: Segue with a Delegate

2015-06-02_06-08-04

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.

2015-06-01_05-52-05

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).

2015-06-01_05-53-30

Control-Drag from the Color Button to the Colors interface.

2015-06-02_06-08-04

Release the mouse button. You will get a choice of segues. Select modal.

2015-06-02_06-08-39

Select the segue that appears and label it  ColorsModal

2015-06-02_06-11-32

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.

2015-06-03_06-13-02

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:

2015-06-03_06-45-25

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()
    }

}

2 thoughts on “Swift Watchkit: Working with Modal Views Part 1: Segue with a Delegate”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s