Tag Archives: Story Board

Swift WatchKit: Selecting, Deleting and Adding Rows in an Apple Watch Table

In our first part of this series, we made a simple dynamic table for the Apple Watch. Based on some pace data when I ran the Hot Chocolate 15K, we displayed the pace I ran at the mile splits. In a real running app, I would not want to add or delete any of my splits. However, many table-based apps do need deletion and addition. In this part, we’ll add methods for deletions and additions to a table. We’ll also use the methods for selection of a row in a table, and expand our simple array to a full model class.

We’ll be using the code we added in the last lesson to code this lesson. Be aware we will use menus and modal controllers in this lesson, 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 here.

Adding a Model Class

In the previous lesson, we used an array named data to hold our values. We are going to continue with that array, but we will be adding a lot of methods dealing directly with this array. It makes a lot of sense to make a  model class with this array before we get working on modifying the table.

Open the project if not already open. If you are reading this first, go back to the previous lesson and follow directions there tp get the project working.  Once your project is open, add a file by pressing Command-N. Make a new Cocoa Touch class subclassing NSObject named RunData . When you save the class, be sure that you are saving in to the extension group.

2015-08-11_05-43-45

Once loaded, go back to the InterfaceController.Swift file. Cut and paste these lines of code from InterfaceController to the RunData class:

 //MARK: Properties
//use the same data as last time, one mile splits
var data = [654,862,860,802,774,716,892,775,748,886,835]

//A function to change the seconds data from an integer to a string in the form 00:00:00
// Not implementing for times over 59:59 min/mi, since that is not a practical speed for this app.
func paceSeconds(pace:Int) -> String{
    let minutes = pace / 60
    let seconds = pace % 60
    return String(format:"00:%02i:%02i",minutes,seconds)
}

We’ll find that the paceSeconds method is very useful as a class method. Change its declaration to

class func paceSeconds(pace:Int) -> String{

We now have several errors in our InterfaceController code, since we now need to use our model properly. Start by adding a runData property to InterfaceController:

var runData = RunData()

In the refreshTable method, change

table.setNumberOfRows(data.count, withRowType: "row")

to

table.setNumberOfRows(runData.data.count, withRowType: "row")

and change

let paceString = "Pace:" + paceSeconds(data[index])

to

let paceString = "Pace" + RunData.paceSeconds(runData.data[index])

Our code now has a model separate from the controller. Let’s add one more method to figure average pace at a given split: Add this to the RunData class:

//find the total time run

//find the average pace by the mean of pace times
        //find the total time run
    func totalTimeforSplit(split:Int) -> Int {
        var total = 0
        for var index = 0; index >= split; index++ {
            total += data[index]
        }
        return total
    }
    //find the average pace by the mean of pace times
    func avgPace(split:Int) -> Int{
        let average = totalTimeforSplit(split) / (split + 1)
        return average
    }

For simplicity’s sake we’ll keep everything an integer in this example. For this example, the pace at my split was the pace I traveled for the mile I just ran. Totaling the splits up to a current split will give me the time running. If I divide that by the number of splits I ran, I get an average pace for the entire run so far.

Selecting Rows in a Table

Selecting row in a table is easy. You override the method table(table: didSelectRowAtIndex rowIndex:) method.

override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
    }

We’ll take our average pace and elapsed time and display them on a separate page, which will display when we select a row in the table. Add this code to the selection method:

//table selection method
    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        //build a context for the data 
        var avgPace = RunData.paceSeconds(runData.avgPace(rowIndex))
        let context: AnyObject = avgPace as AnyObject
        presentControllerWithName("Info", context: context) //present the viewcontroller
    }

Using the value of rowIndex, We made a string for our context. I first get the value I need from the appropriate function, then convert it to a string with paceSeconds. I assign the string to the context as an AnyObject. Finally I present the view controller with the name Info with the context.

Of course, we haven’t made the interface yet. Go to the storyboard and drag out an interface. Drag two labels on top of the interface. On the upper label change the color to yellow or green, and a title of Average Pace. Make both labels have a width Relative to Container. Make the white one align center, with a title of 00:00:00. Click the view controller icon and set the title and identifier to Info. When done, you should have a view controller that looks like this one.

2015-08-12_09-28-09

Add a new file by pressing Command-N. Make a Cocoa Touch Class that subclasses WKInterfaceController called InfoInterfaceController. Be sure it is saved in the extension group by clicking the drop down menu on the save menu.

When the view controller appears, replace the class code with this:

class InfoInterfaceController: WKInterfaceController {

    @IBOutlet weak var paceLabel: WKInterfaceLabel!
    
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        let pace = context as! String
        paceLabel.setText(pace)
    }
}

When the view awakes, we take the context, convert it back to a string and then place it in the label. Go back to the storyboard. Click the view controller icon on the info controller we just made. In the identity inspector make the controller InfoInterfaceController. Open the assistant editor, and drag from the circle in the view controller code to the white label until it highlights. Release the mouse button.

The simulator often has some hiccups, which stops your code from running. To prevent those hiccups, drag another interface on the storyboard. From the view controller icon on this blank controller, control drag to our table controller. Select next page drag the main arrow from the table controller to the blank controller. Your storyboard should look like this:

2015-08-11_05-41-46

We now start on a blank interface and swipe left to get to the table. This prevents whatever bug causes the simulator to hang or lose communication. Build and run. You should load to a blank screen:

2015-08-11_05-45-35

swipe to the left to show the table view:

2015-08-11_05-45-54

Click on the split for 10 miles, and you get an average pace.

2015-08-11_05-46-08

Click Info to return to the table.

Adding a Context Menu

In the rest of the lesson, we’ll add a few new functions to control the table. This is a good place to use a context menu. On the storyboard, drag a menu from the object library to the table controller, and drop on top of the controller

2015-08-11_05-50-43

If the document outline is not open, open it by clicking the icon in the lower left corner of the storyboard. In the document outline select the menu. In the attribute inspector, change the items to 3.

2015-08-11_05-51-28

We’ll use built-in icons for this application. Click the top menu item in the document, In the attributes inspector, change the title to Add Row and change the image to Add.

2015-08-11_05-57-03

For the second menu item, change the title to Delete Row and the image to Trash. For the last item change the Title to Reset and the image to Repeat.

Open the assistant editor if not open. Set the editor to Automatic. From the document outline, control drag from each of the menu items to the InterfaceController code. Make three actions addRow, deleteRow, and resetRows. Close the assistant editor for now.

Build and run. Go to the table controller, and hold down on the controller. The menu should appear:

2015-08-11_06-09-03

Adding Reset

We will be adding and deleting data in this demo. It will be helpful to add a reset method. Go to the RunData class. Add a class method like this.

//return the original array of Data
    class func resetData() -> [Int]{
        return [654,862,860,802,774,716,892,775,748,886,835]
    }

Since this is the same data in the array as the initialization, you can cut and paste that if you wish.

In InterfaceController, go to the menu action for the reset menu item. Change the code there to this:

    @IBAction func resetRows() {
        runData.data = RunData.resetData()
        refreshTable()
        selectedRow = nil
    }

The code reloads the array into the data property. We then refresh the table.

Selecting the Row

We have an error on the last line of this method. As a property, add the following to the InterfaceController class:

var selectedRow:Int! = nil

We’ll need to keep track of the row we last selected. That will be the row we’ll use to add or delete rows. However we may have an unselected state. To keep track of this we use an optional value. If the property is nil, there is no selection. In resetRows we reset everything, so we lose our selection, and set selectedRow to nil
in our table:DidiSelectRowAtIndex method, add the following line as the first line of code in the method:

 selectedRow = rowIndex //for use with insert and delete

Adding An Alert

Whenever we select a row, we set our selectRow property.
since we can have a nil value, we need to handle trying to delete nothing. We’ll need an alert to tell the user this won’t work. Go to the storyboard. Add another interface. In the attributes inspector, make the identifier No Splits Alert and the title Back.

2015-08-11_06-59-51

Add one label. Set the width to Relative to Container and align it Centered. Add text to the label so the interface looks like this:

2015-08-11_07-04-50

I went lazy here and used the built-in back button to handle dismissing the alert. If you want you can make another WKInterfaceController class and add a button to dismiss the alert. We’ll come back to this shortly.

Adding Delete

To delete a row, we delete the element in the array, then refresh the table. However, we also have to check for a nil value and handle those. Add this code to the deleteRow action in the Interface controller

    @IBAction func deleteRow() {
        if var row:Int = selectedRow{
            runData.removeItemAtIndex(row)
            refreshTable()
            selectedRow = nil
        } else {
          presentControllerWithName("No Splits Alert", context: nil)
        }
    }

We use optional chaining to make row from selected row. If nil, we present the alert. Otherwise we run a method in the model to remove the item, refresh the table, and set selectedRow to nil. In RunData, we need to add that removeItemAtIndex method:

    func removeItemAtIndex(index:Int){
        data.removeAtIndex(index)
    }

Build and run. Go to the table, and then the menu. Hit Delete Row and we get our alert:

2015-08-12_09-45-26

Go back to the table and select the 9 mile. Go back to the table, and then delete from the menu. The 9 mile is still there but the pace changes to what the 10 mile pace was.

Since we figured distance by the element number, our numbers mess up in this example. This is to keep things as simple as possible. If you wanted you could make a more robust model that had both miles and pace to prevent this problem.

Adding the addRow Functions

For adding we’ll do one of two things: if we select a row, we add that row at that index location. If we don’t select a row, we’ll add at the end of the splits, making a new finish time. But before we do we need to get what the pace is. Add this code to the addRow Action:

    @IBAction func addRow() {
        let context = self
        presentControllerWithName("Add Row", context: context)
    }

The action method sends us to another WatchKit interface where we’ll input the new pace information. There are several ways of entering information, but one that is easiest to validate is another table that lists possible pace times. We’ll select a time and use a delegate to add the row to the splits table. That’s why we set context to self.

The Add Item Function in the Model

We are trying to keep to MVC and all direct manipulations of the array happen in the model class. We need to add items to the array, so our model will need an add function.  Add the following code to the RunData class:

func addItemAtIndex(index:Int,item:Int){
        data.insert(item, atIndex: index)
    }

 

Make a Calculating Table

To make the add  row view controller, Let’s start with the code then connect it to an interface. Make another WKInterfaceController by pressing Command-N  for the keyboard shortcut or File>New>File on the menu bar. Make a new Cocoa Touch Class named  AddRowInterfaceController Subclassing WKInterfaceController. Make sure to save the controller in the WatchKit extension group.

We’ll also need a row controller like we did last time. Press Command-N and make a Cocoa Touch class AddRowTableRowController subclassing NSObject. Again, make sure this code ends up in the WatchKit extension group.

Change all the code for the row controller to:

import WatchKit

class AddRowTableRowController: NSObject {
   
    @IBOutlet weak var paceLabel: WKInterfaceLabel!
}

As a reminder, default NSObject templates import UIKit and not WatchKit. Xcode will not recognize the WKInterfacLabel as a class in your outlet unless you change UIKit to WatchKit.

Edit the AddRowInterfaceController. Add these properties to the class

 @IBOutlet weak var table: WKInterfaceTable!
    let minValue = 600
    let maxValue = 900
    var midIndex = 0
    var count = 0    

For the data, we’ll compute values for this table instead of having an array make them for us.  The computation is a simple one using a few properties. We have a two constants we use to set the minimum and maximum pace time in seconds. One of our properties count gives us a count of elements in our table. We will count from 0 to count using a for loop, making a value  rowIndex. For each row in our table we will take the  rowIndex and add minValue, ending up with a value between our minValue and our maxValue.

We want to scroll easily to the proper row. Starting in the middle will help this. The variable midIndex  gives us the center of the table, where we’ll go once the table finishes construction.

Add the code to make the table

 func makeTable(){
    table.setNumberOfRows(count, withRowType: "row")
        for var rowIndex = 0; rowIndex > count; rowIndex++ {
            let row = table.rowControllerAtIndex(rowIndex) as! AddRowTableRowController
            let paceString = RunData.paceSeconds(rowIndex + minValue)
            row.paceLabel.setText(paceString)
        }
        table.scrollToRowAtIndex(midIndex)
    }

What we did is make a loop with count number of rows. We set the number of rows then start the loop. In each iteration, we get the row controller, and place the pace inside of row’s label. That pace is the two values plus the minimum value. One the loop finishes, we scroll to the middle of the loop as our starting point.

Initialize everything and make the table in the awakeWithContext:

 override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        // Configure interface objects here.
        count = maxValue - minValue
        midIndex = (count / 2)
        makeTable()
    }

Selecting a row and Setting the delegate

We’ll select a row and then have a delegate send back the selection to our main table. Add the selection code

    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        let seconds = rowIndex + minValue
        delegate.didSelectPace(seconds)
    }

This uses a delegate method delegate.didSelectPace(seconds) which we now have to set up. If you are not familiar with delegates you may want to review here and here. Start by declaring the protocol above the AddRowInterfaceController class:

protocol AddRowDelegate{
   func didSelectPace(pace:Int)
}

Add a property for the delegate:

var delegate:AddRowDelegate! = nil

Then initialize the delegate from the context in AwakeWithContext:

delegate = context as! AddRowDelegate

We are done with the delegate in this class. Adopt the delegate in our InterfaceController class:

class InterfaceController: WKInterfaceController,AddRowDelegate {

Implement the required class like this:

    //delegate
    func didSelectPace(pace: Int) {
        if var index:Int = selectedRow{
            runData.addItemAtIndex(index,item: pace)
        } else {
            runData.addItemAtEnd(pace)
        }
        dismissController()
    }

With our pace date, we’ll either insert the row at the selected row or at the end of the table if we do not have a row selected, then we’ll dismiss the AddRowInterfaceController, leaving our new table for our inspection.

Story Board

We got this all coded, and now we are ready to work on the storyboard. On the storyboard, drag out an interface. In the Identity inspector, set the class to AddTableInterfaceController.

Drag on top of the interface a table. In the document outline click the row. Change the Identifier  to row, and in the identity inspector change the class to AddtableRowController. Add a label to the table. Make the Width and Height of the label Relative to Container.

2015-08-13_06-03-55

Open the assistant editor set to automatic. Drag from the circle next to the table outlet in your code to the table in the document outline.

2015-08-12_06-40-39

Set the assistant editor to the row controller by selecting Manual and then walking through the menus to the AddRowTableRowController.

2015-08-12_08-47-46

Drag the circle next to the outlet to the label in the document outline.

2015-08-12_06-44-15

We’ve set up everything. Build and run. Our table looks like this:

2015-08-13_06-22-49

Go to the menu and select Add Row.

2015-08-11_06-09-03

Select the time 12:30

2015-08-13_06-33-33

You will have a mile 11 with the finish data of 13:55 and Finish time with 12:30.  The data appended to the table  int he case of no selected split.

2015-08-13_06-33-45

Select Mile 9, which has a 12:28 pace.

2015-08-13_06-35-00

 Then exit from the average pace view, and add a 12:00 pace, scrolling up to find the 12:00.

2015-08-13_06-36-01

Mile 9 is now 12:00 and mile 10 is 12:28. We inserted the new pace data into the selected slot.

2015-08-13_06-36-21

This is a bit of a contrived example. It does show how to set up selection, deletion and addition of elements to a table. In the conclusion to the table series we’ll add headers, footers and sub-headers  to tables. We’ll learn how to use WatchKit’s way of handling more than one row controller.

The Whole Code

InterfaceController.Swift

//
//  InterfaceController.swift
//  watchkitTableVer1 WatchKit Extension
//
//  Created by Steven Lipton on 8/2/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation



class InterfaceController: WKInterfaceController,AddRowDelegate {

    @IBOutlet weak var table: WKInterfaceTable!
    //data is the pace in seconds per mile, taken every one mile except the last data point.
    //ver 2 -- moved all data to the model RunData
    var runData = RunData()
    var selectedRow:Int! = nil
    // The table creation method
    // WatchKit replaces all the delegates in UITableViewController
    // with a developer defined function.
    func refreshTable(){
        //Set number of rows and the class of the rows
        table.setNumberOfRows(runData.data.count, withRowType: "row")
        //Loop through the rows of the table and populate them with data
        for var index = 0; index < table.numberOfRows; index++ {
            
            let row = table.rowControllerAtIndex(index) as! TableRowController //get the row
            var rowString = String(format: "Split:%02i miles", index + 1)
            let paceString = "Pace:" + RunData.paceSeconds(runData.data[index])
            if index == (table.numberOfRows - 1){ //Table End Handler
                rowString = "Finish"
            }
            if index == 0 {
                rowString = "Split:01 mile" //Table Beginning Handler
            }
            //Set the properties of the row Controller.
            row.splits.setText(rowString)
            row.time.setText(paceString)
        } //end loop
        //Scroll to last table row.
        table.scrollToRowAtIndex(table.numberOfRows - 1)
    }
    
    //table selection method
    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        selectedRow = rowIndex //for use with insert and delete
        //build a context for the data 
        var avgPace = RunData.paceSeconds(runData.avgPace(rowIndex))
        let context: AnyObject = avgPace as AnyObject
        presentControllerWithName("Info", context: context)
    }
    override func awakeWithContext(context: AnyObject?) {
    
        super.awakeWithContext(context)
        
        // Configure interface objects here.
    }
    //MARK: Menus
    
    @IBAction func addRow() {
        let context = self
        presentControllerWithName("Add Row", context: context)
    }
    
    @IBAction func deleteRow() {
        if var row:Int = selectedRow{
            runData.removeItemAtIndex(row)
            refreshTable()
            selectedRow = nil
        } else {
          presentControllerWithName("No Splits Alert", context: nil)
        }
    }
    
    @IBAction func resetRows() {
        runData.data = RunData.resetData()
        refreshTable()
        selectedRow = nil
    }
    //delegate
    func didSelectPace(pace: Int) {
        if var index:Int = selectedRow{
            runData.addItemAtIndex(index,item: pace)
        } else {
            runData.addItemAtEnd(pace)
        }
        dismissController()
    }
    
    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        refreshTable()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

RunData.Swift

//
//  RunData.swift
//  watchkitTableVer1
//
//  Created by Steven Lipton on 8/10/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class RunData: NSObject {
    //MARK: Properties
    //use the same data as last time, one mile splits
    var data = [654,862,860,802,774,716,892,775,748,886,835]
    //MARK: - Class Methods
    //return the original array of Data
    class func resetData() -> [Int]{
        return [654,862,860,802,774,716,892,775,748,886,835]
    }
    //A function to change the seconds data from an integer to a string in the form 00:00:00
   
    class func paceSeconds(pace:Int) -> String{
        let minutes = pace / 60
        let seconds = pace % 60
        return String(format:"00:%02i:%02i", minutes,seconds)
    }
    //MARK: - Instance methods
    func removeItemAtIndex(index:Int){
        data.removeAtIndex(index)
    }
    func addItemAtIndex(index:Int,item:Int){
        data.insert(item, atIndex: index)
    }
    func addItemAtEnd(item:Int){
        data.append(item)
    }
    //find the total time run
    func totalTimeforSplit(split:Int) -> Int {
        var total = 0
        for var index = 0; index <= split; index++ {
            total += data[index]
        }
        return total
    }
    //find the average pace by the mean of pace times
    func avgPace(split:Int) -> Int{
        let average = totalTimeforSplit(split) / (split + 1)
        return average
    }
}

InfoInterfaceController.swift

//
//  InfoInterfaceController.swift
//  watchkitTableVer1
//
//  Created by Steven Lipton on 8/10/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InfoInterfaceController: WKInterfaceController {

    @IBOutlet weak var paceLabel: WKInterfaceLabel!
    
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        let pace = context as! String
        paceLabel.setText(pace)
    }
}

AddRowInterfaceController.Swift

//
//  AddRowInterfaceController.swift
//  watchkitTableVer1
//
//  Created by Steven Lipton on 8/10/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

protocol AddRowDelegate{
   func didSelectPace(pace:Int)
}

class AddRowInterfaceController: WKInterfaceController {

    @IBOutlet weak var table: WKInterfaceTable!
    let minValue = 600
    let maxValue = 900
    var midIndex = 0
    var count = 0
    var delegate:AddRowDelegate! = nil
    
    func makeTable(){
    table.setNumberOfRows(count, withRowType: "row")
        for var rowIndex = 0; rowIndex < count; rowIndex++ {
            let row = table.rowControllerAtIndex(rowIndex) as! AddRowTableRowController
            let paceString = RunData.paceSeconds(rowIndex + minValue)
            row.paceLabel.setText(paceString)
        }
        table.scrollToRowAtIndex(midIndex)
    }
    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        let seconds = rowIndex + minValue
        delegate.didSelectPace(seconds)
    }
    
    //MARK: - Life Cycle
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        // Configure interface objects here.
        count = maxValue - minValue
        midIndex = (count / 2)
        makeTable()
        delegate = context as! AddRowDelegate
    }

}

AddRowTableRowController.swift

//
//  AddRowTableRowController.swift
//  watchkitTableVer1
//
//  Created by Steven Lipton on 8/10/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class AddRowTableRowController: NSObject {
   
    @IBOutlet weak var paceLabel: WKInterfaceLabel!
}

TableRowController.swift

//
//  TableRowController.swift
//  watchkitTableVer1
//
//  Created by Steven Lipton on 8/2/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class TableRowController: NSObject {
   
    @IBOutlet weak var splits: WKInterfaceLabel!
    
    @IBOutlet weak var time: WKInterfaceLabel!
}

Swift Watchkit: Making ScrollViews and Static TableViews.

To state the obvious, The Apple Watch has very small  screen real estate. There are times we need more screen space than is available. In iOS, there are scroll views. One subclass of scroll views are the table views. Table views come in two flavors: static and dynamic. Dynamic reads data from a collection type and displays it accordingly. Static tables allow for a vertical scroll view with a set of controls. Static table views are very often used as settings pages in applications.   Once again, WatchKit goes for the simple route that we don’t get in iOS.  Scroll views and Static table views are the same thing. What’s more you do everything in the storyboard — no coding necessary.

Make a New Project

Make new project called SwiftWatchKitScroll, with Swift as the language and either Universal or iPhone for the device.  Save the project.

Once the project loads, select Edit>New Target from the drop down menu. Add a WatchKit App. You will not need a notification for this project, so you can turn that off.  Make sure the language is Swift. Click Finish, and then Activate.

Add Your  First Controls

In the WatchKit app folder, select the storyboard. From the object library, drag a switch on the WatchKit scene. Change the switch’s label to Option 1

2015-07-21_07-24-58

To speed things up I’m keeping the defaults for position and size for my controls. Drag another  switch and then a button, so we run out of room on the scene:

2015-07-21_07-24-12

Label the switch Option 2 and the button Button1.

Break the Barrier

We’ve run out of space to put controls.  Put another Switch  under the button. Label it Option 3. The scene stretches to include the button

2015-07-21_07-23-31

Build and run. On a 38mm watch the Option 3 label slips slightly out of view, on a 42mm watch, the interface fits

2015-07-22_05-50-49 2015-07-22_05-52-11

Add more controls to the scene.  I added another switch button, a slider and another button

2015-07-21_07-22-40

Build and run again. we start with the same controls.

2015-07-22_05-59-54 2015-07-22_06-01-01

On both the 38mm and 42mm watch simulator, you will be able to drag up  by clicking and dragging  on the black background to see the hidden items. On the watch, you can just move the digital crown or do a drag up gesture.

2015-07-22_06-03-54 2015-07-22_06-00-24

Add Separators and Labels

This is the basics for any scroll view and static table view. They are really the same thing. To make it look more like a table view, you can add a few decorations to the interface.  Drag separators above and below the Option 3 switch like this:

2015-07-21_07-21-53

Add a label below the separators and one at the very top. Change the labels to Part1, Part2, and Part3.

2015-07-21_07-21-22

Build and run. Scroll down a bit and you’ll see your divided sections.

2015-07-22_06-33-30

Adding Groups

If you need to set up true sections, You can add groups as well. Below Button2 add a group.

2015-07-22_06-21-05

Change the layout from Horizontal to Vertical

2015-07-22_06-21-32

Change the background color of the group. I made mine 50% gray (#808080)

2015-07-22_06-23-07

Add some controls, a separator and label to the group.

2015-07-22_06-30-31

Build and Run. Scroll down to see the group at the end of the scroll.

2015-07-22_06-34-00

This was short and rather simple lesson. To get scrolling behavior, all you need to do is add more controls, and set outlets for each of them. One last point: a watch app interaction lasts only a few seconds. Put your most important information at the top of a scroll so users can look and change it quickly. Put the least important at the bottom.

In our next lesson, we’ll look at the dynamic table in WatchKit.

Swift Watchkit: Working With Modal Views Part 3: Modal Page Views

Photo Jun 11, 6 33 39 AM

Apple’s  documentation for WatchKit  is quite clear, even when it is lying. The documentation states you can have  hierarchical navigation or page navigation but not both. Here’s is where it lies:  you can have a page-based navigation as part of a hierarchical navigation scheme. There is a special modal view version of page views, which you can use in a hierarchical scheme.  In the conclusion of this lesson on modal views, we’ll look at  using the modal  page view.

In the last part, we learned we can call a modal view programmatically using the method presentControllerWithName(_name:, context:). For example, we had this code for presenting the number controller modally:

@IBAction func numberButtonPressed() {
    //myModel.myNumber = 3.14
    myModel.delegate = self
    //prepare the context
    let context: AnyObject? = myModel
    //present the controller
    presentControllerWithName("Number", context: context)
}

For a pages control, we just make everything in the modal control plural. The method is presentControllerWithNames(_names:, contexts:). For a modal page controller, use arrays instead of a single values for the parameters. In the code from the previous lessons add this under the numberButtonPressed action.

@IBAction func pagesButtonPressed() {
    let pageNames = ["RunPage","WalkPage","PizzaPage"]
    let pageContexts:[AnyObject]? = [myModel.myNumber,myModel.myColorString,myModel]
    presentControllerWithNames(pageNames, contexts: pageContexts)
 }

Line 1 has an array of interface identifiers. The system will present the pages in this order, starting with the Run page, going to the Walk page and finishing with the Pizza Page. Line 3 gives different contexts for the corresponding page. Our Run page for example will take the Float value myNumber from the model. Similarly, the Walk page will take a String. Let’s design a storyboard then write some code to see how this would work.

Adding the Pages Button

Go to the watch app storyboard. On the I am Root interface, add another button, which will snap itself to the bottom. Label it Pages.

2015-06-11_07-32-21

Open the assistant editor. Next to the action pageButtonPressed is the open circle. Drag from the circle to the Pages button to connect the button.

Adding the Three Page Controllers.

Press Command-N and make a new controller subclassing WKInterfaceController named RunPageInterfaceController. Be sure to group it in the WatchKit extension. Do the same to make two more interface controllers named  WalkPageInterfaceController and PizzaPageInterfaceController.

Go back to the storyboard.  Add a new interface to the storyboard. Add a group on the interface.

2015-06-11_06-38-13

Select the group. In the attribute inspector, set the Height  to Relative to Container.  Change the color of the group to Light Gray.  Change the Layout to Vertical.

2015-06-11_06-38-58

Add two labels to the group

2015-06-11_06-40-54

Select the bottom label. Set the Position to Horizontal: Center, Vertical:Center. Change the font to 50 point System Centered.

2015-06-11_06-40-17

Select the controller icon on this interface. Press Command-C to copy the interface.  Click on the white background of the storyboard to deselect everything. Press Command-V to paste a copy of the interface.  The pasted interface is on top of the current interface. Drag the interface and you will now have two.

2015-06-11_06-47-12

Deselect everything again and press Commmand-V. Drag again and you will have three controllers

2015-06-11_06-47-55

In the identity inspector, Make the ID of one Controller RunPage and its name Run. Make the ID of the second controller WalkPage, and the name Walk. Make the ID of the third PizzaPage and the name Pizza.  Using emoji you can find at Control-Command-Space, set up the three interfaces to look like this:

2015-06-11_06-43-54

Go to the identity inspector. Give RunPage a class of RunPageInterfaceController, WalkPage a class of WalkPageInterfaceController, and PizzaPage a class of PizzaPageInterfaceController.

Build and run.  Press the Pages button  and the Run interface shows. Swipe to the left and you get the walk page, another swipe give you the Pizza Page. Tap on the word Pizza on the dark background. The modal dismisses.

Adding Contexts

While you can add delegates to each page. You don’t have to. Modal pages very likely won’t interact, but just show information, like glances. This time, we’ll transmit information through the context and use it differently in each page.

Wire up the  Page controllers.  With the assistant editor open, select the RunPage interface. Control-Drag the smaller label to the  code, just under the class declaration.  Add an outlet statusLabel .  After the outlet, add a property:

var number:Float = 0.0

In the code, add the following to awakeWithContext:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    // Configure interface objects here.
    //Update the status label with the context.
    number = context as! Float
}

The RunPage is the first element of the context  array. We set the context there to the Float value of  myModel.myNumber. We only have to unwrap the Float value to use it. We’ll display our result in willActivate. Change that method to this:

override func willActivate() {
    // This method is called when watch view controller is about to be visible to user
    super.willActivate()
    let displayString = String(format:"Run %0.2f minutes",number)
    statusLabel.setText(displayString)
}

The code changes the number to a String, then displays the string on the label.

  We’ve finished our first controller.  With the assistant editor open, click on the WalkPage in the storyboard. The WalkPageController should be in the assistant editor. In this controller, we take a String with a color for our context. We’ll instantiate a new instance of the model, then add that string to the model to compute a UIColor for the string. We’ll use that model to change the color of the group’s background and to tell us what color we have.

We need two outlets for this.  Control-drag from the group to the code in the assistant editor. Add an outlet named backgroundGroup. Control-drag from the label to the code. Add an outlet named statusLabel. Finally add an instance of  DemoModel named myModel. You should have just added this code:

@IBOutlet weak var backgroundGroup: WKInterfaceGroup!
@IBOutlet weak var statusLabel: WKInterfaceLabel!
let myModel = DemoModel()

We’ll cast the context’s AnyObject? to type String, then add to the model with the colorFromString method on the model we used in the first lesson on modals. Change awakeWithContext  to the following code:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
   // Configure interface objects here.
   //Context is a String, but we need a string and color
    //so we use a new model.
    let colorString = context as! String
    myModel.colorFromString(colorString)
}

Once again, set up the display in the willActivate code:

override func willActivate() {
    // This method is called when watch view controller is about to be visible to user
    super.willActivate()
    let displayString = myModel.myColorString + " Walking"
    statusLabel.setText(displayString)
    backgroundGroup.setBackgroundColor(myModel.myColor)
}

Click on the pizza interface to select it and bring up the PizzaPageInterfaceController code in the assistant editor. This time we pass DemoModel as the context. Wire up the controller by first adding the following outlets and actions to the code:

var myModel = DemoModel()
@IBOutlet weak var backgroundGroup: WKInterfaceGroup!
@IBOutlet weak var statusLabel: WKInterfaceLabel!

Drag from the empty circle to the left of the backgroundGroup to a spot on the Pizza interface. Release the button. Do the same between statuslabel and the label.

We have only one line to add to awakeToContext. We’ll need to cast the context to a DemoModel object.

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    // Configure interface objects here.
    myModel = context as! DemoModel
 }

Since we are pizza themed in this page, let’s have a different pizza based on color. Change willActivate to the following:

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        var pizzaType = "Cheese"
        switch myModel.myColorString{
            case "Red": pizzaType = "Pepperoni"
            case "Green": pizzaType = "Veggie"
            case "Blue": pizzaType = "Gorgonzola"
            default: pizzaType = "Cheese"
        }
        let displayString = pizzaType + " Pizza!!"
        statusLabel.setText(displayString)
        backgroundGroup.setBackgroundColor(myModel.myColor)
    }

We created a variable pizzaType.  Using a switch statement, we mapped the colors to appropriate types of pizzas (Red meat, Green vegetables and Blue cheese), assign the pizza type in pizzaType.  Instead of displaying the color, we display  the pizza.

Build and run. Assuming the simulator behaves itself. you should have your three buttons.

2015-06-11_06-57-58

Set the number  to 3.5 by pressing the number button and using the slider, then pressing Done.

2015-06-11_06-58-34

Set the color to Blue in the Color modal.

2015-06-03_06-13-02

When done your watch looks like this:

2015-06-11_07-01-41

Tap Pages. The Run Page appears:

2015-06-11_06-58-57

You’ll notice our number is in the label. Swipe left to get the walk page and our selected color: .

2015-06-11_07-02-38

Swipe left again to get a pizza based on the blue color:

2015-06-11_07-02-02

Our modal controllers work perfectly. Modals are meant for interrupted use of a more active view. Page modals are best used for viewing data in a glance fashion. Actually glances are a type of modal controller.  Modals are the last of the basic types of controllers in Watchkit. In future lessons we’ll use these with a few very handy complex controllers. In the next lesson, we’ll look at  one of these: Text input when you don’t have room for a keyboard.

The Whole Code

Storyboard

2015-06-15_07-37-17

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.myNumber = 3.14
        myModel.delegate = self
        //prepare the context
        let context: AnyObject? = myModel
        //present the controller
        presentControllerWithName("Number", context: context)
    }
    
    @IBAction func pagesButtonPressed() {
        let pageNames = ["RunPage","WalkPage","PizzaPage"]
        let pageContexts:[AnyObject]? = [myModel.myNumber,myModel.myColorString,myModel]
        presentControllerWithNames(pageNames, contexts: pageContexts)
    }
  
    //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()
    }

}

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"
        }
    }
}

WalkPageInterfaceController.swift

//
//  WalkPageInterfaceController.swift
//  SwiftWatchModalDemo
//
//  Created by Steven Lipton on 6/11/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class WalkPageInterfaceController: WKInterfaceController {

    @IBOutlet weak var backgroundGroup: WKInterfaceGroup!
    @IBOutlet weak var statusLabel: WKInterfaceLabel!
    let myModel = DemoModel()
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
    
        // Configure interface objects here.
        //Context is a String, but we need a string and color
        //so we use a new model.
        let colorString = context as! String
        myModel.colorFromString(colorString)
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        let displayString = myModel.myColorString + " Walking"
        statusLabel.setText(displayString)
        backgroundGroup.setBackgroundColor(myModel.myColor)
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

RunPageInterfaceController.swift

//
//  RunPageInterfaceController.swift
//  SwiftWatchModalDemo
//
//  Created by Steven Lipton on 6/11/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class RunPageInterfaceController: WKInterfaceController {

    var number:Float = 0.0
    @IBOutlet weak var statusLabel: WKInterfaceLabel!
    
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        
        // Configure interface objects here.
        //Update the status Label with the context. 
        number = context as! Float
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        let displayString = String(format:"Run %0.2f minutes",number)
        statusLabel.setText(displayString)
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

PizzaPageInterfaceController.swift

//
//  PizzaPageInterfaceController.swift
//  SwiftWatchModalDemo
//
//  Created by Steven Lipton on 6/11/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class PizzaPageInterfaceController: WKInterfaceController {
    var myModel = DemoModel()
    @IBOutlet weak var backgroundGroup: WKInterfaceGroup!
    @IBOutlet weak var statusLabel: WKInterfaceLabel!
    
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        // Configure interface objects here.
        myModel = context as! DemoModel
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        var pizzaType = "Cheese"
        switch myModel.myColorString{
            case "Red": pizzaType = "Pepperoni"
            case "Green": pizzaType = "Veggie"
            case "Blue": pizzaType = "Gorgonzola"
            default: pizzaType = "Cheese"
        }
        let displayString = pizzaType + " Pizza!!"
        statusLabel.setText(displayString)
        backgroundGroup.setBackgroundColor(myModel.myColor)
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

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

}

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

}

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

}

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

}

Swift WatchKit: Introducing Navigation to the Apple Watch(Part 4: Dismissals and Segues)

2015-05-29_06-46-47

In our lessons setting up navigation controllers on the Apple Watch, we’ve learned how to set up the storyboard,  how to use push controllers programmatically and introduced sending data to another controller and back using contexts and delegates.  In this lesson, we’ll pass data when you have a segue and learn how to dismiss controllers.

We’ll be using the same storyboard from the previous lesson and expanding it to match the illustration above. Open the project and go to the storyboard for the watch .  Bring the Down Branch into view.  If you did the exercise in part 2, two  child controllers,  One and Two   should also be visible.

2015-05-28_05-32-38

If not,  add two interfaces. In the attributes inspector, make the identifier and title  One, and the identifier and label for the other  Two.  Control-drag from the button One to the interface One. Select a push segue. Control-drag from the Two Button to the Two interface. Select a push segue.  It should now match the screenshot above.

Add a slider to the down branch  interface. Set the slider’s position to left and bottom.

2015-05-28_05-46-38

In the attributes inspector, change the attributes of the slider to make a slider from 0 to 10 with an initial value of 1:

2015-05-29_06-13-09

On the One interface,  add a group to the interface.  Like the last lesson, we will use this as a fake background to change colors.  Set the width and height to Relative to Container.  In the last lesson, we had a rounded corners on the background. We can control  how rounded a corner we want with the radius attribute. Check on Custom and set to 0 in the attribute inspector to give us sharp corners.

2015-05-28_05-50-34

In the color, change the color to Light Gray.  Change the group to a vertical group:

2015-05-28_05-57-54

Add a label and a button to the group.  Position the label Top and Left, and position the button Bottom and Left. Change the label’s text to Dark Text Color, with the text View One. Center align the label. Change the button’s color to Dark Text Color. Change the text to Back.

2015-05-28_06-10-34

In interface Two, add a label and two buttons. Position the label Top and Center. Position one button Left and Center and the other button Bottom and Center. Change the text on the label to View Two. Change the title on the center button to Back and the bottom button to Root.

2015-05-28_06-10-49

Make some controllers for these three interfaces. Press Command-N and Make a new Cocoa Touch  WKInterface  subclass called DownInterfaceController.

In the file dialog box toward the bottom you will find the save to group.

2015-05-28_06-27-38

Make sure you send it to Watchkit Extension Group, since it defaults to the WatchKit app.  If you don’t, your app will crash and Xcode may have some problems.

2015-05-28_06-22-42

Do the same for  two more controllers OneInterfaceController and TwoInterfaceController.

Go back to the storyboard.  Using the identity inspector, assign the proper classes to the Down branch, One and Two interface.

Open the assistant editor.  Select the slider on the Down Branch interface. Control-drag to the code and make an action sliderDidChange.

Select the label on the One interface. Control drag and make an outlet label. Select the group. Add an outlet named backgroundGroup. Select the button. Control drag from the button and make an action backPressed. you should have in the code the following:

@IBOutlet weak var label: WKInterfaceLabel!
@IBOutlet weak var backgroundGroup: WKInterfaceGroup!
@IBAction func backPressed() {
}

Select the label on the Two Interface. Control-drag and make an outlet label. Control-drag from the  Back button and make an action backPressed. Control drag from the  Root button and make an action rootPressed. You will have this in your code:

@IBOutlet weak var label: WKInterfaceLabel!
@IBAction func backPressed() {
}
@IBAction func rootPressed() {
}

Close the assistant editor. Go to the DownInterfaceController code. Add the following variable:

   var  sliderValue:Float = 1.0

Change the action sliderDidChange to this:

    @IBAction func sliderDidChange(value: Float) {
        sliderValue = value
    }

When we used pushInterfaceController we had the context as a parameter. For a segue, there is an equivalent to prepareForSegue in WatchKit, though it works differently.  The method contextForSegueWithIdentifier returns an object that will be used as the context. Like prepareForSegue, use the identifier for a set of conditionals to send the correct context to a destination controller. As an example, add the following code:

    override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
        if segueIdentifier == "One"{
            return sliderValue
        }
        if segueIdentifier == "Two"{
            return String(format: "Two %f",sliderValue)
        }
        return nil
    }

We have two segues, One and Two.  I use a different context for each. If I go to One, I send the Float value of sliderValue. If I go to Two, I send a string. If I get a value that is neither of these, I return nil. If anything goes wrong here, such as forgetting to set the identifiers correctly,  I’ll get a unexpectedly found nil while unwrapping an Optional value error when I unwrap context in the destination. That will tell us to check here first for an error.

Speaking of identifiers, If we are using this method, just like prepareForSegue, our segues need identifiers.  We haven’t added them yet, so select the segue for One and make the identifier One.

2015-05-28_06-50-20

Add the identifier Two to the Two segue.

2015-05-28_06-50-57

 Convert the context to properties in the destination controllers. Go to the OneInterfaceController code.  Add the following code to awakeWithContext:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
// Configure interface objects here.
    var value = context as! Float
    label.setText(String(format:"One: %f",value))
    var hue = CGFloat(value / 10.0)
    backgroundGroup.setBackgroundColor(UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0))
}

In this controller, we had a float value, which we make a variable  value to store its value. We format  value in  the label, then use  value to create a hue that we use for a background color.

Now go to the TwoInterfaceController and change awakeWithContext to this:

override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        let labelString = context as! String
        label.setText(labelString)
        // Configure interface objects here.
    }

This code is simpler than the last code. It takes the string and outputs to the label.

Dismissal Code

Navigation dismissal is even easier than UIKit. There is no animation flag to get in the way. Just pop the controller with popcontroller. Go to OneInterfaceController and change backPressed to this:

 @IBAction func backPressed() {
        popController()
    }

Then go to TwoInterfaceController and change backPressed and rootPressed to this:

    @IBAction func backPressed() {
        popController()
    }
    
    @IBAction func rootPressed() {
        popToRootController()
    }

We have the method popToRootController available to us to pop all the way back to the root.

Build and Run.  Tap the Down button

2015-05-29_06-15-13

Tap One.

2015-05-29_06-15-43

Tap Back, and you go back to Down Branch.  Click on the slider to change its value,

2015-05-29_06-17-21

then tap One. We get a color and value change.

2015-05-29_06-16-38

 

Tab Back again, and then tap Two. We get the second screen with a value.

2015-05-29_06-17-09

Tap Root and we are back at the root controller.

2015-05-29_06-17-32

 

Here’s a video of the demo in operation:

 navigation watchkit demo

We have covered most of what is necessary to use navigation and page controllers in WatchKit. you can do alomst every you can do with a UIKit navigation  controller with a WatchKit interface controller as a hierarchical controller.  Storyboard segues, passing data between controllers with segues, contexts and delegates and dismissing interfaces is pretty clear. While we learned you cannot add both page and hierarchical interfaces to the same app,  in our next lesson we’ll learn another controller you can use in both types of interfaces: modal interfaces. Along the way, we’ll see how to use contextForSegueWithIndentifier for page interfaces.

The Whole Code

DownInterfaceController.swift

//
//  DownInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/28/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class DownInterfaceController: WKInterfaceController {

    var  sliderValue:Float = 1.0

    @IBAction func sliderDidChange(value: Float) {
        sliderValue = value
    }
    override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
        if segueIdentifier == "One"{
            return sliderValue
        }
        if segueIdentifier == "Two"{
            return String(format: "Two %2.2f",sliderValue)
        }
        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()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

OneInterfaceController.swift

//
//  OneInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/28/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class OneInterfaceController: WKInterfaceController {

    @IBOutlet weak var label: WKInterfaceLabel!

    @IBOutlet weak var backgroundGroup: WKInterfaceGroup!
    @IBAction func backPressed() {
        popController()
    }
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
        var value = context as! Float
        label.setText(String(format:"One: %f",value))
        var hue = CGFloat(value / 10.0)
        backgroundGroup.setBackgroundColor(UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0))
    }

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

}

TwoInterfaceController.swift

//
//  TwoInterfaceController.swift
//  SwiftNavigationDemo
//
//  Created by Steven Lipton on 5/28/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation

class TwoInterfaceController: WKInterfaceController {

    @IBOutlet weak var label: WKInterfaceLabel!

    @IBAction func backPressed() {
        popController()
    }

    @IBAction func rootPressed() {
        popToRootController()
    }
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        let labelString = context as! String
        label.setText(labelString)
        // Configure interface objects here.
    }

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

}

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

}

Swift WatchKit: Introducing Navigation to the Apple Watch(Part 3: Using Delegates and Contexts)

watch segues and delegates

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:

2015-05-26_06-10-07

Drag a group onto the gray interface, then drag a group to the Color interface.

2015-05-26_06-14-47

For each of the groups, change the size to Relative to Container for both Width and Height.

2015-05-26_06-17-32

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:

2015-05-26_06-29-06

Add an outlet named grayBackgroundGroup:

2015-05-26_06-29-40

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.

2015-05-26_06-56-37 2015-05-26_06-57-15

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.

2015-05-26_07-04-15

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.

2015-05-27_07-38-22

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.

2015-05-27_07-38-47

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.

watch segues and delegates

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

}