Make App Pie

Training for Developers and Artists

Swift WatchKit: Working with Modal Views Part 2: Presenting Programmatically

Photo Jun 10, 8 54 07 AMIn 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:

2015-06-10_08-49-29

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.

2015-06-05_08-03-53

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.

2015-06-10_08-52-12

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.

2015-06-10_08-59-42     2015-06-10_09-04-19

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

2015-06-10_08-50-18

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

}

3 responses to “Swift WatchKit: Working with Modal Views Part 2: Presenting Programmatically”

  1. […] now have a working modal from a segue. In the next part, we’ll present modals […]

  2. […] the last part, we learned we can call a modal view programmatically using the method […]

  3. […] so if you are not yet familiar on how those work, take a look at the lesson on context menus and on programmatic modal controllers. We’ll also be using class methods, so if you need a refresher on that, you can try […]

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: