Tag Archives: Notification

Add Actions and Categories to Notification in Swift

In  earlier lessons I’ve shown you how to make a notification and how to manage notifications with the UserNotifications frame work. One very exciting part of the frame work is executing  the app’s code in the background. You don’t have to open the app to do custom actions or even to input data. Using categories and actions you can build code that does that directly from the notification. In this lesson I’ll show you how to use this powerful feature.

Categories, Actions and Delegates — Oh my!

Notifications have two special objects called categories and actions.  Actions are controls added to a notification, usually a button. Categories are a set of actions we can link to the content of a notification. You can mix and match categories and actions as much as you wish.

Actions have no executable code. Instead you specify in a method of UNUserNotificationCenterDelegate  the code based on an identifier  in the action.

This makes for a very flexible system for adding small bits of code to a application. There are time and memory limits on the code, so keep it short and simple.

Setting up the Demo

Let’s set up an example application to show  a very simple alarm application. The alarm will time for 10 seconds, then display an alarm and sound a sound. We’ll add two actions to this alarm: one as a five second snooze button and the other to place a comment on the notification.  You’ll find a starter file here notificationcategorydemo_start if you want to skip this section.

Open a new single view project in Xcode called NotificationCategoryDemo. Make it a Swift Application with a Universal device.

Go to the storyboard. Add a label and a button. I’m not going to get fancy here, but I set up my button and label like this:

2016-12-01_06-17-19

I used the Title 1 font.  I suggest changing the attributes of the label. Set the Lines to 0 and Line Break to Word Wrap. This way, long text entries will word wrap by themselves.
2016-12-02_06-21-08
Open   to the assistant editor and control-drag from the button to the code to make an IBAction named StartButton.  Control drag from the label to the code and make a IBOutlet named commentsLabel.   Close the assistant editor and go to Viewcontroller.swift.

I’m going to go fast here and just give you the code. I’ll assume you’ve read the post on how to make a user notification. Under import UIKit, add the following:

import UserNotifications 

Add the following properties and constants to the ViewController class

let time:TimeInterval = 10.0
let snooze:TimeInterval = 5.0
var isGrantedAccess = false

Set up the required authorization check. Change viewDidLoad to this:

    override func viewDidLoad() {
        super.viewDidLoad()
      UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert,.sound,.badge],
            completionHandler: { (granted,error) in
                self.isGrantedAccess = granted
                if granted{
                    self.setCategories()
                } else {
                    let alert = UIAlertController(title: "Notification Access", message: "In order to use this application, turn on notification permissions.", preferredStyle: .alert)
                    let alertAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
                    alert.addAction(alertAction)
                    self.present(alert , animated: true, completion: nil)
                }
        })

Create a function setCategories which we’ll use in the lesson and resolve the error in viewDidLoad

func setCategories(){
}

Create a function addNotification which simplifies adding notifications to the rest of the code. I particularly hate retyping that error handler closure a zillion times.

func addNotification(
    content:UNNotificationContent,
    trigger:UNNotificationTrigger?,
    indentifier:String)
{
    let request = UNNotificationRequest(
        identifier: indentifier, 
        content: content, 
        trigger: trigger)
    UNUserNotificationCenter.current().add(request,
        withCompletionHandler: { (errorObject) in
            if let error = errorObject{
                print("Error \(error.localizedDescription) in notification \(indentifier)")
            }
        }
     )
}

We’ll add the notification from the  Start button. Change the startButton method to this:

@IBAction func startButton(_ sender: UIButton) {
    if isGrantedAccess{
        let content = UNMutableNotificationContent()
        content.title = "Alarm"
        content.subtitle = "First Alarm"
        content.body = "First Alarm"
        content.sound = UNNotificationSound.default()
        let trigger = UNTimeIntervalNotificationTrigger(
             timeInterval: time,
             repeats: false)
        addNotification(
             content: content, 
             trigger: trigger , 
             indentifier: "Alarm")
        }
    }

I did add one new type of content I haven’t talked about before. If you specify a value in the sound property of your notification content and grant permission for using a sound, sounds will play during your notification. I’m using the default sound available at UNNotificationSound.default().

I’ll use in-app notification in this application, and you’ll need the delegate anyway for actions. Add the UNUserNotificationDelegate to the ViewController class:

class ViewController: UIViewController,
     UNUserNotificationCenterDelegate {

Set the delegate to self in viewDidLoad

UNUserNotificationCenter.current().delegate = self

Then add to your code the userNotificationCenter(willpresent notification:...) method.

// MARK: - Delegates
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert,.sound])
    }

Be certain to add .sound to hear the sound in the in-app notification.

The setup is complete. You can download a starter file by clicking  notificationcategorydemo_start

Adding Categories and Actions

Categories and actions must register with the system before you make any notifications. Generally you do that in viewDidLoad. If you look at the code there,  you’ll find I set a function call to setCategories if notification access is granted. You’ll add the categories and actions in setCategories.

Actions are contained in categories. Add actions first then categories. Actions are objects of class UNNotificationAction. You use a constructor for UNNotificationAction to make an actions. Add this to the setCategories function.

func setCategories(){
    let snoozeAction = UNNotificationAction(
        identifier: "snooze",
        title: "Snooze 5 Sec",
        options: [])

There’s three parameters here. The identifier is a unique string to identify the action in the delegate. The title is the button’s title when the actions display on the device. The options parameter are a list of options you can change the behavior or look of the button, such as a destructive button or forcing authentication before carrying out an action. Leave this blank to keep things simple.

Add this action to a category which we’ll call alarmCategory. Categories are UNNotificationCategory objects, and like UNNotificationAction, have a constructor. Add this to your code:

let alarmCategory = UNNotificationCategory(
    identifier: "alarm.category",
    actions: [snoozeAction],
    intentIdentifiers: [],
    options: [])

There are four parameters for this constructor. There is a unique identifier which you’ll use to link the category to a notification’s content. The actions parameter is an array of actions associated with in this category. You can have several categories which mix and match actions differently for different notifications or different contexts for a single notification. The parameter intentIdentifiers is a Siri thing and that’s way beyond the scope of this lesson, so leave it blank. Finally, there are options for a custom dismiss action and allowing CarPlay to use the actions. Again, I left the options blank.

The final step is to register the categories in the current UserNotificationCenter. You specify a set of categories the system should know. Add this code.

UNUserNotificationCenter.current().setNotificationCategories([alarmCategory])

Since we have only one category, this is a very simple list.

Using Categories in Notifications

Having your categories set up, add them to your notification content. The UNMutableNotificationContent  has a property categoryIdentifier. In the startButton method, specify the category identifier in your content.

content.categoryIdentifier = "alarm.category"

You are almost set to run your code. Set your simulator to iPhone 6. There’s one feature of the simulator that rides close to that thin line of being a feature or a bug. On anyone not using a 3D touch trackpad it is a bug from my perspective. The simulator assumes you have 3D Touch on your Mac if you simulate devices that have 3D touch. Accessing notification actions requires 3D touch on those devices in the simulator. Setting your simulator to an iPhone 6 lets any Mac running the simulator access the actions.

Build and run. The app appears.

2016-12-02_07-38-33

Press the Start button. Wait ten seconds. The notification appears.

2016-12-02_07-39-19

 

You can swipe down from the notification and see the action, but it does nothing. There’s no code for it do anything.

2016-12-02_07-40-31

Make an Action Do Something

It is the user notification center’s delegate that does the heavy lifting. The userNotficationCenter(didReceive response: completionHandler:) method is based on the actionIdentifier you defined earlier. The delegate method executes a bit of code to handle that action.

Where you keep your delegates in your code, add this.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
}

Before you do anything else, add the completionHandler to the bottom of the code. In this method, the system uses the closure, but at the end of your code you have to call it. I try to add it first so I don’t forget it.

completionHandler()

The UNNotificationResponse object has two properties: the actionIdentifier of the action that fired, and the delivered notification. I tend to be most interested in the request, so make two constants to work with these values easily.

let identifier = response.actionIdentifier
let request = response.notification.request

The identifier is a string I can compare to the identifier names I defined in the setCategories method. So to execute actions for the snooze action identifier, add an if clause

        
if identifier == "snooze"{
}

Inside the if clause, add code to do the actions when the user presses the Snooze 5 Sec button. For this demo, I’ll make a notification that fires five seconds later. You need mutable content from the request. Add this inside the if clause:

let newContent = request.content.mutableCopy() as! UNMutableNotificationContent

By copying the old request’s content, all content is set up. You just have to change the subtitle and body like this:

newContent.body = "Snooze 5 Seconds"
newContent.subtitle = "Snooze 5 Seconds"

The trigger will change from 10 seconds to 5 seconds. Define a new trigger method.

let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: snooze, repeats: false)

Send the new trigger and content. Add a notification using the addNotification method you defined earlier.

addNotification(content: newContent, trigger: newTrigger, indentifier: request.identifier)

Build and run. Tap the start button and wait. When the notification appears, swipe down to see the button. Tap the snooze button, and five seconds later, the snooze notification appears.

2016-12-02_07-50-03

 

Since we copied the content, you can swipe down again, and snooze as many times as you like.

2016-12-02_07-52-22

Add Text Input

There’s a subclass of UNNotificationAction you’ve used if you replied to a text message in a notification. There is a text input action in notifications. Adding text input, with a few variations, is the same as adding any other action.

There’s two constructors for the UNTextinputNotificationAction object. The shorter has the same parameters as the UNNotificationAction. The longer of the two adds two parameters for placeholder text and the submit button title. Use that one for this project, adding this to setCategories, between the snoozeAction and the alarmCategory.

 let commentAction = UNTextInputNotificationAction(identifier: "comment", title: "Add Comment", options: [], textInputButtonTitle: "Add", textInputPlaceholder: "Add Comment Here")

Add the action to the category. Change the actions parameter for the alarmCategory from [snoozeAction] to [snoozeAction,commentAction].

let alarmCategory = UNNotificationCategory(identifier: "alarm.category", actions: [snoozeAction,commentAction], intentIdentifiers: [], options: [])

In the delegate method usernotificationCenter( didReceive response: completionHandler:) add another if clause:

if identifier == "comment"{
}

The delegate method thinks that response is a UNNotificationResponse, not a UNTextInputNotificationResponse. In the if clause, downcast the response to the proper type.

let textResponse = response as! UNTextInputNotificationResponse

There’s one extra property on a UNTextInputNotificationResponse: a string named userText. Send that string to the label in the app:

commentsLabel.text = textResponse.userText

Make new notification that will include your comment. Use the body in the notification for your comment:

let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
newContent.body = textResponse.userText
addNotification(content: newContent, trigger: request.trigger, indentifier: request.identifier)

Build and run. Press Start, then Command-L to lock the screen. Ten seconds later the notification appears.

2016-12-02_07-58-22

Swipe to the left, and two buttons appear.

2016-12-02_07-58-40

Tap View. the actions appear.

2016-12-02_07-58-59

Tap Add Comment. The keyboard should appear in the simulator. If it does not press Command-K.

2016-12-02_07-59-20

Type a comment then tap Add.

2016-12-02_08-00-25

Wait and the notification appears with your comment. Body text can be as long as you want, as long as it is one character. An empty body hides the notification.

2016-12-02_08-00-56

Tap the notification, open the phone with command-shift-H and app title has changed.

2016-12-02_08-01-18

 

Actions on Apple Watch

It’s not that difficult to add actions to a notification. When you have some code you want to execute without opening the app, it is extremely useful to use actions. As an added benefit, if you create an action on your iPhone app’s notification, any user with an Apple Watch will get both the notification and the actions on their watch. When the phone if sleeping or locked, the notification will go to the watch, with the actions below the notification.

img_7915

The text action  in Add Comment uses the text input system of the watch, so you can dictate, scribble, use emoji or your quick phrases.

img_7913

Tapping a text action will run the code on your phone for the notification. You’ll see the notification on your watch

img_7917

And the text label on the phone changes.

img_7918

You never have to pull your phone out of your pocket or bag to respond with actions. With not that much code, you get a lot of performance with notification actions.

 

The Whole Code

There is a download of the completed project here: notificationcategorydemo.zip

//
//  ViewController.swift
//  NotificationCategoryDemo
//
//  Created by Steven Lipton on 12/2/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit
import UserNotifications

class ViewController: UIViewController,UNUserNotificationCenterDelegate {
    
    //MARK: Properties and outlets
    let time:TimeInterval = 10.0
    let snooze:TimeInterval = 5.0
    var isGrantedAccess = false
    @IBOutlet weak var commentsLabel: UILabel!
    //MARK: - Functions
    func setCategories(){
        let snoozeAction = UNNotificationAction(identifier: "snooze", title: "Snooze 5 Sec", options: [])
         let commentAction = UNTextInputNotificationAction(identifier: "comment", title: "Add Comment", options: [], textInputButtonTitle: "Add", textInputPlaceholder: "Add Comment Here")
        let alarmCategory = UNNotificationCategory(identifier: "alarm.category",actions: [snoozeAction,commentAction],intentIdentifiers: [], options: [])
        UNUserNotificationCenter.current().setNotificationCategories([alarmCategory])
    }
    
    func addNotification(content:UNNotificationContent,trigger:UNNotificationTrigger?, indentifier:String){
        let request = UNNotificationRequest(identifier: indentifier, content: content, trigger: trigger)
        UNUserNotificationCenter.current().add(request, withCompletionHandler: {
            (errorObject) in
            if let error = errorObject{
                print("Error \(error.localizedDescription) in notification \(indentifier)")
            }
        })
    }
    
    //MARK: - Actions
    @IBAction func startButton(_ sender: UIButton) {
        if isGrantedAccess{
            let content = UNMutableNotificationContent()
            content.title = "Alarm"
            content.subtitle = "First Alarm"
            content.body = "First Alarm"
            content.sound = UNNotificationSound.default()
            content.categoryIdentifier = "alarm.category"
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: time, repeats: false)
            addNotification(content: content, trigger: trigger , indentifier: "Alarm")
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert,.sound,.badge],
            completionHandler: { (granted,error) in
                self.isGrantedAccess = granted
                if granted{
                    self.setCategories()
                } else {
                    let alert = UIAlertController(title: "Notification Access", message: "In order to use this application, turn on notification permissions.", preferredStyle: .alert)
                    let alertAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
                    alert.addAction(alertAction)
                    self.present(alert , animated: true, completion: nil)
                }
        })
    }
    
    // MARK: - Delegates
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert,.sound])
    }
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let identifier = response.actionIdentifier
        let request = response.notification.request
        if identifier == "snooze"{
            let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
            newContent.body = "Snooze 5 Seconds"
            newContent.subtitle = "Snooze 5 Seconds"
            let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: snooze, repeats: false)
            addNotification(content: newContent, trigger: newTrigger, indentifier: request.identifier)
            
        }
        
        if identifier == "comment"{
            let textResponse = response as! UNTextInputNotificationResponse
            commentsLabel.text = textResponse.userText
            let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
            newContent.body = textResponse.userText
            addNotification(content: newContent, trigger: request.trigger, indentifier: request.identifier)
        }
        
        completionHandler()
    }
}

 

 

Using Settings Bundles with Swift

Have you ever wondered how to put  user defined settings for your app into the settings app?  Xcode can create a special property list called a settings bundle which can append the  NSUserDefaults with more entries from Settings App.  You’ll find out in this lesson how to set up a settings bundle in your app for using the Settings App using XML. In the next lesson we’ll dive deep into the settings bundle using the property list editor.

I’ll assume you know how to use property lists and NSUserDefaults in this lesson. If you are not familiar with them you might want to check out my recent posts on Property Lists and NSUserDefaults

Create the  Settings Bundle

Start a new Single view application  project in Xcode called SettingsBundleDemo using Swift and a Universal device.

Right-click on the SettingsBundleDemo group folder and select New file. Select the  Resources  category in the template window. Select Settings Bundle and click Next.

2016-03-11_05-57-54

You should have name of settings. Keep the setting bundle in the SettingsBundleDemo Group  in the save window. Click Create.

The system will open up the settings bundle. which looks like a building block. In the Navigator open up the settings bundle by clicking on the arrow.

2016-03-11_06-00-28

Click on the Root.plist file. You’ll see a property list  like this:

2016-03-13_12-33-22

If closed, Open the Preference Items array. This is populated with demo data. Unlike other property lists,  this one does not directly store the value as a dictionary of values, but an array of dictionaries of controls. You pick the control you want in the Settings app by listing it in the property list. When we attach the settings bundle to the standard defaults of NSUserDefaults, the system will create key values in the NSUserDefaults.

The following table lists the type of controls that you can use:

2016-03-13_15-54-53

Each type of control has several attributes.   If not already open, Open the Group Control, the Item 0 entry,  in Root.plist.

2016-03-13_12-38-07

Groups are control used to group other controls together. They have only a title and a Type. Open the Item 3 (Slider).

2016-03-13_12-39-56

The slider has more required controls, the ones listed here.  Many controls will have a  poorly named attribute Default Value. This is the value shown in  the settings app, not a true value of the default. It will show the values after the first use, but it is for display purposes not value purposes.

Each control type has its own attributes, as described in the chart above.  Here’s a list of all the possible attributes for the property list.

2016-03-14_08-11-55

Making Your Own Settings

Delete the current entries. Property lists do not delete well in Root.plist.  The simplest way is to cut them out. Select Item 0 (Group). Right click on group, and select Cut in the menu. Then do the same for the Item 3( Slider)  and the rest of the controls. You should have a clean property list like this.

2016-03-13_12-43-40

Select Preference Items.  Make sure it is open. The arrow should be pointing down.  Press the Add (+) button on the entry. A new entry appears.

2016-03-13_12-48-19

There will also be a menu that opens. If you happen to click somewhere and lose the menu, click the down chevron(2016-03-13_12-50-18)  in the entry. You get a list of controls.

2016-03-13_12-51-21

Select Toggle Switch. This will give us a Bool value.  Open up the toggle switch control and you have the following attributes:

2016-03-13_12-53-55

Click the value for each blank attribute, and set Title to  Room for cream.  Set Identifier to coffee_cream.  The identifier is the key for the NSUserDefaults entry.  Set the Default Value to YES. Your attributes should look like this:

2016-03-13_12-57-07

Close the attributes for the switch, and select Item 0. Right click and select Add Row from the menu. Although it is the default, in the type menu, select Text Field.   Open up the Item 1(Text Field – ) to see the attributes.

2016-03-13_13-01-53

 

Set the Title to Beverage of choice.  Set the Identifier to coffee_type. Right click on Identifier and select Add Row. A new row appears asking for an appropriate attribute:

2016-03-13_13-08-55

Select the default Default Value. Set the Default Value to Coffee.

2016-03-13_13-08-56

Close the attributes for the Text menu. Select the  Preference items entry, right click  and select Add Row.  The new row pushed the other rows down becoming Item 0. Select Multi-Value. Multi-value uses two arrays to make a selection table. Open up the attributes of the multi-value.  Set the title to Size. Set the Default Value to 0. Set the Identifier to coffee_size.

2016-03-13_13-28-32

This control does not by default set up values. You must do that. Select the bottom attribute,  Right-click the entry, then choose Add Row.  Make the row type Values. Open Values‘ array, which should be empty. Using either the Add row or the + Icon, add four  sub rows to Values. Add the  number  0  to the first  row. Change the type of the row to Number.  Do the same for the other rows, making the values 1,  2 and 3.

2016-03-13_13-28-33

Close the Values array. Add another attribute to the multi-value control Titles. Open the attribute’s array and add four entries. These will be  the strings Small, Medium, Large, and Extra Large.

2016-03-13_13-31-04

We can look at our settings page now.  Just to make it easy to know when we can look at it, set the back ground in the Launchscreen.storyboard to Orange(#ff8000), and the Main.Storyboard to Yellow(#FFFF00). Build and run. When the Background turns yellow, go to the settings app.  If on a live device, hit the Home button. If on the simulator,  hit Command-Shift-H on your keyboard for the home button. Navigate to the settings app.

2016-03-13_13-42-05

Scroll down to the bottom of the app. You’ll find our app there.

2016-03-13_13-42-25

Click the item and we get a settings page.

2016-03-13_13-42-58

Setting up the Storyboard

Stop the app, then go to  Main.storyboard in Xcode. Add a switch, a label, a text field  and  a segmented control to the storyboard. Configure the controls to look like this:

2016-03-13_14-27-04

Select the switch. In the size inspector, set Compression Resistance and Content Hugging to 1000 horizontally and vertically. Switches should never change size, and this prevents that from happening.

2016-03-13_14-36-49

Click the stack view button2016-03-13_14-36-07 to make a horizontal stack view. Select everything on the storyboard, and make vertical stack view by pressing the stack view button2016-03-13_14-36-07 again.  Set the Stack View’s attributes to this:

2016-03-13_14-43-19

Pin the stack view 71 up, 5 left and 5 right. Update frames for items of new constraints.

2016-03-13_14-50-17

Open up the assistant editor and connect up the following outlets to their proper controls:

@IBOutlet weak var roomForCream: UISwitch!
@IBOutlet weak var drinkText: UITextField!
@IBOutlet weak var sizeSegment: UISegmentedControl!

Reading the Settings Bundle

The NSUserDefaults does not know we have a settings bundle. Our first task is to register the settings bundle with NSUserDefaults.

func registerSettingsBundle(){
   let appDefaults = [String:AnyObject]()
   NSUserDefaults.standardUserDefaults().registerDefaults(appDefaults)
}

This registers the settings bundle to the NSUserDefaults standard defaults. the registerDefaults method looks for property lists in the resources directory and changes the value:key entries to dictionaries of the form [String:AnyObject]. We only run this once in our code.

Make a new function updateDisplayFromDefaults and get our defaults

func updateDisplayFromDefaults(){
//Get the defaults
    let defaults = NSUserDefaults.standardUserDefaults()
}

Read the key:value pairs and assign them to our outlets. Add this to the function:

//Set the controls to the default values. 
    roomForCream.on = defaults.boolForKey("coffee_cream")
    if let drink = defaults.stringForKey("coffee_type"){
        drinkText.text = drink
    } else{
        drinkText.text = ""
    }
    sizeSegment.selectedSegmentIndex = defaults.integerForKey("coffee_size")

The stringForKey method returns a optional value. Our code checks for a nil value and reacts accordingly. Add the new function to viewDidload:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    updateDisplayFromDefaults()
 }

We want to start fresh when we load the app. Go to the simulator or your device. Delete the application by pressing and holding on the app until it shakes. Once shaking, click the X to delete it. You will be asked

2016-03-13_15-03-16

Click Delete and the app and the settings are now deleted. Build and run the application. We start with a blank preferences, since they haven’t been written to the app yet. Since the Default Value of the property list is a display value, it does not reflect here.

2016-03-13_15-14-09

Press Command-Shift-H, then navigate over to the settings. Set your settings to this:

2016-03-13_15-20-13

Press Command-Shift-HH to and select the demo app. Our app hasn’t changed.

2016-03-13_15-14-09

Close the app in Xcode. Re-run the app. Now we get our defaults.

2016-03-14_07-56-20

Updating Defaults with Observers

Updating our preferences on restart only  is not a good thing. The problem is we need to tell the app that there was a change. This needs to be done automatically.In both a settings bundle and for NSUserDefaults within an app, this will be a frequent problem. You change a setting and want everywhere that uses it to update. Such changes need notification observers. I think of fire watchers as an analogy to observers. You have a forest ranger sitting in a tower looking for fire in the forest below. If she finds one, then she sets off an alrm to fight the fire to everyone in the forest. An observer does the the same thing in code. It looks for a certain event to happen, then runs a target method if is sees it happening. These events can be system defined events known as notifications (not to be confused with push notifications that show up on your device) or you can make up your own. I’ll discuss how to make notifications in a future post. We’ll concentrate on system generated ones.

The NSUserDefaults class generates a notification NSUserDefaultsDidChangeNotification. We can set an observer for the notification, and then run some code to update the display. Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    updateDisplayFromDefaults()
        
    NSNotificationCenter.defaultCenter().addObserver(self,
        selector: "defaultsChanged",
        name: NSUserDefaultsDidChangeNotification,
        object: nil)
    }

We call the default notification center which keeps track of our observers to register it. This center is NSNotificationCenter.defaultCenter(). We add the observer with name NSUserDefaultsDidChangeNotification and tell it to run the function defaultsChanged when it observes a notification.

As an aside, you might be tempted to add the code from registerSettingsBundle as part of your updateDisplayFromDefaults. Every execution of registerSettingsBundle changes the NSUserDefaults by adding more defaults. Each time we add a default we get a notification, causing a recursive death spiral until we run out of memory. Keep it separate.

We’ll need a small function defaultsChanged to run when we have a notification. Add this to your code:

func defaultsChanged(){
        updateDisplayFromDefaults()
    }

There’s also a bit of legacy code we have to add. Prior to iOS9, we need to remove the observer for good memory management. In iOS9 and later, ARC does that for us. For any iOS8 devices, add the following to remove the observer.

deinit { //Not needed for iOS9 and above. ARC deals with the observer.
NSNotificationCenter.defaultCenter().removeObserver(self)
}

The code uses the Swift class’ deinint function to remove the observer from NSNotificationCenter.defaultCenter().

A Bug in the Simulator

Build and run. Hit Command-Shift-H then switch over to the settings app. Select the settings for the SettingsBundleDemo and… It’s blank.

2016-03-13_15-19-17

This doesn’t happen on a device. Only the simulator. There is a workaround. It seems the simulator is still running your last iteration of your app. When you hit stop in Xcode it doesn’t stop the settings app. Click Command-Shift-HH to double-click the home button. Swipe up on the Settings app to kill the process. Start the Settings again, and settings refreshes itself. You’ll have a settings page again. Change the settings to this.

2016-03-14_07-33-44

Go back to the Settings Bundle Demo. Our results show up this time.

2016-03-14_07-34-16

That’s the basics of Settings Bundles. There are a few more advanced options such as child settings pages and coding the settings directly in XML. The charts above have the tags for XML and there is is the Root.plist code below in XML for your further exploration.

The Whole Code

//
//  ViewController.swift
//  SetttingsBundleDemo
//
//  Created by Steven Lipton on 3/11/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var roomForCream: UISwitch!
    @IBOutlet weak var drinkText: UITextField!
    @IBOutlet weak var sizeSegment: UISegmentedControl!
    
    deinit { //Not needed for iOS9 and above. ARC deals with the observer.
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    
    func registerSettingsBundle(){
        let appDefaults = [String:AnyObject]()
        NSUserDefaults.standardUserDefaults().registerDefaults(appDefaults)
        //NSUserDefaults.standardUserDefaults().synchronize()
    }
    
    func updateDisplayFromDefaults(){
    
        
     
        //Get the defaults
        let defaults = NSUserDefaults.standardUserDefaults()

        //Set the controls to the default values. 
        roomForCream.on = defaults.boolForKey("coffee_cream")
        if let drink = defaults.stringForKey("coffee_type"){
            drinkText.text = drink
        } else{
            drinkText.text = ""
        }
        sizeSegment.selectedSegmentIndex = defaults.integerForKey("coffee_size")
    }

    func defaultsChanged(){
        updateDisplayFromDefaults()
    }
    @IBAction func updateDefaults(sender: AnyObject) {
        updateDisplayFromDefaults()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        registerSettingsBundle()
        updateDisplayFromDefaults()
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "defaultsChanged",
            name: NSUserDefaultsDidChangeNotification,
            object: nil)
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}


Root.plist

While we did not discuss them here, you can make entries in XML to your property list. Here’s the property list for the demo in XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>StringsTable</key>
	<string>Root</string>
	<key>PreferenceSpecifiers</key>
	<array>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Size</string>
			<key>Key</key>
			<string>coffee_size</string>
			<key>DefaultValue</key>
			<string>0</string>
			<key>Values</key>
			<array>
				<integer>0</integer>
				<integer>1</integer>
				<integer>2</integer>
				<integer>3</integer>
			</array>
			<key>Titles</key>
			<array>
				<string>Small</string>
				<string>Medium</string>
				<string>Large</string>
				<string>Extra Large</string>
			</array>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSToggleSwitchSpecifier</string>
			<key>Title</key>
			<string>Room for cream</string>
			<key>Key</key>
			<string>coffee_cream</string>
			<key>DefaultValue</key>
			<true/>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSTextFieldSpecifier</string>
			<key>Title</key>
			<string>Beverage of Choice</string>
			<key>Key</key>
			<string>coffee_type</string>
			<key>DefaultValue</key>
			<string>Coffee</string>
		</dict>
	</array>
</dict>
</plist>

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

}