Tag Archives: WatchOS

Introducing Core Motion: Make A Pedometer

Apple packed a lot of sensors into the small packages that are iOS devices. You as a developer can detect movement in three dimensions, acceleration, rotations, and impact and use those in your applications. All of this brought to your application in the CoreMotion framework.

CoreMotion has many uses. Game developers use it to make a phone into a gaming device where the phone’s motion controls the game play. Fitness developers use core motion to measure types of movements common to a sport on both the iPhone and Apple Watch. Using the sensors and Core motion could measure a swing of a baseball bat or golf club for example.  Basic movements can also let other application execute a function. The Apple watch wakes up to a raise of the wrist for example.

Core motion is a complex  framework to use, not only for coding reasons. As it deals with motion, a developer might need to know more about that motion before developing an application. The framework has some low-level objects for working directly with the sensors.  There are, however, some motions that are so universal, core motion combines several sensors to get more bottom-line data. One good example of this kind of motion is putting one foot in front of the other. For walking and running motions CoreMotion has the CMPedometer class. The class gives you direct data about the number of steps,   To give you a taste of some of the issues involved in developing for Core motion, let’s make a pedometer from the CMPedometer class. You’ll need to hook up a phone to Xcode since the simulator does not work with Core Motion.

Setting Up the Project

Create a new single-view project named CoreMotionPedometer. Use Swift as a language and a iPhone for a device.  Save the project.

When the applications loads, change the Display Name to CMPedometer

2017-02-10_06-03-10

This will give us a smaller name for the Icon. In this application you may need to test off of Xcode, and this will help you find your app better.

Core motion in general and the pedometer more specifically uses several motions that Apple considers private data. On a pedometer, you’re also using location data, so privacy is an even bigger issue. Developers need to add a key NSMotionUsageDescription to the info.plist to ask permission of the user. You can add the key in XML like this:

<key>NSMotionUsageDescription</key>
<string>This is a step counter, and we&apos;d like to track motion. </string>

Most people will just go to the info.plist and add a entry to the dictionary.
2017-02-10_06-11-09

In the drop down, select Privacy – Motion Usage Descriptor
2017-02-10_06-10-45

This creates a key with a String value. The string will be the body of an alert to accept the privacy settings for the pedometer. Add This is a step counter, and we’d like to track motion.
2017-02-10_06-17-55

Designing the Storyboard

Go to the main storyboard. Add Five labels and a button. Place the button towards the bottom of the scene and the labels towards the top. Make the labels white, the button a green background and the scene background to black like this:

2017-02-10_06-26-20

Layout the button on the bottom of the phone a quarter of the height of the device. Change the tile color for the Start button to White, and the title font to System Black 28pt. Using the auto layout pin tool pinMenuButton, turn off the Constrain to Margins  and set the left to 0, bottom to 0 and right to 0.

2017-02-10_06-31-35

Add the three constraints. Control drag from the green of the button up to the black of the background.

2017-02-10_06-39-09

When you release the mouse button, select Equal Heights in the menu that appears

2017-02-10_06-37-13

The start button will fill the screen.  Go to the size inspector. Find the Equal Height to Superview constraint, and click the Edit button there.

2017-02-10_06-41-07

In the edit dialog that appears change the Multiplier to 1:4.

2017-02-10_06-45-07

You’ll have a nice big button on the bottom.

2017-02-10_06-48-00

I like having a big start/stop button. If you are doing a workout, it may be difficult to hit a small button, and this helps the user hit the button. It’s on the bottom in the thumb zone so the user can hit it one handed.

Select the Pedometer Demo label. In the attribute inspector, set the font to 22pt System heavy.  Using the auto layout pin tool pinMenuButton, turn  Using the auto layout pin tool pinMenuButton, turn on the Constrain to Margins  and set the Top to 20,  Left to 0 and Right to 0. Add the constraints.

Select the other four labels.  Set the font to System Heavy 17pt, Lines to 2 and Line Break to Word Wrap

2017-02-10_06-53-00

You’ll be using two lines of text in this demo, one for a metric measurement and one for an Imperial measurement used in most sporting events.  You’ll place them on two lines in the same label.

With all four labels selected, select the Stackview tool  stack view button from the autolayout toolbar in the lower right. The labels will embed in a vertical stack view.  Change the stack view to these attributes:

2017-02-10_07-02-31

Use the pin tool to pin all four sides of the stack view 10 points. Update the constraints.  You’ll have a storyboard like this one.

2017-02-10_07-04-04

Close the attributes inspector and open the assistant editor. Control drag from the labels and make the following outlets, with the statusTitle outlet to the Pedometer Demo label and the rest to their respective labels :

@IBOutlet weak var statusTitle: UILabel!
@IBOutlet weak var stepsLabel: UILabel!
@IBOutlet weak var avgPaceLabel: UILabel!
@IBOutlet weak var paceLabel: UILabel!
@IBOutlet weak var distanceLabel: UILabel!

Now add at the action. Control-drag from the button and add an action

@IBAction func startStopButton(_ sender: UIButton) {
}

Some Preliminary Coding

Before we code the pedometer, I’m going to add a few properties and functions that will help us later. Close the assistant editor and go to the ViewController.swift file.

A very critical line goes just under the import UIKit line, where we import core motion.

import CoreMotion

Then add these properties and constants.

    
    let stopColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
    let startColor = UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0)
    // values for the pedometer data
    var numberOfSteps:Int! = nil
    var distance:Double! = nil
    var averagePace:Double! = nil
    var pace:Double! = nil

The constants will change the color of the stat stop button. You’ll store values in these for the properties of the pedometer object. Of course you need a pedometer object. Add this after these properties:

var pedometer = CMPedometer()

Pedometers don’t compute elapsed time. You might want that too in your pedometer. Add these two more properties to use a timer. :

    // timers
    var timer = Timer()
    let timerInterval = 1.0
    var timeElapsed:TimeInterval = 0.0

As you’ll find out, there’s more to this timer than just recording time.

You’ll need some more  methods to handle some unit conversions you’ll do later, so add these:

   //MARK: - Display and time format functions
    
    // convert seconds to hh:mm:ss as a string
    func timeIntervalFormat(interval:TimeInterval)-> String{
        var seconds = Int(interval + 0.5) //round up seconds
        let hours = seconds / 3600
        let minutes = (seconds / 60) % 60
        seconds = seconds % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }
    // convert a pace in meters per second to a string with
    // the metric m/s and the Imperial minutes per mile
    func paceString(title:String,pace:Double) -> String{
        var minPerMile = 0.0
        let factor = 26.8224 //conversion factor
        if pace != 0 {
            minPerMile = factor / pace
        }
        let minutes = Int(minPerMile)
        let seconds = Int(minPerMile * 60) % 60
        return String(format: "%@: %02.2f m/s \n\t\t %02i:%02i min/mi",title,pace,minutes,seconds)
    }
    
func computedAvgPace()-> Double {
    if let distance = self.distance{
        pace = distance / timeElapsed
        return pace
    } else {
        return 0.0
    }
}

func miles(meters:Double)-> Double{
        let mile = 0.000621371192
        return meters * mile
    }

The pedometer works in Metric, returning meters and meters per second. If you want other units such as a distance in miles, or a pace in minute per mile, you’ll need these to calculate the values. I convert times to strings in hh:mm:ss here too.

Coding the Pedometer

With all these functions in place, your’e ready to code the pedometer. You’ll use the startStopButton‘s action to toggle between starting and stopping the pedometer. Add this to the startStopButton code

if sender.titleLabel?.text == "Start"{
    //Start the pedometer
   //Toggle the UI to on state
   statusTitle.text = "Pedometer On"
   sender.setTitle("Stop", for: .normal)
   sender.backgroundColor = stopColor
} else {
   //Stop the pedometer
   //Toggle the UI to off state
   statusTitle.text = "Pedometer Off: "
   sender.backgroundColor = startColor
   sender.setTitle("Start", for: .normal)
}

You have button that toggles and appears correctly on the user interface. Below the Start the pedometer comment add this:

pedometer = CMPedometer()
pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
    if let pedData = pedometerData{
            self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
    } else {
            self.stepsLabel.text = "Steps: Not Available"
    }
})

The first line of this code clears the pedometer by creating a new instance in the pedometer property. The startUpdates:FromDate:withHandler: method starts sending updates from the pedometer. When an update occurs the handler within the closure executes. It checks of the pedometer is nil. If nil, the pedometer is not available. There are several other ways of checking this, but this is the most direct. If there is a value, the pedometer property number of steps is sent to the stepsLabel‘s text property.

To stop a pedometer, add the following under Stop the pedometer comment.

pedometer.stopUpdates()

Run the Application

You are ready to run our first iteration. However, core motion only works on a real device, not the simulator. Plug your phone into a device, and select the device in the run schemes. I’ll assume you know how to do this and how to set up your device for testing. Run the application.
You’ll get a screen like this:

052fa7ca-aa18-46fd-9127-06023afccf80

Press Start. You’ll get a screen asking for permission. Tap OK

112f8982-77fb-40af-b2ef-35842bc3e2f4

Pick up your phone. Move your arms back and forth for 30 seconds like you are jogging. This is one of the motions that the pedometer uses to count steps. Press STOP.

0a9e7fae-7abc-4372-b9b4-311ac9d13e9f

While you see a few steps on the pedometer, you also see a lot of errors in the console Most of them you can ignore — they are errors related to internal caches you can’t touch. The very last error on the other hand is very important:
This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.

What’s happening here? Remember the code for updating is a closure, and runs on a separate thread from the main thread where UI updates happen. The closure tries to access the main thread when it really shouldn’t, because its timing is not the same as the main thread. This can crash the app. While this specifically mentions auto layout, I’d suggest never directly changing outlets from the closure to avoid any bad behavior.

Using Timer Loops

Instead of setting the labels in the closure, you set a property. Change this:

self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"

to this:

self.numberOfSteps = Int(pedData.numberOfSteps)

The pedometer numberOfSteps property is of type NSNumber. You must covert it to an Int to use in the ViewController numberOfSteps property.
You might think you can use a property observer to change the display. For example change the numberOfSteps property to this:

// values for the pedometer data
var numberOfSteps:Int! = nil{
    didSet{
        stepsLabel.text = "Steps:\(numberOfSteps)"
    }
}

When the property changes, the label changes. You can run this code and do a simulated jog with your device. IF you do, you get that error message again. In code this is still updating the pedometer in the handler thread. You need a thread that has no problem updating to the view. That’s a timer.

Comment out the property observer:

// values for the pedometer data
var numberOfSteps:Int! = nil
/*{  //this does not work. 
    didSet{
        stepsLabel.text = "Steps:\(numberOfSteps)"
    }
}*/

We earlier declared some timer properties. You’ll use that to set up a timer with these functions:

    //MARK: - timer functions
        func startTimer(){
        if timer.isValid { timer.invalidate() }
        timer = Timer.scheduledTimer(timeInterval: timerInterval,target: self,selector: #selector(timerAction(timer:)) ,userInfo: nil,repeats: true)
    }
    
    func stopTimer(){
        timer.invalidate()
        displayPedometerData()
    }
    
    func timerAction(timer:Timer){
        displayPedometerData()
    }

I discuss timers in more detail here. Basically startTimer starts a timer with a 1 second interval that repeats. I’m being course here, you can set the interval to a finer resolution if you wish. Every second it calls the function timerAction from the selector. In timerAction I call a function displayPedometerData I’ve yet to define which will display my pedometer data. The stopTimer function shuts down the timer and updates the display one last time. Add the startTimer and stopTimer functions to the button action so it starts and stop the timer when the pedometer starts and stops.

@IBAction func startStopButton(_ sender: UIButton) {
        if sender.titleLabel?.text == "Start"{
            //Start the pedometer
            pedometer = CMPedometer()
            startTimer() //start the timer
            pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
                if let pedData = pedometerData{
                    self.numberOfSteps = Int(pedData.numberOfSteps)
                    //self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
                } else {
                    self.stepsLabel.text = "Steps: Not Available"
                }
            })
            //Toggle the UI to on state
            statusTitle.text = "Pedometer On"
            sender.setTitle("Stop", for: .normal)
            sender.backgroundColor = stopColor
        } else {
            //Stop the pedometer
            pedometer.stopUpdates()
            stopTimer() // stop the timer
            //Toggle the UI to off state
            statusTitle.text = "Pedometer Off: "
            sender.backgroundColor = startColor
            sender.setTitle("Start", for: .normal)
        }
    }

Create a new function to update the view. For now we’ll update just the steps again.

func displayPedometerData(){  
    //Number of steps
    if let numberOfSteps = self.numberOfSteps{
        stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
     }
}

I did two more things than I did with the property observer. I used an optional chain to unwrap numberOfSteps, and then used as String initializer to format the string.
If you run the application and do your little in-place run, you’ll notice two differences: the step count updates faster than before and the error message disappears. We only have that CoreLocation cache warning on the console. The timer thread indirectly updated the display separating the pedometer thread from the main thread.

Adding Elapsed Time

One advantage to a timer loop is we have a timer. Usually I use a higher resolution timer(0.1 seconds for example), but for this lesson I’ll leave it at a one second interval.

I can change the displayPedometerData function to this:

func displayPedometerData(){
    //Time Elapsed
    timeElapsed += self.timerInterval
    statusTitle.text = "On: " + timeIntervalFormat(interval: timeElapsed)
        //Number of steps
        if let numberOfSteps = self.numberOfSteps{
            stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
        }
    }

I increment the pedometer with a property timeElapsed I created earlier. It keeps a count of the number of seconds elapsed since I started the pedometer. I display it using one of the formatting functions we added earlier that displays the time as hh:mm:ss.

To keep this time after you stop the timer, append the timeIntervalFormat function to the status title label

statusTitle.text = "Pedometer Off: " + timeIntervalFormat(interval: timeElapsed)

Build and run. Start the pedometer. You’ll get both a timer and step count now.

9872d97b-a238-4012-8597-a74b3732a053

Stop the pedometer.

Adding Other Pedometer Properties

There’s several other properties of pedometers. I selected three more to show on our pedometer: current pace, Average pace and distance. Why you are getting that core location cache message makes sense now: the Pedometer checks your location repeatedly using CoreLocation. You have no control over that which is why I said to ignore the warning message. With that location data, the pedometer computes distance, and from the steps, distance, and it’s own timer pace and average pace.

All of these properties are optional. If the hardware or property is unavailable or nonexistent, the property returns nil. However if you can’t get pace from a pedometer, you can compute the average pace from the distance and time. I made a function earlier computedAvgPace that will compute an average pace or leave it as 0 if core location is not available.

To implement the other properties, change the startUpdates closure to add the pedometer data to the viewController’s properties:

  pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
                if let pedData = pedometerData{
                    self.numberOfSteps = Int(pedData.numberOfSteps)
                    //self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
                    if let distance = pedData.distance{
                        self.distance = Double(distance)
                    }
                    if let averageActivePace = pedData.averageActivePace {
                        self.averagePace = Double(averageActivePace)
                    }
                    if let currentPace = pedData.currentPace {
                        self.pace = Double(currentPace)
                    }
                } else {
                    self.numberOfSteps = nil
                }
            })

Each pedometer property, if a number, is converted from NSNumber! to a Double for use in the classes, like we did for the integer numberOfSteps.

In the displayPedometerData function, change it to this to include the other properties:

 func displayPedometerData(){
        timeElapsed += 1.0
        statusTitle.text = "On: " + timeIntervalFormat(interval: timeElapsed)
        //Number of steps
        if let numberOfSteps = self.numberOfSteps{
            stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
        }
        
        //distance
        if let distance = self.distance{
            distanceLabel.text = String(format:"Distance: %02.02f meters,\n %02.02f mi",distance,miles(meters: distance))
        } else {
            distanceLabel.text = "Distance: N/A"
        }
        
        //average pace
        if let averagePace = self.averagePace{
            avgPaceLabel.text = paceString(title: "Avg Pace", pace: averagePace)
        } else {
            avgPaceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        } 
        
        //pace
        if let pace = self.pace {
            paceLabel.text = paceString(title: "Pace:", pace: pace)
        } else {
            paceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        }
    }

For each of these properties we optionally chain the property. A nil vaule shows there was no reading for some reason. For the two pace strings, if we do not get a pace, I calculate the pace from the distance. I use the paceString function defined in the conversion functions to make a string of both meters per second and minutes per mile.

Run again, and start the pedometer. Make the running motion and the device will begin to display data.

6bbd7e99-1d47-4fb3-a8f1-cd886ebfc705

The Main Points for Core Motion

Core motion has a lot more to it, but this introduction give you some of the basics all core motion methods use. THis one is high level, the lower level, closer to the sensors require more tweaking than this virtually automatic method. However there are several points you should remember with Core Motion:

  • Add the entry to the info.plist for security permissions
  • Include the Core Motion LIbrary
  • Check for availability of the device and the functions by looking for nil on properties.
  • Don’t directly update outlets from a core motion closure
  • Do indirectly update outlets from a timer loop.

The Whole Code

You’ll find all but the info.plist entry in this code. if you run this, make sure to include that. There is a download file  coremotionpedometer with the completed project, including some app icons if you want to test off of Xcode. Go ahead, run a mile. It’s good for you.

ViewController.swift

//
//  ViewController.swift
//  CoreMotionPedometer
//
//  Created by Steven Lipton on 2/10/17.
//  Copyright © 2017 Steven Lipton. All rights reserved.
//

import UIKit
import CoreMotion

class ViewController: UIViewController {

//MARK: - Properties and Constants
    let stopColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
    let startColor = UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0)
    // values for the pedometer data
    var numberOfSteps:Int! = nil
    /*{ //this does not work. for demo purposes only.
        didSet{
            stepsLabel.text = "Steps:\(numberOfSteps)"
        }
    }*/
    var distance:Double! = nil
    var averagePace:Double! = nil
    var pace:Double! = nil
    
    //the pedometer
    var pedometer = CMPedometer()
    
    // timers
    var timer = Timer()
    var timerInterval = 1.0
    var timeElapsed:TimeInterval = 1.0
    

    
//MARK: - Outlets
    
    @IBOutlet weak var statusTitle: UILabel!
    @IBOutlet weak var stepsLabel: UILabel!
    @IBOutlet weak var avgPaceLabel: UILabel!
    @IBOutlet weak var paceLabel: UILabel!
    @IBOutlet weak var distanceLabel: UILabel!
    
    @IBAction func startStopButton(_ sender: UIButton) {
        if sender.titleLabel?.text == "Start"{
            //Start the pedometer
            pedometer = CMPedometer()
            startTimer()
            pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
                if let pedData = pedometerData{
                    self.numberOfSteps = Int(pedData.numberOfSteps)
                    //self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
                    if let distance = pedData.distance{
                        self.distance = Double(distance)
                    }
                    if let averageActivePace = pedData.averageActivePace {
                        self.averagePace = Double(averageActivePace)
                    }
                    if let currentPace = pedData.currentPace {
                        self.pace = Double(currentPace)
                    }
                } else {
                    self.numberOfSteps = nil
                }
            })
            //Toggle the UI to on state
            statusTitle.text = "Pedometer On"
            sender.setTitle("Stop", for: .normal)
            sender.backgroundColor = stopColor
        } else {
            //Stop the pedometer
            pedometer.stopUpdates()
            stopTimer()
            //Toggle the UI to off state
            statusTitle.text = "Pedometer Off: " + timeIntervalFormat(interval: timeElapsed)
            sender.backgroundColor = startColor
            sender.setTitle("Start", for: .normal)
        }
    }
    //MARK: - timer functions
    func startTimer(){
        if timer.isValid { timer.invalidate() }
        timer = Timer.scheduledTimer(timeInterval: timerInterval,target: self,selector: #selector(timerAction(timer:)) ,userInfo: nil,repeats: true)
    }
    
    func stopTimer(){
        timer.invalidate()
        displayPedometerData()
    }
    
    func timerAction(timer:Timer){
        displayPedometerData()
    }
    // display the updated data
    func displayPedometerData(){
        timeElapsed += 1.0
        statusTitle.text = "On: " + timeIntervalFormat(interval: timeElapsed)
        //Number of steps
        if let numberOfSteps = self.numberOfSteps{
            stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
        }
        
        //distance
        if let distance = self.distance{
            distanceLabel.text = String(format:"Distance: %02.02f meters,\n %02.02f mi",distance,miles(meters: distance))
        } else {
            distanceLabel.text = "Distance: N/A"
        }
        
        //average pace
        if let averagePace = self.averagePace{
            avgPaceLabel.text = paceString(title: "Avg Pace", pace: averagePace)
        } else {
            avgPaceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        }
        
        //pace
        if let pace = self.pace {
            print(pace)
            paceLabel.text = paceString(title: "Pace:", pace: pace)
        } else {
            paceLabel.text = "Pace: N/A "
            paceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        }
    }
    
    //MARK: - Display and time format functions
    
    // convert seconds to hh:mm:ss as a string
    func timeIntervalFormat(interval:TimeInterval)-> String{
        var seconds = Int(interval + 0.5) //round up seconds
        let hours = seconds / 3600
        let minutes = (seconds / 60) % 60
        seconds = seconds % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }
    // convert a pace in meters per second to a string with
    // the metric m/s and the Imperial minutes per mile
    func paceString(title:String,pace:Double) -> String{
        var minPerMile = 0.0
        let factor = 26.8224 //conversion factor
        if pace != 0 {
            minPerMile = factor / pace
        }
        let minutes = Int(minPerMile)
        let seconds = Int(minPerMile * 60) % 60
        return String(format: "%@: %02.2f m/s \n\t\t %02i:%02i min/mi",title,pace,minutes,seconds)
    }
    
    func computedAvgPace()-> Double {
        if let distance = self.distance{
            pace = distance / timeElapsed
            return pace
        } else {
            return 0.0
        }
    }
    
    func miles(meters:Double)-> Double{
        let mile = 0.000621371192
        return meters * mile
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

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


}


Why do we need Delegates in iOS and WatchOS?

About two years ago someone asked me a very good question: Why do we need delegates for UIViewControllers?  He thought Swift  made things easier, but this delegate stuff seems very complicated. Shouldn’t we be able to send a message or initializer between classes?

When I first learned iOS, I’ll admit it took me months to understand what happened with delegation. I found lots of confusing code and little explanation. AS I was researching some more preferences to send people to, I found the results lacking. Most often, tutorials refer to how to use an Apple factory delegate, not making your own callback. Its these callbacks which require full knowledge of delegates.

I decided it was time to update this, and to include two examples developers might run into: the iOS and watchOS versions. With the maturing of watchOS in watchOS 3 I think more developers might begin to look at developing watch apps, and there’s some twists there that might cause some confusion.

Let’s start at the beginning, so everyone understands the problem.

What is a Class?

Let’s start at the beginning, so everyone understands the problem. While we use classes in object-oriented programming, it’s good to review what they actually are. A class is a collection of data, which we call properties, and actions we can do to those properties, which we call methods.

Properties and methods are either public or private. A public method is one that classes other than the defining class can see and can use. Private means that the property or method is only usable and visible within the defining class. Other classes cannot see or use it. In Swift the private keyword makes properties and methods private. Swift’s calculated properties feature is another way to make properties private. In Swift, there is also a default state which makes a method or class public to the current target, but not other targets.

We tend not to like other people messing with our insides, and that is a good programming practice too. In general, it is best to leave what is public by other classes to a necessary minimum. Keeping properties and methods private and not exposing all of our class is known as encapsulation.

Encapsulation allows us to make code as modular as building blocks. Just as a few stubs come out of an otherwise standard sized brick, only a few usable methods come out of a class. Then they can attach to a lot of other bricks.

What is  Model –  View – Controller  or MVC?

MVC schematic blankA term heard often  when working with Xcode, is MVC. MVC stands for Model-View-Controller. It is not an exclusive term to Xcode projects. It is a pattern of programming, a good organization of any program or application in a graphics-rich environment, and arguably any environment that interacts with the user. MVC separates the major parts of an application. First it separates the data and the user interaction then adds an intermediary between them. Why is this important? You might write and publish an application for an iPhone, then decide an iPad version would be a good idea, then decide to make a watch version. With MVC, you only change one part completely, the view and possibly some of the controller. The code handling your data never changes between the versions saving a lot of time and effort.

What is a Model?

MVC schematic modelThere are parts of our program that deal with information we want to process. A pizza ordering system has a list of data giving us information about each person’s order. There may be other links to that data with more data about each customer, and about each pizza. In a pizza ordering system this is our model: the collection of all the data we will use in our ordering system. It does not in any way interact with the user. It does not display anything or does it ask for input. It is just data. Here is an example of very simple model:

class switchState{
    var state:Bool
    func textState()->String{
        if state {
            return "On"
        } 
        return "Off"
    }
    func overLoadSwitch(users:Int,load:Int){
      let totalLoad = users * load 
      if totalLoad > 100{
          switch = false
      }
}

This model is the state of a switch.  That’s the data. the model has two methods. textState(), describes the state of the switch as a string,  overLoadSwitch() turns off the switch if users multiplied by load is greater than 100 . There is a lot more methods I should add to describe the switch, but any method is changing or describing data only. There is no user input or output here. Models might make calculations but again there is no user interaction here.

What is a View?

MVC schematic viewWhere all the user interaction happens is in the view. In Xcode, most people use Interface Builder either as a scene in a storyboard or a .xib file to build their views.  A developer can programmatically create a view class to hold the different controls.

2016-08-01_07-11-48As the model never interacts with the user,  the view never interacts directly with the data. The view doesn’t do much but sit there. It might respond to a user touch with feedback such as a notifying a method somewhere, a color change when a button gets tapped or a scrolling motion at times, but that is all it does. The view does contain a lot of properties and methods and that tell us the state of the view. We can change the appearance and behavior of the view through methods and properties. The view can tell the controller that there was a change in the view, such as a button getting pressed, or a character typed. it can’t do anything about it, but it can broadcast something.

What is a Controller?

MVC schematicThe heart of MVC connects these two. Called the controller or view controller, it coordinates what happens in the model and what happens in the view. If a user presses a button on the view, the controller responds to that event. If that response means sending messages to the model, the view controller does that. If the response requires getting information from the model, the controller does that too. in Xcode, @IBOutlet and @IBAction connect Interface Builder files containing views to the view controller.

The key to MVC is communication. To be more accurate, the lack of communication. MVC takes encapsulation very seriously.  The view and the model never directly talk to each other. The controller can send messages to the view and the controller. The view and controller may do an internal action to the message sent as a method call or it may return a value to the controller. The controller never directly changes anything in either the view or the model.

So to summarize, a view, a model and a controller cannot directly change a property in each other. A view and a model may not talk to each other at all. A view can tell the controller there is a change in the model. A controller can send messages in the form of method calls to the view and the model and get the responses back through that method.

MVC schematic full annotated

How Does MVC Connect to Other MVC’s

What we’ve discussed so far is for only one scene in a much larger application. Suppose I have the following  watchOS storyboard:

2016-09-21_05-01-58

I have an a button Switch which loads a second face that has a switch.  When I decide which way I want the switch, I press done.

The Easy Direction

In Xcode we have what are known as segues. Segues are a convenience to point from one view controller to another.  When segues keep track of some things that would become cumbersome to control ourselves in a simple way. When we move from one controller to the next, the segue tells the system to open this particular view controller, which then opens up a view and model. The model and view in the new MVC setup is different then the calling one. Apple includes a method prepare(for segue:) for  iOS which give us a chance to set values in the new view controller,  and subsequently the new view controller’s view and model.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "switch"{
         let vc = segue.destination as! SwitchViewController
         vc.switchState = false
        }

With the introduction of WatchOS came a slightly different approach.  instead of having the full model available,  watchOS sends to new controllers a single value called context. As this is type Any?,  you can put whatever you want in this value. Most often developers send dictionaries of values to other controllers through the contextForSegue method. When the destination application wakes up. the awake method  converts the context to the proper type and assigns it correctly.

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
        return self
    }

For both a identical  phone or watch app I could press the Switch button.  It launches  the switch face and  pass a value to the switch to be false.

The Problem Direction

We can turn the switch off and on easily enough.  But when we press Done to send it back to the original controller  is when problems show up. By the rules of MVC, we need a method to return a value. Where in a called instance can we go back to the class calling it? With an encapsulated class we can’t. There is no way to send that revised model back to the original controller without breaking encapsulation or MVC. The new view controller does not know anything about the class that called it. We look stuck. If we try to make a reference directly to the calling controller, we may cause a reference loop that will kill our memory. Simply put, we can’t send things backwards.

This is the problem that delegates and protocols solve by being a little sneaky. Imagine another class, one that is really a skeleton of a class. This class contains only methods. It declares that certain methods are in this class, but never implements them. In Swift they are protocols. We make a protocol class that has one method. That method is what you do when you are done  with the switch, and want to go back to the calling controller. It has a few parameters, things you want to pass back to the calling view controller.  So it might look like this:

protocol SwitchDelegate {
    func didFinishSwitch(switchState:Bool)
}

I passed back the state of the switch in this case.

In the controller with the switch, we make an instance of this protocol, calling it delegate.

delegate:SwitchDelegate! = nil

Since we have a property of type SwitchDelegate, we can use the methods of the SwitchDelegate type, In our example, that is our method didFinishSwitch. We can stick that method call in a action for a Done button:

@IBAction func doneButtonPressed(sender:UIButton!){
    delegate.didFinishSwitch(switchState: switchState)
    dismiss(animated: true, completion: nil)
}

or for WatchOS

@IBAction func submitSwitchStatus() {
    delegate.didFinishSwitch(switchState: switchState)
    pop()      
}

Since protocols are skeletons, it means any other class can adopt them. A class makes the protocol methods part of its own class with a stipulation. As soon as a protocol gets adopted, you need to flesh out the skeleton. The developer has to code the required methods in the adopted class’ code. We adopt a protocol by placing it after the name of the class and superclass. For iOS you might have

class OrderPizzaViewController:UIViewController,PizzaEditDelegate

and for watchOS, you might have

class InterfaceController: WKInterfaceController,SwitchDelegate {

As soon as you do that, you will get a compiler error since the protocol’s method does not exist in the class. In the code for the adopting class, in our example OrderPizzaViewController, we would implement the method

func didFinishSwitch(switchState: Bool) {
        if switchState {
            textState = "Switch is On"
        } else {
            textState = "Switch is Off"
        }        
    }

We get the data back, and what we need with it, in this case a string that we’ll print to the label.
One more step. While back in the destination controller, I said the delegate was an instance of the protocol, I didn’t say where the delegate was. In prepare(for Segue) I add one more line vc.delegate = self saying the protocol is your controller

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "switch"{
         let vc = segue.destination as! SwitchViewController
         vc.switchState = false
         vc.delegate = self
        }

In WatchOS this gets a bit trickier. I have only one context to pass both the switchState and the delegate. For multiple values generally developers use a dictionary like this:

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
        let context:[String:Any] = ["switchState":false,"delegate":self]
        return context
    }

the awake method would have code to unwrap the dictionary and assign the values.

When we tap Done in a watch or phone application, the method runs, it knows it is located in the calling controller, and calls it there where we added to the original class.The data is a parameter so the program can easily transfer into the controller and to the model. Delegates and protocols are bit sneaky but it works, and is one of the most important techniques when working with view controllers.

mvc-schematic

The original question asked why didn’t Swift make this simpler. As I hope I’ve shown here with a Swift context, it doesn’t matter what object oriented language you use. Delegates are part of the MVC pattern, which is a good programming practice.

Tables and Scroll Views in WatchOS2

One of the most powerful and important controls on both wearable and mobile devices are table views. Table views come in two flavors: static and dynamic. Dynamic table views read data from a collection type and displays it. 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, WatchOS goes for the simple route that we don’t get in iOS. In iOS’s UIKit we also have the more free-form scroll view. In WatchOS’s WatchKit, scroll views and Static table views are the same thing. They are created on the storyboard, while dynamic table views have some code but simplify the process compared to UIKit. In this lesson we’ll make scrolling behavior in the Apple Watch with tables and scroll views.

Make a New Project

Make a new project using the WatchOS application template iOS App with WatchOS App Name the project SwiftWatchKitTable, with Swift as the language and Universal for the device, clicking off Include Notification Scene. Save the project. Start your watch and phone simulators to get them ready to run.

Add Your First Controls

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

2016-03-17_06-04-23

Drag another 2 buttons, so we run out of room on the scene. Label them Button 2, and Button 3

2016-03-17_06-09-10

Break the Barrier

We’ve run out of space to put controls. Put another button, labeled Button 4 under Button 3. The scene stretches to include the button

2016-03-17_06-11-04

Set your simulator for a 38mm watch app.

2016-03-17_06-14-21

Build and run. On a 38mm watch the Button 4 slips slightly out of view

2016-03-17_06-21-32

Stop the watch app. Change to a 42mm watch face in the simulator. On a 42mm watch, the interface fits

2016-03-17_06-25-29

Add three more buttons to the scene by copy and paste. Select Button 4, press Command-C to copy. Press Command-V three times to makes three more buttons. Label them accordingly. The Interface continues to grow.

2016-03-17_06-29-16

Build and run again with the 44mm simulator. We start with the same controls as before, though we can see the edge of another button.

2016-03-17_06-34-18

In the watch simulator, click and drag up 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.

2016-03-17_06-36-19

Add Separators and Labels

Scroll views and static table views are the same thing. Unlike iOS, there is no horizontal scroll, only vertical. To make it look more like a table view, you can add a few decorations to the interface. Find the separator object in the object library.

2016-03-17_06-40-05

Drag separators above and below the Button 3 like this:

2016-03-17_06-40-56

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

2016-03-17_06-44-32

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

2016-03-17_06-48-12

Adding Groups

If you need to set up true sections, you can add groups as well. Below Button 2 add a group. Make the background Medium Green(#008000)

2016-03-17_07-02-45

Change the layout from Horizontal to Vertical

2015-07-22_06-21-32

Add a label with the text My Group, a date control and a switch to the group.

2016-03-17_07-02-46

Build and Run. Scroll down to see the group.

2016-03-17_07-07-59

Dynamic Table Views

If you have controls that don’t change, using the storyboard is the best way to have a series of controls. In the vertical, you are not limited by size as the watch automatically scrolls to accommodate your controls.

If you need to show a list of data on your watch, you will use a dynaimic table view. Dynamic table views follow this pattern. Instead of a special controller like UITableViewController in iOS, it is a control you place on the interface. You may be delighted to know that WKInterfaceTable is a lot simpler to put together than a UITableViewController: there are no delegate or data sources involved. It’s a very different experience than the iOS equivalent. It’s shrinks to one method you need to code. Since it is a control, you can easily mix other controls on the same interface as the table.

Modify the Project

On the Interface.storyboard, delete everything but two of the buttons. Make the button backgrounds Medium Blue(#8080FF). Title the top button Bottom and the bottom button Top

2016-03-17_07-28-43

Add a Table object

In the object catalog, find the table object.

2015-07-30_05-26-25

Drag the table object to the interface in the storyboard, inserting it between the two buttons. A group called Table Row appears.

2016-03-17_07-31-48

Open the document outline to look at the structure of a table object:

2016-03-17_07-32-42

A table has one or more table row controllers. In each row controller is a group for adding controls. Most typically labels or images, but we can add buttons.

Tables can have more than one row controller. You may have one row controller for the data, one for a header and one for a footer . In this lesson, we’ll use only one. In the next lesson on advanced tables, we’ll work with multiple row controllers.

Click on the Table Row Controller in the document outline. Row controllers need unique names in the table. In the attribute inspector, make the identifier row

2015-07-30_05-39-09

Click the group for the row controller. By default, this group is horizontal. For our app, set the Layout to Vertical. Set the Height Size to Size to Fit Content. Drag two labels into the group. Title them like this:

2016-03-17_07-38-10

Make a Row Controller

Our next step in building a table is making a table row controller. Tap Command-N to make a new file. Create a new WacthOS Class named TableRowController. Subclass NSObject for this file. Save the file in the WatchKit Extension Group. Be careful that Xcode does not default you to the iOS app.

2016-03-17_07-46-18

Row controllers don’t do a lot. Typically they contain the outlets for the controls in the row controller. In our example, add two outlets for WKInterfaceLabel, splits and time.

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

You can add actions, but for our basic example we’ll stick to just outlets. We’ll discuss actions in row controllers in the next part of the series.

Connect a Row Controller

Now we connect our row controller to the table. Go to the storyboard. Click on the row controller named row in the document outline. In the identity inspector, change the Class to TableRowController.

2015-07-30_06-07-07

Now we open the assistant editor. Xcode assumes you want to work with the interface. Most likely you will see the InterfaceController.swift file in the assistant editor. Click at the top where it says Automatic and select Manual. Select through the choices to get the Watchkit Extension file TableRowController.swift.

2016-03-17_07-52-02

From the circles to the left of the outlets, connect the outlets to the controls on the storyboard:

2016-03-17_07-55-11

We’ve now connected the row controller to the storyboard. You can close the assistant editor to make room.

Add a Model

Like UITableView, the typical data for a WKInterfaceTable is an array. I’m making a simple constant array for this of a running pace measured in seconds/mile. Go to the InterfaceController.swift file. Add this to the InterfaceController class.

    var data = [654,862,860,802,774,716,892,775,748,886,835]

Pace data as an Int is not very user-friendly. Since I’m using an Int for my data and not NSTimeInterval I can’t use time formatters. There is an easy solution the make a string to display the data we need. Add the following function to the InterfaceController Class:

    func paceSeconds(pace:Int) -> String{
        let hours = pace / 3600
        let remainingSeconds = pace % 3600
        let minutes = remainingSeconds / 60
        let seconds = pace % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }

Since I’m using integers, dividing by 3600 seconds give me hours. Taking the mod of 3600 gives me the seconds less than one hour. Dividing by 60 seconds in a minute gives me the number of minutes. Taking the mod of 60 seconds give me the remaining seconds. I just format that in a string and am done.

Implement the Table

Select the storyboard, open up the assistant editor again, and set back to Automatic to get the InterfaceController.swift file in the assistant editor. Control-drag from the Table control in the document outline to the code to make an outlet. Label the outlet table.

 @IBOutlet weak var table: WKInterfaceTable!

Close the assistant editor, and select the interface controller. We will write one method to load and refresh the table. Start by making the method in InterfaceController.

func tableRefresh(){
}

Watchkit has no delegates and data sources for tables. Everything happens in this method. Instead of a data source, we tell the table how many rows there are in the table, and what row controllers to use with one of two methods in WKInterfaceTable. In the simple cases of one row controller, we use setNumberOfRows(rows:, withRowType:). Add this to the tableRefresh method.

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

The parameter rows is the number of rows in the table. We count the elements in the array with data.count. Row types are the type of row controller we used. That is the identifying string for the row controller we set earlier named row.

We’ve now made an empty table. We’ll make a loop to populate the table. Add this to the code.

for index in 0 ..< table.numberOfRows {

}

This will loop through all the elements in the array. We’ll use index to reference a row on a table. We need index as a data point too — it’s the number of miles run.
In our loop, we get the row from the table. Add this code to the loop

let row = table.rowControllerAtIndex(index) as! TableRowController

We get the row at the current index, then cast it correctly to a TableViewController. Populate the row controller row with data:

let rowString = String(format: "Split:%02i miles", index + 1)
let paceString = "Pace:" + paceSeconds(data[index])
row.splits.setText(rowString)
row.time.setText(paceString)

Our full method should look like this

func tableRefresh(){
        table.setNumberOfRows(data.count, withRowType: "row")
        for index in 0 ..< table.numberOfRows {
            let row = table.rowControllerAtIndex(index) as! TableRowController
            let rowString = String(format: "Split:%02i miles", index + 1)
            let paceString = "Pace:" + paceSeconds(data[index])
            row.splits.setText(rowString)
            row.time.setText(paceString)
        }
    }

Finally, call the tableRefresh method when we start the watch app. Change willActivate() to

override func willActivate() {
        super.willActivate()
        tableRefresh()
    }

It’s a good practice to refresh the interface at the very last moment before activation to keep it accurate. That’s why we use willActivate instead of awakeWithContext.
Build and run. You should have a working table.

2016-03-17_08-18-34

The Bottom button is at the top of the table. Scroll down to the end:

2016-03-17_08-18-51

A Few Changes

There’s some labeling changes we need to make. We should use mile instead of miles in the first table entry. We also have a problem with the last entry in the table. Races and runs rarely end exactly at a mile marker. Most races are uneven miles (3.1, 6.2, 13.1, 26,2). It is likely we will have a fraction of a mile as the last entry in the table. Given we do not have data for exactly how far we’ve run, we change rowString to Finish instead. Add this to the code for the loop, above the setText method calls

if index == (table.numberOfRows - 1){ //table end
    rowString = "Finish"
}
if index == 0 {
    rowString = "Split:01 mile" //Table beginning
}

Change rowString from let to var

var rowString = String(format: "Split:%02i miles", index + 1)

Build and run. At the top we have mile.

2016-03-18_05-54-27

At the bottom, we have Finish.

2016-03-18_05-54-36

Scroll to a Row

You can programmatically scroll to a row. In our app, we might want to see the finish instead of the start first. We might have a lot of data and want to get to the top quickly. The method scrollToRowAtIndex does this. We’ll take the two buttons we added and make them table navigation.

Go to Interface.storyboard and open up the assistant editor, set to Automatic so you see the InterfaceController class in the assistant window. Control-drag from the Bottom button to the code. Make an action toBottomAction. Control-drag from the Top button to the code. Make an action toTopAction. Add this code to the actions:

@IBAction func toBottomAction() {
    table.scrollToRowAtIndex(table.numberOfRows - 1)
}
    
@IBAction func toTopAction() {
     table.scrollToRowAtIndex(0)
}

The code table.scrollToRowAtIndex(0) scrolls us to the top of the table, which is index 0. Since rows begin with 0, we subtract 1 from the numberOfRows property of the table.
Build and Run.Tap the bottom button. We go to the last entry.

2016-03-18_06-22-41

Scroll up a bit to get the top button. Tap the button. We go to the first entry.

2016-03-18_06-22-55

You have a working table in WatchKit. It’s not very interactive or advanced. We have yet to add navigation or actions to table rows. While we can easily add headers and footers to the table as controls before or after the table, we don’t have and subheadings or subtotals. We might want different rows for different data. We’d also like to add and delete rows. In the next tutorial we’ll cover advanced table controller topics in WatchOS.

The Whole Code

InterfaceController.swift

//
//  InterfaceController.swift
//  SwiftWatchKitTable WatchKit Extension
//
//  Created by Steven Lipton on 3/17/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {
    var data = [654,862,860,802,774,716,892,775,748,886,835]
    
    @IBOutlet var table: WKInterfaceTable!
    func paceSeconds(pace:Int) -> String{
        let hours = pace / 3600
        let remainingSeconds = pace % 3600
        let minutes = remainingSeconds / 60
        let seconds = pace % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }
    
    @IBAction func toBottomAction() {
        table.scrollToRowAtIndex(table.numberOfRows - 1)
    }
    
    @IBAction func toTopAction() {
        table.scrollToRowAtIndex(0)
    }
    
    func tableRefresh(){
        table.setNumberOfRows(data.count, withRowType: "row")
        for index in 0 ..< table.numberOfRows {
            let row = table.rowControllerAtIndex(index) as! TableRowController
            var rowString = String(format: "Split:%02i miles", index + 1)
            let paceString = "Pace:" + paceSeconds(data[index])
            if index == (table.numberOfRows - 1){ //table end
                rowString = "Finish"
            }
            if index == 0 {
                rowString = "Split:01 mile" //Table beginning
            }
            row.splits.setText(rowString)
            row.time.setText(paceString)
        }
    }
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        
        // Configure interface objects here.
    }

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

}

TableRowController.swift

//
//  TableRowController.swift
//  SwiftWatchKitTable
//
//  Created by Steven Lipton on 3/17/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit

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

Using WatchOS2 Navigation in Swift

It’s rare to have a one controller application, even in something as small as the Apple watch. Multiple View Controllers, or Interface Controllers as they are called in WatchKit, need ways to move between controllers. WatchOS2 has a simplified version of the iOS navigation types. In this lesson we’ll explore these three types of navigation that are available for WatchOS: Page, Hierarchical, and Modal. You’ll find them summarized on this chart below

2016-03-04_12-03-25

In this tutorial we’ll show all three programmatically and on the storyboard.

Set Up the Project

Before starting the project, start the watch and phone simulators. If you do not have direct access to them, you might want to read the Before you Begin section in a earlier post.

Open Xcode if not already open. Create a new project and pick the iOS App with Watchkit App Watch project template.

2016-02-03_05-34-40

You’ll get a configuration window like this:

2016-03-02_06-25-53

Name the project WatchNavigationDemo using Swift as the language. Use a Universal device and uncheck Include Notification Scene as shown above. In the Navigator find these files:

2016-03-02_06-29-30

The Root Controller

Click on the Interface.storyboard in the WatchNavigationDemo WatchKit App group. You will get a blank watch scene with a watch controller and a notifications controller. Drag two buttons and a label to the controller. Set the background color to the system Dark Gray Color(#555555) for both buttons. In the attributes inspector, set one button’s alignment to Top Vertical Alignment, and the other button’s alignment to Bottom Vertical Alignment. Set the label’s text to Root and the font to Headline. Set the label’s vertical and horizontal alignment to Center.

2016-03-02_06-37-05

Click the view controller icon. At the top of the attribute inspector, add a Title and Identity of Root

2016-03-02_06-42-12

For each of our controllers we will add and identity and title. Title will set the title on the watch face. The Identifier names this controller programmatically. If you are only using segues, then you do not need it, but it is mandatory if you are programmatically calling your controllers. If you have neither a segue nor identifier you will get a warning error from Xcode that the controller is unreachable. With our controller done, it should look like this:

2016-03-01_06-11-01

The Page Controllers

Drag another interface controller to the storyboard. Change the background to Green(#AACC00). Add a button and a label. Set the vertical alignment for the button to Bottom. Set the background color to the system Dark Gray Color(#555555). Select the controller and change the Title and Identifier to Page 0. Set the label’s text to Page 0. Set the label’s vertical and horizontal alignment to Center.

Your controller should look like this:

2016-03-01_06-12-42

Select the interface controller icon, then press Command-C to copy the controller. Deselect the controller by clicking on the background of the storyboard. Press Command-V to paste a copy of the controller. This pastes the controller directly on top of the original. It looks like the original controller is selected again. Drag this controller and you will find it is a copy.

2016-03-02_07-09-40

Deselect, and press Command-V again. You now have three Page 0 controllers. Change the Identifier and Title of one copy to Page 1 and the other to Page 2. Arrange the controllers like this

2016-03-02_07-16-45

The Modal Controllers

Add two more controllers, which we’ll use as our modal controllers. Make one controller have a Red(#FF0000) background and the other a Blue(#0000FF) background. Set the Title and Identifier of the red modal to RedModal. Set the Title and Identifier of the blue modal to BlueModal.

2016-03-01_06-10-36

Arrange your controllers like this:

2016-03-02_07-32-44

Page Navigation Through Segues

Page navigation is similar to iOS page navigation  with pages sliding horizontally with a swipe. It is much simpler to set up than the iOS equivalent. All the glances on an apple watch are in a page sequence. If you don’t have a watch you can still see a quick example in the watch simulator. You can swipe up from the watch face to get to glances. Swipe left and right to see two glances.

Like most storyboard transitions between controllers we use segues. For page segues, We control-drag from one controller to another controller, then repeat to make the chain of controllers. Control-drag from the black background of Root to the green of Page 0. When you release the button, a Relationship Segue menu appears:

2016-03-01_06-18-24

Select Next Page. A segue appears.

2016-03-02_08-02-00

Control-drag from Page 0 to Page 1, repeating the process. Then do it again from Page 1 to Page 2. We have four pages connected together.

2016-03-02_08-06-51

Build and run. You have four pages to scroll back and forth through.

page navigation

Hierarchy Navigation by Segue

Like navigation controllers in iOS, hierarchy navigation pushes the next controller to view through a selection device, such as a button. As the name implies you can branch in as any direction you want, unlike the linear page navigation. The settings app on the Apple Watch is an example of a hierarchy navigation you can see on both the watch and the simulator.

Delete all the segues on the storyboard. Select the top button on the root controller. Control-drag from the top button to the Page 0 controller. When you release the mouse button, you will get this menu:

2016-03-01_06-13-40

Select Push. A segue appears between the controllers.

2016-03-03_05-31-22

The process is almost the same as the page segues. The difference is you start your drag from a control, usually a button but it may be a group or table. Make a segue between the bottom button of Root and Page 1. Control drag from the bottom button of Root to Page 1. Select Push for the Action Segue type.

Page and Hierarchy are Mutually Exclusive

Control-drag from Page 1 to Page 2 and make a page segue. Your storyboard should look like this.

2016-03-03_05-46-53

Xcode does not put any warnings up, but what we just did is wrong. You cannot combine page and hierarchy segues. You can only use one or the other on the storyboard. With one exception, once you use one type or segue or the other. You are stuck in the project using that type of navigation only. However, nothing tells you this except the documentation. Clean the project with a Shift-Command-K, then Build and Run. Tap the top button, and you get Page 0.

At the top left of the watch is a back indicator which you tap to go back to root. If you try going to page 1 with the bottom button, there is no page indicator on the Page 1 to get us to page 2. Swipes don’t work either. The system will ignore any illegal segues.

Stop the app and delete the page segue. Control-drag from the button on Page 1 to Page 2 and select the Push segue. Clean, Build and Run. You get all three pages.

hierarchy navigation

Adding Modal Controllers

The last type of controller does not care what type is the one before it. Modal controllers can be called by both page and hierarchy controllers. While the two we’ve learned so far transition horizontally, modal controllers transition vertically. In the watch or simulator, swipe up on the watch face and you have a modal to the glances. The glances, as we discussed earlier, happen to be page navigation. Here’s an exception to the mutual exclusion between hierarchy and pages. If you have a hierarchy then segue to a modal, your modal can segue to pages. However it does not work the other way around. From a page, you cannot call a modal and then a hierarchy.

You set a Modal segue the same way as a hierarchical segue: from control to controller. Stop the app. From the Page 0 button, control-drag to the RedModal controller. Select Modal in the Action Segue menu

2016-03-01_06-23-17

You get a segue between the two.

2016-03-03_06-43-33

Control-drag from the Page 2 button to the BlueModal controller. Select a Modal action segue. From the BlueModal, control-drag to the RedModal. Select the Page relationship segue. Your storyboard should look like this:

2016-03-03_06-44-38

Build and run. All the navigation works.

modal navigation

Programmatic Navigation

We’ve covered navigation on the storyboard. While the mutual exclusive part does provides some limits for building apps, there is enough flexibility to set up your entire app in the storyboard. It’s probably best to setup your applications in the story board, as many of the attributes of a controller are not accessible programmatically. However, many developers prefer programmatic navigation to segues. Sometimes the logic of the code dictates a more programmatic approach. There are three methods that call controllers directly, as shown in the chart above. We’ll discuss each as we code the controllers.

Press Command-N to make a new file. Make a new WatchOS Watchkit file named PageIntefaceController, subclassing WKInterfaceController. Save the file being careful to make sure the group is in the extension.

In the file that appears, add the following code.

    var whichModal = "RedModal"
    @IBOutlet var goModalOutlet: WKInterfaceButton!
    
    @IBAction func goModalAction() {
        presentControllerWithName(whichModal, context: nil)
    }

In the action goModalAction, the method presentViewControllerWithName:context: presents the modal controller with the identifier name from the storyboard. We’ve used a string variable whichModal, which we’ll use in a later iteration to switch between the blue and red modals. The second parameter context, sends data to the destination controller. We set this to nil to not use it. We’ll discuss this parameter in mmore detail below.

Select the InterfaceController.swift file. Add the follow code to the InterfaceController class

@IBAction func topButton() {
    pushControllerWithName("Page 0", context: nil)
}
@IBAction func bottomButton() {
    let names = ["Page 2","Page 1","Page 0"]
    presentControllerWithNames(names, contexts: nil)
 }

The bottomButton action, contains the presentControllerWithNames method. This presents the page views. It has similar parameters to presentControllerWithName, with one change: instead of a single value it uses arrays. The array order is the order of the pages displayed. Our array is in the constant names, counting down pages instead of up as we have been doing. The contexts array has the corresponding context to in the names array. We are not using it yet, so we left that nil.

The action topButton has the method for the hierarchical segue pushControllerWithName:context: Like a navigation controller, it pushes the new interface into view. The parameters are identical to its modal brother.

Connect up your outlets and actions. Go back to the story board, and select the Page 0 controller. In the identity inspector, change the class to PageInterfaceController. Repeat for Page 1 and Page 2. Close the right panel inspector and open the assistant editor. Click on the Root Controller in the storyboard. Drag from the topButton action to the upper button. Drag from the bottomButton action to the bottom button.

Select the Page 0 controller. Connect the outlet and action to the button. Do the same for Page 1 and Page 2.

Clean, build and run. From the root menu, click the top button.

2016-03-03_06-05-00

It goes to a hierarchical controller version of Page 0. Tap the button in Page 0 and we get the red modal.

Go back to the root, and tap the bottom button. You get a page controller starting with Page 2 and counts down pages to 0.

2016-03-03_06-05-30

Tap any button on a controller goes to the red modal.

programmatic navigation

We seem to have broken the mutual exclusive nature of the page and hierarchy controllers. Programmatically,  there’s one more wrinkle. The system does not know which navigation you use until your first use of it on the root. In our app, we set that by the button pressed.

Working with Contexts

Contexts pass data to a destination controller. In the modal and Hierarchical controllers it is a single AnyObject? For pages it is an optional array of AnyObject. Close the assistant editor, and go to the InterfaceController.swift file. Change the actions to this:

    @IBAction func topButton() {
      pushControllerWithName("Page 0", context: "BlueModal")
    }
    @IBAction func bottomButton() {
        let names = ["Page 2","Page 1","Page 0"]
        let contexts = ["RedModal","BlueModal","RedModal"]
        presentControllerWithNames(names, contexts: contexts)
    }

In topButton we pass a string as a context. In bottomButton we added a string array contexts that will pass the string RedModal for Page 0 and 2 and a BlueModal for page 1.

Go to the PageInterfaceController code. Change the awakeWithContext method to this:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    if let newContext = context{
        whichModal = newContext as! String
        goModalOutlet.setTitle(whichModal)
    }
}

The awakeWithContext method is the equivalent to viewDidLoad in iOS, except it receives a context. We can use that context to set up the class. First optionally chain the context’s value. Then assign it, downcasting appropriately. I’m setting which modal we will use this way, and also setting the button’s title to tell you where you are going.

Build and run. Select the bottom button and you will see the contexts changing the page controllers.

contexts

What if we wanted to make a more elegant title for the button? A common way of passing more than one parameter in the context is to use a dictionary. Change InterfaceController’s actions to this:

@IBAction func topButton() {
    let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
    pushControllerWithName("Page 0", context: context)
}
@IBAction func bottomButton() {
    let names = ["Page 2","Page 1","Page 0"]
    let contexts:[[String:AnyObject]] = [
        ["Name":"RedModal","Title":"Red"],
        ["Name":"BlueModal","Title":"Blue"],
        ["Name":"RedModal","Title":"Red"]
    ]
    presentControllerWithNames(names, contexts: contexts)
}

Using a dictionary of type [String:AnyObject], I pass two strings to the array. I could use a [String:String], but I want to demonstrate the more generic case where you could use more than one type of data in the dictionary. Go to PageInterfaceController. Change awakeWithContext like this:

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        if let newContext = context{
            let contextDictionary = newContext as! [String:AnyObject]
            whichModal = contextDictionary["Name"] as! String
            let title = contextDictionary["Title"] as! String
            goModalOutlet.setTitle(title)
        }
    }

We changed the code to read a dictionary instead of a single value. You could do the same with a class or struct as well, though dictionaries are nicely temporary in comparison. Build and run. Click the top button and you find the Blue titled button

2016-03-04_07-07-28

Go Back to Root, then click the bottom button, we have a Red button

2016-03-04_07-07-27

Contexts with Segues

Watchkit also has a equivalent to prepareForSegue for passing data when you use a storyboard segue. Like prepareForSegue, you override the method. For modals and hierarchy there is the method For pages there is the to accommodate the array of contexts. Go to the storyboard and add a push segue between Root and Page 0

2016-03-03_05-31-22

In the attributes inspector, set the Identifier for the segue to Page0. Go to InterfaceController. Comment out the code in topButton

@IBAction func topButton() {
    //let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
    //pushControllerWithName("Page 0", context: context)       
}

Add the following function to your code

override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
    
if segueIdentifier == "Page0"{
    let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
        return context
    } else {
        return nil
    }        
}

The contextForSegueWithIdentifier works similar to prepareForSegue. Take  segueIdentifier and do what you need to set up the destination controller for that specific segue. The difference is we return the context, instead of setting a properties of the destination controller directly. For pages, we do the same thing using arrays and contextsForSegueWithIdentifier

Clean, build and run. Tap the top button on Root You should see no difference between the last run and this one.

2016-03-04_07-07-28

That is most of what you need to know about navigation. There are two or three more thing you might want to know such as dismissals and delegates. We’ll cover those next time as we delve into using tables in WatchOS2.

The Whole Code

InterfaceController.swift

//
//  InterfaceController.swift
//  WatchNavigationDemo WatchKit Extension
//
//  Created by Steven Lipton on 3/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {
    
    @IBAction func topButton() {
        // code commented out for using segues and context
        // code for hierachy (push) segues
        //let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
        //pushControllerWithName("Page 0", context: context)
        
    }
    override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
        // the prepareForsegue of WatchOS for modals and push
        // use contextsForSegueWithIdentifier for pages
        if segueIdentifier == "Page0"{
        let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
            return context
        } else {
            return nil
        }
        
    }
    @IBAction func bottomButton() {
        // A Page controller sequence with a dictionary as a context.
        let names = ["Page 2","Page 1","Page 0"]
        let contexts:[[String:AnyObject]] = [
            ["Name":"RedModal","Title":"Red"],
            ["Name":"BlueModal","Title":"Blue"],
            ["Name":"RedModal","Title":"Red"]
        ]
        presentControllerWithNames(names, contexts: contexts)
    }
   
}

PageInterfaceController.swift

//
//  PageInterfaceController.swift
//  WatchNavigationDemo
//
//  Created by Steven Lipton on 3/3/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class PageInterfaceController: WKInterfaceController {
    var whichModal = "RedModal"
    @IBOutlet var goModalOutlet: WKInterfaceButton!
    
    @IBAction func goModalAction() {
        //present a modal controller
        presentControllerWithName(whichModal, context: nil)
    }
    override func awakeWithContext(context: AnyObject?) {
        // the viewDidLoad of WatchKit/WatchOS. 
        // has a parameter which has the context sent to the controller 
        // So you can set the controller up 
        // we use a dictionary  of [String:AnyObject]
        super.awakeWithContext(context)
        if let newContext = context{
            let contextDictionary = newContext as! [String:AnyObject]
            whichModal = contextDictionary["Name"] as! String
            let title = contextDictionary["Title"] as! String
            goModalOutlet.setTitle(title)
        }
    }
}

How to Use Watch Timers and NSTimers in WatchOS2 and Swift

There’s a legacy from WatchOS1 which is not only frustrating but deceptive. While one would think that a watch would have easy to use built in timers, that is far from the case. WatchOS2 changed this situation slightly, but still makes timekeeping not as easy as one would think. There is a timer in WatchKit, but it is mere window dressing. Core to understanding timers is the same as understand them for iOS: NSTimer. In this lesson, we’ll make an app to explore all the varieties of timers and when each is useful.

Set Up the Project

Make a new WatchOS project called WatchHIITDemo. Use the iOS App with WatchKit App template and set the language to Swift. Check off notifications and glances. Go to the watch app interface.storyboard. Drag a button to the storyboard. Set its vertical alignment to Bottom. Change the title to Start/Stop. Set a background color of yellow(#FFFF00) and a dark text color for the text color. Drag a label to the storyboard. Set its title to Stopped. Change the font to System at 22 point. Set the Vertical Alignment and Horizontal Alignment to Center.

2016-02-18_05-41-53

Add a Watch Timer

Watchkit has a built in timer of class WKInterfacetimer. You’ll find it in the object library looking like this.

2016-02-18_05-44-03

Drag a timer to the storyboard. Set the horizontal and vertical alignment to center. Set the font to System 22 point to match the label. Drag the timer under the Stopped label.

2016-02-18_05-48-52

Select the timer to see its attributes in the inspector.

2016-02-18_05-50-06

Near the top you will find a check box for Enabled. Make sure this is off. When checked, it starts the timer. We’ll use the button to start and stop the timer, leave it unchecked. Surrounding the enabled button is several selections for the format. Timers use NSDate to store a date. We can use anything that an NSDate can display as our timer. Play around and select a few different formats. You’ll notice a box Preview Secs which lets you select a time in seconds to show in the timer.

Change the attribute to have hours, minutes and seconds only in positional format as illustrated above. We must set this here since WatchKit will not allow us to set format programmatically.

Add Groups and Another Timer

Scroll through the object library and find Group. Drag the group to the storyboard. In the attribute inspector, You’ll find an attribute Layout.

2016-02-18_05-58-24

Click this and you’ll see two options, Horizontal and Vertical. This is the core of the group object. Basic group objects don’t do much on their own. They act as layout containers for other objects, much like stack views in iOS. If we want two objects next to each other, we can make them a horizontal layout. Set the layout to Horizontal.
Drag a label and a timer into the group. Drag the label to the left of the group. They line up on the same row.Set te label text to Elapsed. Change the horizontal alignment on the new timer to Right Our storyboard looks like this:

2016-02-18_06-14-25

The center elements are a little too close to the button. We can use a group to position them better using group’s Insets attribute. Drag another group to the storyboard. Set the vertical and horizontal alignment to Center. Change the Layout to Vertical. Drag the Stopped label and the larger timer to the group. Select the group. In the attributes inspector, change insets to Custom. You get new fields to add space in points to the group.

2016-02-18_06-13-46

Set the bottom to to 15 points. The group moves up 15 points.

2016-02-18_06-14-25

Connect the Outlets and Actions

Open the assistant editor with the watch view controller selected on the storyboard. The watch extension opens in the assistant editor. If you wish, you can close the attributes inspector to give yourself some space.

Control drag from the Elapsed label and make a label outlet named elapsedLabel. Control drag from the Stopped label and make a label outlet named statusLabel. Control drag from the top timer and make a timer outlet named workoutTimer. Control drag from the center timer and make a timer outlet named intervalTimer. You should have the following outlets in your code.

@IBOutlet var elapsedLabel: WKInterfaceLabel!
@IBOutlet var statusLabel: WKInterfaceLabel!
@IBOutlet var workoutTimer: WKInterfaceTimer!
@IBOutlet var intervalTimer: WKInterfaceTimer!

Button Outlets and Actions in WatchOS

There’s a difference between iOS and WatchOS in naming objects with both actions and outlets. Objects in WatchKit have no sender parameter in their actions. This presents a problem. Since we have no parameters for the action, If we name an outlet and an action for the same object with the same name, the compiler will give an error that we used the same identifier twice. To work around this, I add Action to the end of the action. Control drag from the button to the code. Make an outlet named startStopButton.

 
@IBOutlet var startStopButton: WKInterfaceButton!

Control drag again from the button to the code. This time select action in the popup. Name the action startStopAction. You get an action.

@IBAction func startStopAction() {
}

You can name the outlet and action whatever you like, but don’t name them the same as in iOS.

Using the WKInterfaceTimer

Under the action, add the following code:

    //basic wkTimer
    func wkTimerReset(timer:WKInterfaceTimer,interval:NSTimeInterval){
        timer.stop()
        let time  = NSDate(timeIntervalSinceNow: interval)
        timer.setDate(time)
        timer.start()
    }
 

We’re making a function so we can reset and start both timers. The WKINterfaceTimer class has three methods: stop,start and setDate which I demonstrate in this method. This class is really a souped-up label with a timer in the background. The timer constantly runs. The start and stop methods start and stop updating to the label. The setDate method sets the timer. If the value of the timer’s parameter is positive, it becomes a countdown timer. If the value is negative, it counts up like a stopwatch.

The parameter’s value for setTime is type NSDate. We’ve used the NSDate initializer timeIntervalSinceNow to get a date interval seconds in the future, then set the date in the timer. Once set, we start up the timer.

You’ll notice we really didn’t need to start and stop the timer since they are only cosmetic. The only real work is done by setDate. I did that here to demonstrate the functions.

Change the startStopAction like this:

func startStopAction(){
    isStarted = !isStarted //toggle the button
    if isStarted{
          startStopButton.setTitle("Stop")
         wkTimerReset(workoutTimer,interval: 0.0) //counts up if zero or less 
         wkTimerReset(intervalTimer,interval:15.0) // counts down if greater than zero
    } else {
          startStopButton.setTitle("Start")
          workoutTimer.stop()
          intervalTimer.stop()
    }
}

Above the outlets, make a property for our button toggle

var isStarted = false

Launch the phone and watch simulators. Let them load completely. If you have no easy way to launch the simulator, check the directions here so you have an easy link to the simulator. This will prevent timeout problems when loading and running the simulator. When the watch face shows on the simulator, set the simulator to run the watch app.

2016-02-18_06-36-21

Build and run.

After the app loads, press start and your watch counts down 15 seconds, then stops. Meanwhile the elapsed timer continues to count up.

You can stop it by pressing the stop button.

Using a NSTimer

We’ve found the one big problem with the WatchKit timer: it does nothing but count. It’s a automatic label. To get events on a timer we need to use the NSTimer object. These objects create their own timing loop. They can be used in two ways: as a one shot timer and as a repeating timer. We set a time in seconds for the loop to complete, and give the loop a function to run when it completes the loop. Using our current WKInterfaceTimer setup, we can fire a timer when the run is complete.

Add the following function to the code:

func loopTimer1(interval:NSTimeInterval){ //NSTimer to end at event.
        if timer.valid {
            timer.invalidate()
        }
        timer = NSTimer.scheduledTimerWithTimeInterval(interval,
            target: self,
            selector: "loopTimer1DidEnd:",
            userInfo: nil,
            repeats: false)
        wkTimerReset(intervalTimer, interval: interval)
        wkTimerReset(workoutTimer, interval: 0)

    }

The last two lines are familiar. Those reset the watch timers. The if clause checks if the timer is running, which we get from the valid property. If we are running a timer loop on timer, shut it down. If not, we start the timer with the class method scheduledTimerWithTimeInterval. Its first parameter is a NSTimeInterval telling the timer how many seconds to run before running the ending function. The next two parameters target and selector give the location of that method. The function self.loopTimer1didEnd runs when the timer is done. Note the colon at the end of the selector. This means there is a parameter on this function, which is the timer. Therefore we’ll need function loopTimer1DidEnd(timer:NSTimer) in our code. The next parameter userInfo is of type AnyObject?. This allows us to pass data to our selector as we’ll see shortly. Finally we have a Bool parameter indicating if we stop or repeat the timer after timing to interval. In this iteration we have a timer that ends when the watch’s intervalTimer ends.

We need that terminating function from the selector. Add the following code under loopTimer1:

    func loopTimer1DidEnd(timer:NSTimer){
        statusLabel.setText("Stopped")
        startStopButton.setTitle("Start")
        isStarted = false
        intervalTimer.stop()
        workoutTimer.stop()
        timer.invalidate()
    }

This does what we expect. it shuts down everything but the elapsed timer. That isn’t new, but now we can control events and change our button and label to reflect the end of the interval. If you have a single event, such as a simple countdown timer, this is a good strategy.

Comment out the code in startStopAction and replace it with this:

//NSTimer for event
        isStarted = !isStarted //toggle the button
        if isStarted{
            statusLabel.setText("Run")
            startStopButton.setTitle("Stop")
            loopTimer1(myInterval)
        } else {
            startStopButton.setTitle("Start")
            workoutTimer.stop()
            intervalTimer.stop()
            timer.invalidate() //stop timer
        }

We’ve set up this button action to toggle on and off the timer. We’ll call the function loopTimer1 to start and use NSTimer‘s invalidate method to stop the timer.

Build and run.

Repeating Timers for Updating

While nice, what if I want the countdown timer to reset itself and start the countdown again? For that we need a repeating timer.
Add another method to the class:

func loopTimer2(interval:NSTimeInterval){ //NSTimer to end at event.
        if timer.valid {
            timer.invalidate()
        }
        timer = NSTimer.scheduledTimerWithTimeInterval(interval,
            target: self,
            selector: "loopTimer2DidEnd:",
            userInfo: interval, //pass data to selector
            repeats: true) //repeat the interval
        wkTimerReset(intervalTimer, interval: interval)
        wkTimerReset(workoutTimer, interval: 0)
        
    }

This code is not much different than the last timer. The first difference is setting repeats to true. At the end of each loop, we run the termination selector then start again. The second difference is assigning a value to userInfo. Termination functions have a parameter of the timer. The parameter userInfo in this initializer sets the NSTimer property userInfo. We can pass data from the loopTimer2 function to the terminating function loopTimer2DidEnd through userInfo.

Add this code for the terminating function.

    func loopTimer2DidEnd(timer:NSTimer){
        isRunning = !isRunning
        wkTimerReset(intervalTimer, interval: timer.userInfo as! NSTimeInterval) //using userinfo data
        if isRunning {
            statusLabel.setText("Run")
        }else{
            statusLabel.setText("Walk")
        }
    }

In the third line we use timer.userInfo as! NSTimeInterval to pass the value of the timer interval to the function. The property userInfo is of type AnyType? so we can pass anything through and then downcast it in the terminating function. For more than one data value, you can pass a dictionary of [String:AnyType] or a class through.

We’ve changed the whole structure of the termination function. It no longer does stuff to end the loop. Instead it runs code to change the loop when it reaches the specified time interval. Like our previous example, we count on the timer the length of a workout interval, then run this code to reset the clock. In many applications this is a an efficient way of updating: run for a specified interval, check the current status outside of the clock and update the clock accordingly. Since WatchOS1 uses power and time to update the WKInterfaceTimers fro a NSTimer running n the phone, this is a very popular way of updating timer objects on a watch. In WatchOS2, it’s not as much of an issue with the extension running on the watch.

We also toggle between running and walking. To do this we use a common element of these types of loops: a flag. Flags are often a property of the class, allowing us to have a status for the clock outside of the timer loop. Here they are telling us if the loop is walking or running. Add the property isRunnig to the top of the class.

var isRunning = true

In startStopAction, comment out loopTimer1, then add our new timer

//loopTimer1(myInterval)
 loopTimer2(myInterval)       

We’ll need to change the status label as we get different activites. Comment out statusLabel.setText("Run"){ in startStopAction. Add this underneath it:

if isRunning {
statusLabel.setText(“Run”)
}else{
statusLabel.setText(“Walk”)
}

Build and Run. The clock counts down, then changes from run to walk and back again. For many events where you want to update your timer on a periodic basis, this is a good low power strategy. A transit app like My Bus Times Can’t talk to the bus database every second without draining power quickly. Instead it uses this strategy and checks on the bus arrival every twenty seconds and updates the arrival time.

Repeating timers without WKInterfaceTimer

The last type of timer we’ll look at is the same as the iOS equivalent. Instead of using a WKInterfaceTimer, we use labels. We’ll set a timer interval for timer smaller than the time measurement. Each time interval we update the labels and check for any events in the termination function, which is better described as an update loop. Add the following function to the class:

func loopTimer3(interval:NSTimeInterval, timerInterval:NSTimeInterval){ //NSTimer to update at event
        if timer.valid {
            timer.invalidate()
        }
        timer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, //set from parameter to a short time.
            target: self,
            selector: "loopTimer3DidEnd:",
            userInfo: interval, //pass data to selector
            repeats: true)
        wkTimerReset(intervalTimer, interval: interval)
        wkTimerReset(workoutTimer, interval: 0)
        
    }

The only change between this code and the last version is including the timerInterval parameter, then using timerInterval as the time of our updates. Usually it’s a constant, but for you to play around with the value, I’ve made it a parameter. Depending on the application I use around 10% of the smallest unit displayed. This app goes to 1 second so I’ll be rough in my measurements and use a quarter of a second for updates which we’ll see later. I’m not using 0.1 seconds because less updates requires less computing power. You’ll need to find the right balance between processing, accuracy and smooth updating.

Our update code is a lot more complex. Add the following code:

    func loopTimer3DidEnd(timer:NSTimer){
        //infinite loop with events in the selector
        //uses labels instead of witch kit timers
        workoutTime += timer.timeInterval  //increment count up timers
        intervalTime -= timer.timeInterval //decrement count down timers
        if intervalTime <= 0 { //the workout interval is over
            //switch activities
            intervalTime = timer.userInfo as! NSTimeInterval
            isRunning = !isRunning
            wkTimerReset(intervalTimer, interval: timer.userInfo as! NSTimeInterval)
            if isRunning {
                statusString = "Run "
            }else{
                statusString = "Walk "
            }
        }
        statusLabel.setText(statusString + formatTimeInterval(intervalTime))
        elapsedLabel.setText(formatTimeInterval(workoutTime))
    }

WE are no longer using this as a termination function. it runs continuously and need to keep track of our timers on its own. I’m using two variables workoutTime and intervalTime to do this. To run our elapsed time counter, I add the length of time between updates which I have in the timeInterval property of NSTimer. To count down, I subtract. If my countdown timer reaches 0 or less, our workout interval is over and we do much of the same as we did in loopTimer2DidEnd. For demonstration purposes I left a wkTimerReset for the run/walk timer, but I also did something else. I set a string instead of the label to switch between running and walking. After the if clause is the reason why. I’m changing my label to hold the time, and appending the status in front of it. We’ll need a string conversion function, which we’ll get to shortly.
To get rid of many of our errors, We’ll need to add the properties for the counters and string as properties:

    var statusString = "Stopped"
    var workoutTime:NSTimeInterval = 0.0
    var intervalTime:NSTimeInterval = 0.0

We also have to add the formatting function. Sadly there is no factory formatter for NSTimeInterval as there is for NSDate. Fortunately the function is not difficult for HH:MM:SS presentation of seconds. Add this function:

//format seconds into strings of hh:mm:ss
    func formatTimeInterval(timeInterval:NSTimeInterval) -> String {
            let secondsInHour = 3600
            let secondsInMinute = 60
            var time = Int(timeInterval)
            let hours = time / secondsInHour
            time = time % secondsInHour
            let minutes = time / secondsInMinute
            let seconds = time % secondsInMinute
            return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }

We do a series of divisions and remainders to get the time in components. Change the button action to run this timer with a 0.25 second update:

//loopTimer1(myInterval)
//loopTimer2(myInterval)
loopTimer3(myInterval, timerInterval: 0.25)

Also add the updated statusString here:

if isRunning {
                statusLabel.setText("Run")
                statusString = "Run "
            }else{
                statusLabel.setText("Walk")
                statusString = "Walk "
            }


Our last step is to initialize the countdown timer from our constant. Change <code>awakefromContext</code> to this:

override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        intervalTime = myInterval
    }

Build and Run. Start the watch and see what happens.

Stop the watch and notice the timers. In all the cases, they don't time well together. Both the resolution of your timing loop and the starting of different timing loops at different times lead these to not agree with each other. I did this for comparison purposes, but you can see its a bad idea to have too may timers running at the same time. I could probably have my two labels more in sync if I used a 0.1 second interval.

If you need constant updating of a lot of your app, this is the way to do it. If you have less needs of updates, you'll get better efficiency from one of the other ways.
The final case made the timer not stop and reset like the other cases but pause. What if you wanted to stop here? IN the next installment, we'll discuss adding menus and navigation to an app to let you do just that.

The Whole Code

//
//  InterfaceController.swift
//  WatchHIITDemo WatchKit Extension
//
//  Created by Steven Lipton on 2/18/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {
    //MARK: Outlets and Properties
    
    let myInterval:NSTimeInterval = 15.0
    
    //Timer Properties
    //While I didnt in the tutorial for simplicity, note that I made these private.
    //Generally this is a good practice. Nothing but the class should mess
    //with your timer's workings.
    
    private var isStarted = false
    private var isRunning = true
    private var timer = NSTimer()
    
    private var statusString = "Stopped"
    private var workoutTime:NSTimeInterval = 0.0
    private var intervalTime:NSTimeInterval = 0.0
    
    //Outlets
    @IBOutlet var elapsedLabel: WKInterfaceLabel!
    @IBOutlet var statusLabel: WKInterfaceLabel!
    @IBOutlet var workoutTimer: WKInterfaceTimer!
    @IBOutlet var intervalTimer: WKInterfaceTimer!
    
    @IBOutlet var startStopButton: WKInterfaceButton!
    
    
    //MARK: - Actions
    @IBAction func startStopAction() {
    /*
        //iteration 1 -- straight WKInterfaceTimer
        isStarted = !isStarted //toggle the button
        if isStarted{
            startStopButton.setTitle("Stop")
            statusLabel.setText("Run")
            wkTimerReset(workoutTimer,interval: 0.0) //counts up if zero or less
            wkTimerReset(intervalTimer,interval:myInterval) // counts down if greater than zero
        } else {
            startStopButton.setTitle("Start")
            statusLabel.setText("Stopped")
            workoutTimer.stop()
            intervalTimer.stop()
        }
 */
        //NSTimer for event
        isStarted = !isStarted //toggle the button
        if isStarted{  // Set the status label
   //            statusLabel.setText("Run")


            if isRunning {
                statusLabel.setText("Run")
                statusString = "Run "
            }else{
                statusLabel.setText("Walk")
                statusString = "Walk "
            }

            startStopButton.setTitle("Stop")
            
            //loopTimer1(myInterval)  //Stop the counter at end of timer interval
            //loopTimer2(myInterval)  //Repeat the counter at end of timer interval
            loopTimer3(myInterval, timerInterval: 0.25)  //Use small intervals and stop
        } else {
            startStopButton.setTitle("Start")
            workoutTimer.stop()
            intervalTimer.stop()
            timer.invalidate() //stop timer
        }

        
    }
    
    func wkTimerReset(timer:WKInterfaceTimer,interval:NSTimeInterval){
        timer.stop() //stop display of WKInterfaceTimer
        let time  = NSDate(timeIntervalSinceNow: interval)
        timer.setDate(time)
        timer.start() //srat display of WKInterfaceTimer
    }
    
    func loopTimer1(interval:NSTimeInterval){ //NSTimer to end at event.
        if timer.valid { //kill a running timer before starting a time
            timer.invalidate()
        }
        timer = NSTimer.scheduledTimerWithTimeInterval(interval,
            target: self,//target and selector give location of code
            selector: "loopTimer1DidEnd:", //to execute when timer done
            userInfo: nil,
            repeats: false)
        wkTimerReset(intervalTimer, interval: interval)
        wkTimerReset(workoutTimer, interval: 0)

    }
    
    func loopTimer1DidEnd(timer:NSTimer){
        statusLabel.setText("Stopped")
        startStopButton.setTitle("Start")
        isStarted = false
        intervalTimer.stop()
        workoutTimer.stop()
        timer.invalidate()
    }
    
    func loopTimer2(interval:NSTimeInterval){ //NSTimer to end at event.
        if timer.valid {
            timer.invalidate()
        }
        timer = NSTimer.scheduledTimerWithTimeInterval(interval,
            target: self,
            selector: "loopTimer2DidEnd:",
            userInfo: interval, //pass data to selector
            repeats: true) //repeat the interval
        wkTimerReset(intervalTimer, interval: interval)
        wkTimerReset(workoutTimer, interval: 0)
        
    }
    
    func loopTimer2DidEnd(timer:NSTimer){
        isRunning = !isRunning
        wkTimerReset(intervalTimer, interval: timer.userInfo as! NSTimeInterval)
        if isRunning {
            statusLabel.setText("Run")
        }else{
            statusLabel.setText("Walk")
        }
    }
    
    func loopTimer3(interval:NSTimeInterval, timerInterval:NSTimeInterval){ //NSTimer to update at event
        if timer.valid {
            timer.invalidate()
        }
        timer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, //set from parameter to a short time.
            target: self,
            selector: "loopTimer3DidEnd:",
            userInfo: interval, //pass data to selector
            repeats: true)
        wkTimerReset(intervalTimer, interval: interval)
        wkTimerReset(workoutTimer, interval: 0)
        
    }
    
    func loopTimer3DidEnd(timer:NSTimer){
        //infinite loop with events in the selector
        //uses labels instead of
        workoutTime += timer.timeInterval
        intervalTime -= timer.timeInterval
        if intervalTime <= 0 { //the workout interval is over
            //switch activities
            intervalTime = timer.userInfo as! NSTimeInterval
            isRunning = !isRunning
            wkTimerReset(intervalTimer, interval: timer.userInfo as! NSTimeInterval)
            if isRunning {
                statusString = "Run "
            }else{
                statusString = "Walk "
            }
        }
        statusLabel.setText(statusString + formatTimeInterval(intervalTime))
        elapsedLabel.setText(formatTimeInterval(workoutTime))
        
    }
        
    
    //format seconds into string of hh:mm:ss
    func formatTimeInterval(timeInterval:NSTimeInterval) -> String {
            let secondsInHour = 3600
            let secondsInMinute = 60
            var time = Int(timeInterval)
            let hours = time / secondsInHour
            time = time % secondsInHour
            let minutes = time / secondsInMinute
            let seconds = time % secondsInMinute
            return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        intervalTime = myInterval
    }

    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: Selecting With Multiple Rows in Apple Watch

row data mapping illustration

In the last lesson we created a multi-row table. However, we can only view the table, not select from the table. Multi-row tables provide some challenges with selection. Along the way, we’ll make a new interface to display selected information, using a technique we have not yet covered in this series: A dictionary as a context.

The problem of a muti row table is rather annoying. We have an array with different types of data stored in it for the different row types, all mixed together. We have header, footer,  row, and subhead data. Each uses the same data type but uses them differently. Subheads summarize what a mile or running looks like, while rows give us the data for that interval.

Basic Selection

We are going to modify the project from the last lesson to add selection. Go to the storyboard. In the document outline select the Header row. In the attribute inspector, note the Allow Selection Checkbox is unchecked

2015-08-26_06-31-28

Now select the Row table controller. The Allow Selection Checkbox is checked.

2015-08-26_06-33-49

As the title states, the rows checked for selection will allow selection. We checked for the subheader and the row, and unchecked for the header and footer.

In our InterfaceController code add the following

 override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
	// code goes here
}

This is the basic selection function. row data mapping 1We access data from the rowIndex. In a single row table, this is easy. However with a multi-Row table the index does not describe the data, so we need to do a little work to get the data we are interested in. We’ll look at three ways to do this.

A New Interface

Before we do anything we need another interface to display our information. Go to the storyboard. Drag a new interface controller to the storyboard. Add three labels and a button. Once added, position the button to the bottom and the three labels remain on the top. Title the button Done. Title the labels Distance, Pace and Title like this:

2015-08-26_06-30-47

Press Command-N and make a new InterfaceController subclass called InfoInterfaceController. Make sure to save it in the Extension group. Leave it blank for now and go back to the storyboard. Select the our view controller’s icon and in the attributes inspector, set the identity and title to Info. In the Identity inspector set the class to InfoInterfaceController

Open the assistant editor set to Automatic. Control-drag from each label to the code. Make outlets named pace, distance and infoTitle to the corresponding label. Control-drag from the button to the code and make an action named doneViewing. Your code should now have these lines.

    @IBOutlet var pace: WKInterfaceLabel!
    @IBOutlet var distance: WKInterfaceLabel!
    @IBOutlet var infoTitle:WKInterfaceLabel!
    
    @IBAction func doneViewing() {
    }

Change the action to this:

@IBAction func doneViewing() {
        dismissController()
    }

We now have a big dismissal button for the modal interface we created.

A Dictionary Context

Close the assistant editor and go to the InfoInterfaceController.swift file. We’ll only need awakeWithContext so you can delete the other life cycle methods if you wish. In awakeWithContext code this:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    // Configure interface objects here.
    //context is sent as a Dictionary of [String:String]
	let data = context as! [String:String]
}

In previous lessons we used a class or a struct for a context. This time we’ll use a dictionary with string values. We convert the AnyObject! context to the dictionary [String:String] a dictionary with strings for keys and strings for values. We picked strings for a reason: they can be directly displayed by the setText method of WKInterfaceLabel. Add this at the bottom of awakeWithContext

	distance.setText( "Distance:" + data["Distance"]!)
	pace.setText("Pace:" + data["Pace"]!)
	infoTitle.setText(data["Title"])

When we call the modal, it will assign the correct strings to the labels. Note here I did this the fast simple way. In production code, you should be checking for nil values.

Setting up the Selection Method

Switch over to the InterfaceController.swift file. Change the table:didSelectRowAtIndex: to this:

    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        //selection of data and presenting it to
        var myDistance = "0 miles"
        var myPace = "00:00:00"
        var myTitle = "No title yet"
        var context = [String:String]()
        //code goes here	
        presentControllerWithName("Info", context: context)
    }

We’ve made a few variables to work with, and set up the format of our context as a dictionary. Our last line presents this dictionary to the modal Info. In between, we’ll set up a switch statement to access the data we need for the modal. Add this above the presentControllerWithName line:

switch rowTypes[rowIndex]{
	case "Row":            
		myTitle = "0.5 mile data"
		context = ["Pace":myPace,"Distance":myDistance,"Title":myTitle]
	case "Subhead":
		myTitle = "1.0 mile data"
		context = ["Distance":myDistance,"Pace":myPace,"Title":myTitle]
	default:
		myTitle = "Not a value row type for selection: " + rowTypes[rowIndex]
		print(myTitle)
		context = ["Distance":"Error","Pace":"Error","Title":myTitle]
}  

This time we only need the two rows we set up for selection. We can skip Header and Footer since it will never run this code.

Selection Option 1: A Pointer to the Data Array

row data mapping pointer 1How do you access data in data[0] when you are accessing rowdata[2]? The index of the data set, our rows is not the same index as the rows in our table. The first answer is to include a pointer to the index. We set that up in the last lesson in the class RowData:

class RowData {
    var data:Int = 0
    var index:Int = 0
}

The last lesson had a method addRowWithType to add rows to the table with RowData. While we had the index for the data handy, we saved it.

func addRowWithType(type:String,withData data:Int, withIndex index:Int){
        rowTypes = rowTypes + [type] //append the rowtype array
        let newData = RowData()
        newData.data = data
        newData.index = index
        rowData = rowData + [newData] //append the rowData array
    }

While we also stored the data itself (we’ll get to that shortly) we stored the index  of the  array data  that held the data. We are using a simple type Int for the data. If we used a custom class in the  data instead, this way would point us to the array and we could access all the properties and methods for the class with a simple pointer.

In our example, we will grab the data from the data array and compute the distance based that index pointer. Add this just under the myTitle = "0.5 mile data" line

//using a pointer to the original data
let dataIndex = rowData[rowIndex].index
myDistance = String (format:"%02.1f",(Double(dataIndex + 1) / splitsPerMile ))
myPace = paceSeconds( data[dataIndex])

We make a constant dataIndex for the pointer to the original data. We can get the distance by dividing by the number of splits. We need a constant for that so make a class constant at the top of the class:

let splitsPerMile = 2.0

Pace is our data, which we can get from data[dataIndex]. This option is a very good one, with a low memory impact for bigger tables.

Build and run. Select the 12:20  row, and you get this:

2015-08-26_07-51-17

Selection Option 2: Add to the Model

row data rowData arrayOur second option we’ve also implemented: using the rowData array to store our values for us. Currently it stores the pace only, but with a few lines of code, it can store the distance as well. Change the class definition of RowData to this:

class RowData {
    var data:Int = 0
    var index:Int = 0
    var distance = 0.0
}

We added a Double named Distance as a property. Now change addRowWithType to add a calculation for distance:

func addRowWithType(type:String,withData data:Int, withIndex index:Int){
	rowTypes = rowTypes + [type] //append the rowtype array
	let newData = RowData()
	newData.data = data
	newData.index = index
	newData.distance = Double(index + 1) / splitsPerMile // <----added line
	rowData = rowData + [newData] //append the rowData array

Back in the table:didSelectRowAtIndex: method, Comment out this code

/*          let dataIndex = rowData[rowIndex].index
            myDistance = String (format:"%02.1f",(Double(dataIndex + 1) / splitsPerMile))
            myPace = paceSeconds( data[dataIndex])
*/

Add this code under the commented out code

//Using the data model
            myPace = paceSeconds(rowData[rowIndex].data)
            myDistance = String (format:"%02.1i",rowData[rowIndex].distance)

Build and run and you will get the same results as before.

2015-08-26_07-51-17

This option keeps everything handy in the array rowData[rowIndex], which we computed when we built the table. No tracing of where something is really stored is necessary.

Selection Option 3: Add to the Row Controller Class

row data mapping classThe last option might be the most controversial, and more complicated to set up . Instead of storing the data in a property of the InterfaceController for the table, store the data in the row controller. Like the second option, it has the advantage of having everything right where we need it. One could argue it breaks MVC however. You could argue that the model should stay the model of the Interface controller, and the row controller should be thought of more like views than controllers. You could also argue that the table row controller is a controller, and as such should have its own model.

I would probably use this option when my view controller model was generally the same for all rows, but I had a lot of processing to do for a  row type. In our example, let’s make the myDistance a computed property, and when we set the pace, update the label.

class SubheadTableRowController: NSObject {
    var myPace = 0
    var myPacestring:String = "" {
        didSet {
            label.setText(myPacestring)
        }
    }
    var numberOfSplits = 0.0
    var splitsPerMile = 0.0
    var myDistance:Double{
        return numberOfSplits / splitsPerMile
    }
   @IBOutlet weak var label:WKInterfaceLabel!
}

We added several properties, including a computed property and a property observer. We could add some methods as well, depending on the situation. If there were methods to update the label, based on the properties, then this is a way to go.

When we display the row, in refreshBuildtable, we would need to update the properties along with the display. Change the case "Subhead" in the  SubheadTableRow controller from this:

case "Subhead":
    let row = table.rowControllerAtIndex(rowIndex) as!  SubheadTableRowController
    row.label.setText("Avg Pace: " + paceSeconds(rowData[rowIndex].data))

to this:

case "Subhead":
    let row = table.rowControllerAtIndex(rowIndex)
        as!  SubheadTableRowController
    row.myPace = rowData[rowIndex].data
    row.myPacestring = paceSeconds(row.myPace)
    row.numberOfSplits = Double(rowData[rowIndex].index + 1)
    row.splitsPerMile = splitsPerMile

We set the properties and the controller did the rest. Interestingly, we did use our option 2 data to set this up in the controller. In the selection method, we now access everything from the row controller. Add this code to our table:didSelectRowAtIndex:  method

case "Subhead":
	myTitle = "1.0 mile data"
	//example of storing data in the row controller
	let row = table.rowControllerAtIndex(rowIndex) as! SubheadTableRowController
	myPace = row.myPacestring
	context = ["Distance":myDistance,"Pace":myPace,"Title":myTitle]

We get the row from the table, then access the properties. At the selection part, using a class simple way. Build and run

Select the first  subheading, and you get a result

2015-08-26_08-42-36

These three are not exclusive. We used option two to get our option three for example, and we very easily could have used all three. Each has its benefits. Option one points to a model well, separating the model from the controller. If we use a class instead of a type for the array value, we can store and process that data rather nicely. Both option two and option one use a second array from our data array, one we need to display our data. Option three give us greater control at the Row controller level,  when we need to do a lot of processing for the final output. In reality, you would use each option as a tool to get to the a final result.

The Whole Code

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

import WatchKit
import Foundation

class RowData {
    var data:Int = 0
    var index:Int = 0
    var distance = 0.0
}

class InterfaceController: WKInterfaceController {
    
    @IBOutlet weak var table: WKInterfaceTable!
    let splitsPerMile = 2.0
    var rowData:[RowData] = []
    var rowTypes = ["Header","Subhead", "Row","Row","Subhead", "Row","Row","Footer"]
    var data = [740,745,750,740]
    //var data = [740,745,750,740,760,765,770,755]
    func avgData(array:[Int],start:Int,end:Int) -> Int{
        var total = 0
        for index in start...end{
            total = total + array[index]
        }
        return total / (end - start + 1)
    }
    
    func paceSeconds(pace:Int) -> String{
        let hours = pace / 3600
        let minutes = (pace - (hours * 3600 )) / 60
        let seconds = pace % 60
        return String(format:"%02i:%02i:%02i",hours, minutes,seconds)
    }
    func refreshTable(){
        var dataIndex = 0
        table.setRowTypes(rowTypes)
        for var rowIndex = 0; rowIndex > rowTypes.count; rowIndex++ {
            switch rowTypes[rowIndex]{
            case "Header":
                let row = table.rowControllerAtIndex(rowIndex) as! HeaderTableRowController
                row.label.setText(String(format:"Count: %i",data.count))
            case "Subhead":
                let row = table.rowControllerAtIndex(rowIndex) as!  SubheadTableRowController
                let avg = paceSeconds(avgData(data, start: 0, end: dataIndex))
                row.label.setText("Avg Pace: " + avg)
            case "Row":
                let row = table.rowControllerAtIndex(rowIndex) as! RowTableRowController
                row.label.setText("Pace " + paceSeconds(data[dataIndex++]))
            case "Footer":
                let row = table.rowControllerAtIndex(rowIndex) as! FooterTableRowController
                let avg = paceSeconds(avgData(data, start: 0, end: data.count - 1))
                row.label.setText("Pace: " + avg)
            default:
                print("Not a value row type: " + rowTypes[rowIndex]   )
            }
        }
    }
    //MARK: Iteration 2 of the table code
    func addRowWithType(type:String,withData data:Int, withIndex index:Int){
        rowTypes = rowTypes + [type] //append the rowtype array
        let newData = RowData()
        newData.data = data
        newData.index = index
        newData.distance = Double(index + 1 ) / splitsPerMile // <----added line rowData = rowData + [newData] //append the rowData array } func buildTable(){ //clear the arrays rowTypes = [] rowData = [] //make counters var dataIndex = 0 var subheadIndex = 1 // skipping zero //make a header addRowWithType("Header", withData: data.count, withIndex: 0) //loop through the data let subHeadInterval = 2 //if we are on an even row except 0, add a subhead/foot for var index = 0; index > data.count; index++ {
            if index % subHeadInterval == 0 && index != 0{
                addRowWithType("Subhead", withData: avgData(data, start: index - subHeadInterval, end: index - 1), withIndex: subheadIndex++)
            }
            // add the row data
            addRowWithType("Row", withData: data[index], withIndex: dataIndex++)
            
        }
        //add the footer
        addRowWithType("Footer", withData: avgData(data, start: 0, end: data.count - 1), withIndex: 0)
    }
    
    func refreshBuildtable(){
        buildTable() //refresh the table data
        table.setRowTypes(rowTypes) //set the row types
        //loop through the rowtype table
        for var rowIndex = 0; rowIndex > rowTypes.count; rowIndex++ {
            //parse the rowtypes
            switch rowTypes[rowIndex]{
            case "Header":
                let row = table.rowControllerAtIndex(rowIndex) as! HeaderTableRowController
                row.label.setText(String(format:"Count: %i",rowData[rowIndex].data))
                
            case "Subhead":
                let row = table.rowControllerAtIndex(rowIndex) as!  SubheadTableRowController
                row.myPace = rowData[rowIndex].data
                row.myPacestring = paceSeconds(row.myPace)
                row.numberOfSplits = Double(rowData[rowIndex].index + 1)
                row.splitsPerMile = splitsPerMile
                
            case "Row":
                let row = table.rowControllerAtIndex(rowIndex) as! RowTableRowController
                row.label.setText(paceSeconds(rowData[rowIndex].data))
            case "Footer":
                let row = table.rowControllerAtIndex(rowIndex) as! FooterTableRowController
                row.label.setText("Pace: " + paceSeconds(rowData[rowIndex].data))
            default:
                print("Not a value row type: " + rowTypes[rowIndex]   )
            }
        }
    }
    
    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        //selection of data and presenting it to
        var myDistance = "0 Miles"
        var myPace = "00:00:00"
        var myTitle = "No title yet"
        var context = [String:String]()
        switch rowTypes[rowIndex]{
        case "Row":
            
            myTitle = "0.5 mile data"
            //using a pointer to the original data
/*            let dataIndex = rowData[rowIndex].index
            myDistance = String (format:"%02.1f",(Double(dataIndex + 1) / splitsPerMile))
            myPace = paceSeconds( data[dataIndex])
*/
            
            //Using the data model
            myPace = paceSeconds(rowData[rowIndex].data)
            myDistance = String (format:"%02.1i",rowData[rowIndex].distance)
            context = ["Pace":myPace,"Distance":myDistance, "Title":myTitle]

        case "Subhead":
            myTitle = "1.0 mile data"
            //example of storing data in the row controller
            let row = table.rowControllerAtIndex(rowIndex) as! SubheadTableRowController
            myPace = row.myPacestring
            myDistance = String(format: "%02.1f miles", row.myDistance)
            context = ["Distance":myDistance,"Pace":myPace,"Title":myTitle]
        default:
            myTitle = "Not a value row type for selection: " + rowTypes[rowIndex]
            print(myTitle)
            context = ["Distance":"Error","Pace":"Error","Title":myTitle]
        }
        presentControllerWithName("Info", context: context)
    }
    
    //MARK: life cycle
    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        //refreshTable()
        refreshBuildtable()
    }
}
//
//  InfoInterfaceController.swift
//  WatchKitMultiRow
//
//  Created by Steven Lipton on 8/22/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InfoInterfaceController: WKInterfaceController {
    
    @IBOutlet var pace: WKInterfaceLabel!
    @IBOutlet var distance: WKInterfaceLabel!
    @IBOutlet var infoTitle:WKInterfaceLabel!
    
    @IBAction func doneViewing() {
        dismissController()
    }
    override func awakeWithContext(context: AnyObject?) {
        //context is sent as a Dictionary of [String:String]
        super.awakeWithContext(context)
        // Configure interface objects here.
        let data = context as! [String:String]
        distance.setText( "Distance:" + data["Distance"]!)
        pace.setText("Pace:" + data["Pace"]!)
        infoTitle.setText(data["Title"])
    }
}
//
//  SubheadTableRowController.swift
//  wkMultirowSelect
//
//  Created by Steven Lipton on 8/24/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class SubheadTableRowController: NSObject {
    var myPace = 0
    var myPacestring:String = "" {
        didSet {
            label.setText(myPacestring)
        }
    }
    var numberOfSplits = 0.0
    var splitsPerMile = 0.0
    var myDistance:Double{
        return numberOfSplits / splitsPerMile
    }
   @IBOutlet weak var label:WKInterfaceLabel!
}

//
//  HeaderTableRowController.swift
//  wkMultirowSelect
//
//  Created by Steven Lipton on 8/24/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class HeaderTableRowController: NSObject {
    @IBOutlet weak var label:WKInterfaceLabel!
}

//
//  HeaderTableRowController.swift
//  wkMultirowSelect
//
//  Created by Steven Lipton on 8/24/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class HeaderTableRowController: NSObject {
    @IBOutlet weak var label:WKInterfaceLabel!
}

//
//  RowTableRowController.swift
//  wkMultirowSelect
//
//  Created by Steven Lipton on 8/24/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class RowTableRowController: NSObject {
    @IBOutlet weak var label:WKInterfaceLabel!
}

//
//  FooterTableRowController.swift
//  wkMultirowSelect
//
//  Created by Steven Lipton on 8/24/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class FooterTableRowController: NSObject {
    @IBOutlet weak var label:WKInterfaceLabel!
}

Swift Watchkit: Adding Context Menus

2015-07-01_06-58-23Menus are cool. Since the first interactive program, there have been menus in applications. The Apple Watch is no exception, but has no space to put a menu. To solve this problem, Apple uses the new force touch gesture to pop open a menu. You can make your own menu, with customized icons and captions. Like much of WatchKit, it’s simple to make a menu. However it’s not so simple to make an icon. In this lesson, we’ll show you what you need to know to make menus with your own custom icons.

Making Icons for WatchOS Menus

WatchOS menu icons share a lot with iOS tab bar icons. They are relatively small, and they do not use colors like images. Instead they use the transparency channel, also known as the alpha channel to make a shade of gray.  As you can see in this illustration, 100% alpha is black and 0% alpha is white. Any value in between is a shade of gray.

alpha color

Icons do not care about color. They are always one color. In WatchOS menus, that is a shade of gray.  If we use the icon in some other part of our interface, we can see it in color, but the system will make the grayscale icon for the menu icon.

WatchKit is very particular about the sizes of icons. An icon needs to be on an transparent image 120 pixel square for 42mm and 104 pixel square for a 38mm. The icon image should be 53px square for a  42mm watch and  40px square for a 38mm  watch.  You can make a icon bigger that that, but it is not recommended for good looks.  To summarize all this, we have this handy guide:

Apple watch Menu Icon template guide

Developers and graphic designers have different ideas of what application to use for making icons.  Some go for the raster graphic route using Photoshop or its open source counterpart Gimp. Some like me prefer vector graphics. For vector graphics there is the free open source application Inkscape. For my workflow, I paid around $30 for iDraw by Indeeo, since it has linked versions for both Mac and iPad. I can work on either device.

image    2015-07-02_06-11-05

I’ve made three icons for this lesson, saving each as a .png file. One was a camera icon I modified from iDraw’s clipart, an undo button and a cancel button.

  IconCamera42@2x IconCancel42@2x IconUndo42@2x

I also made a copy for the  38mm watch:

 IconCamera38@2x IconCancel38@2x IconUndo38@2x

Right click  each icon above and save it. You can also download all of them here: WatchMenuIcons.zip for use  in the rest of the exercise.

To demonstrate alpha values, I set the top of the camera body and the lens of the camera at different alpha values.

camera42@2x annotated

 If you are only using lines for your graphics, you need thick lines to be seen clearly on the menu. Make sure you use thick lines if you make line graphics such as the Undo or Cancel icons. The icon guide  has a table of Apple’s recommended line weights.

Create the Project

In Xcode, make a new single-view project called SwiftWatchMenuIcons. You need the language set to Swift and device as Universal. 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 Icons to Xcassets

In the WatchKit App group, click on the Images.xcassets folder.  This opens up the assets folder.  I haven’t found if Apple came up with a naming convention for watch images for automatic loading,  so we’ll add them manually. At the bottom of the assets folder you will find an add icon(+)

2015-07-02_06-51-52

Click the Icon and you get a menu. Select New Image Set.

2015-07-02_06-53-46

Xcode will list a new image set under your app icon image set.  Repeat this process two more times to get three image sets.

2015-07-02_06-54-54

 Click one of the image sets. In the attributes inspector, change its name to Camera.  You’ll notice it gives you a generic image set

2015-07-02_06-59-15

You’ll notice under the name is a dropdown called Device. Change it from its current setting of Universal to Device Specific. You will get a set of check boxes. Uncheck iPhone and iPad, so only Apple Watch is selected.

2015-07-02_06-58-28

The image set changes to Apple Watch.

2015-07-02_06-59-42

Open your finder window where you saved the icon images. Drag from the 42mm camera icon to the spot marked for a  42 mm image in the assets folder and from the 38mm camera icon to the spot for the 38mm image.

2015-07-02_07-02-27

When done you’ll have two icons in the image set:

2015-07-02_07-05-40

Now repeat this for the other two image sets, naming them Cancel and Undo, then moving in the icons for Cancel and Undo. When done you should have three image sets like this:

2015-07-02_06-55-54

Add a Menu to the Storyboard

Go to the storyboard for the watch app. Find the menu in the object browser:

2015-07-06_05-27-55

Drag the menu into the interface:

2015-07-06_05-30-10

 

There is no visible change, unless you look at the document outline. If you do not have it open, click the 2015-07-06_05-31-05 icon at the bottom of the storyboard. In the panel that appears on the left, you will see the menu in the outline.

2015-07-06_05-33-19

This menu has one menu item, we need three. There are two ways to add menu items. The first is to  drag a menu item from the object library to the document outline.

2015-07-06_05-41-13

The second is to select the menu and change the properties to 3. There is a maximum of four menu items.

2015-07-06_05-45-03

In the document outline, select the top menu item.

2015-07-06_05-50-12

The attributes inspector shows this:

2015-07-06_05-50-47

Change the Title to Camera. Making sure the image type is Custom. Click the drop down for the image, and change it to Camera:

2015-07-06_05-59-10

Repeat for the other two menu items changing them to Cancel and Undo. When done, your document outline should look like this:

2015-07-06_06-09-28

Connect the Menu Items.

Like buttons, menu items have actions in their interface controller.  Unlike buttons we cannot drag from the button on the storyboard. Instead, we need to set them from the document outline.

Open the assistant editor.  Control drag from the menu item Camera in the document outline to the code in the assistant editor.

2015-07-06_06-05-06

Create an action named cameraPressed.

2015-07-06_06-10-59

Do the same for the Cancel and Undo buttons, creating the following code:

    @IBAction func cameraPressed() {
    }
    @IBAction func undoPressed() {
    }
    @IBAction func cancelPressed() {
    }

Our buttons will display a message on the watch when pressed. We need two more objects for that. Drag an image and a label to the interface. Position the image center and top. Position the label center and bottom. Your interface should look like this:
2015-07-06_06-24-01Control-drag from the label  to the code. Make an outlet named displayLabel. Control-drag from the image to the code make an outlet named displayImage.

Add Code for the Menus

For starters, we’ll make a method to display the icon and the text. Watchkit can’t read properties, so we have to get sneaky. Add this to your code:

func displayAction(action:String){
    displayLabel.setText(action)
    if let image = UIImage(named: action){
        displayImage.setImage(image)
    }else{
        displayLabel.setText("No Image " + action)
    }
}

We’ll read a string parameter action which will be our action. First, we display the menu button pressed on the label. Next we assign a UIImage with the same name as our action to image. If image is non-nil, we have a image and we set the image in displayImage. If the value of Image is nil, we didn’t find the image, and we display  a No Image message.

Change our three action methods to this:

@IBAction func cameraPressed() {
        displayAction("Camera")
    }
    @IBAction func undoPressed() {
        displayAction("Undo")
    }
    @IBAction func cancelPressed() {
        displayAction("Cancel")
    }

We tell the displayAction method which action to display. It appears we are ready to run.

Run the App, Find a Bug

Build and run. We start with a blank watch:

Photo Jul 07, 5 56 37 AM

Force touch on the watch or click and hold on the simualtor. We get our menu:

Photo Jul 07, 5 56 43 AM

Notice the camera has shades of gray in the lens and top of the camera due to our alpha channel changes.  Press the camera menu item and we get this.

Photo Jul 07, 5 56 50 AM

We do run the camera action but the watch can’t find an image.  I made this error intentionally to underline a very common mistake with WatchKit. There are two asset libraries: One for the app and one for the extension. If you reference an image in the interface, you need the image in the WatchKit App bundle. If you reference the image in your code, then you need to reference the image in your WatchKit Extension.

2015-07-07_06-24-37

Just like we did above to add the icons to the app,  add the icons to the extension.

2015-07-07_06-28-24

Build and run. Open the menu with a force touch, and select the camera.

Photo Jul 07, 6 36 44 AM

 Now we have a camera. Notice the camera is yellow like the original image, since this is an image, not an icon. The lens and camera top are darker in color. That again is the alpha value. They are darker than the body of the camera because they are letting the black background color through. This was the reason I made the icon yellow. If you plan to reuse your icons as images in your extension, make them a color that contrasts well with black.  If you made black icons, they would be black on black images, rendering them invisible. You might  spend hours looking at code trying to find a bug of missing images when your images are merely camouflaged.

In our next WatchKit lesson we’ll start working with the last of the WatchKit controls: table views.

The Whole Code

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

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    @IBOutlet weak var displayLabel: WKInterfaceLabel!

    @IBOutlet weak var displayImage: WKInterfaceImage!

    @IBAction func cameraPressed() {
        displayAction("Camera")
    }
    @IBAction func undoPressed() {
        displayAction("Undo")
    }
    @IBAction func cancelPressed() {
        displayAction("Cancel")
    }

    func displayAction(action:String){
        displayLabel.setText(action)
        if let image = UIImage(named: action){
            displayImage.setImage(image)
        }else{
            displayLabel.setText("No Image " + action)
        }
    }
}