Tag Archives: Apple Watch

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.

Make a WatchOS 3 Haptic Catalog with a Picker

How does the Apple Watch communicate to the user when they are not looking at the watch face? That is done with haptics. Haptics are sounds and taps letting the user know something is happening. In this lesson, I’ll explain to you how to use haptics in Watch OS applications by creating a catalog of all the haptics using a WatchOS picker.

The picker control is an object for selection of multiple items. You can use the picker to display either full-screen images or text for selection. In this lesson, I’ll keep it simple with a text-based picker.

Make a New Project

Create a new WatchOS project called HapticPickerDemo. Use Swift as the language, uncheck Notifications, and keep the device Universal.  Open the interface.storyboard file in the WatchKit App group.

Find the picker in the object library. Drag a picker to the interface.

2016-08-27_07-01-54

With the picker selected you’ll see four attributes of a
picker.  The Focus Style and Indicator give you visual elements to highlight the picker. The most important attributes are Style and of course Enabled. You have three style choices: List, Stack and Sequence. List is a text-based picker. Stack and Sequence is an animated and non-animated image picker.

2016-08-27_07-09-18

Set your attributes to the illustration above.  Set the picker’s Style  to List. Set the Focus Style to  Outline with Caption. This will show an outline around the picker with a caption at the top. This caption is context sensitive to the picker selection. If you’ve set a complication on a watch, you are familiar with this focus style, such as  the black on green lettering  for weather in this setting:

2016-08-27_07-02-50

From the Object Library, add  a button under the picker. Title the button Play Haptic.

2016-08-27_07-04-29

Close the attributes and navigation panels. Open the assistant editor.

Make an outlet for the picker by control dragging from the picker to the InterfaceController class

@IBOutlet var picker: WKInterfacePicker!

Make an action for the picker’s action,changing Outlet to Action in the popup after you control drag.

 func pickerDidChange(_ value: Int) {
    }

Make an action for the button’s action, changing Outlet to Action in the popup after you control-drag.

 func playHaptic(_ value: Int) {
    }

Playing a Haptic

There’s two parts to a haptic you need to know. There is a play function [in WatchOS 2 playHaptic()]  you call on the current device object called by
WKInterfacedevice.current()object [in WatchOS 2 WKInterfaceDevice.currentdevice() ]. The play method has one parameter of type WKHapticType, which is an enumeration of haptic types.

  • notification tells the user there is a notification,
  • directionUp indicates an upward value,
  • directionDown indicates a downward value,
  • success indicates the successful completion of a task,
  • failure a failed task,
  • retry tells the user to retry,
  • start the beginning of an action,
  • stop the end of an action,
  • click is a very slight click, which you probably won’t hear but probably feel on a watch.

Close the Assistant editor.  Go to InterfaceController.swift in the App extension group. To make the button play a .success haptic change the playHaptic action to this:

 
func playHaptic(_ value: Int) {
    WKInterfaceDevice.current().play(.success)
}

Build and run using the 38mm simulator. Press the Play Haptic Button and  you get a sound.

In the simulator, you don’t get the taps you’ll get if you were using the watch. If you have a watch, run this on your watch and you’ll feel the tap.

Implementing the Picker

There is no documentation on what the taps feel like. It’s hard to describe in words,you have to feel it. In the rest of this lesson we’ll use WatchKit’s WKInterfacePicker to make a selectable catalog of haptics you can run on an Apple Watch.

Pickers have one parameter. Since pickers hold the list in a sequence, the parameter value is the index of that sequence. Delete all life cycle methods but willActivate. Add the list of haptic types exactly in the order above for the picker as an array.

let titles = [
    "notification","directionUp",
    "directionDown","success",
    "failure","retry",
    "start","stop","click"
]

Pickers will not use these arrays directly. Pickers get their selections from a WKPickerItem object. Code will create an array of WKPickerItem, and set that as the picker’s items. WKPickerItem has several properties to make the picker flexible.

  • title – A String? to use in a list
  • caption – A String? used as a caption for item in a list
  • accessoryImage – A Small WKImage? to display next to title in a
    list or as an alternate to text as in small complication setting.
  • contentImage – In a Stacked or Image Sequence style, a
    WKImage?

The picker has a setItems method which takes the array of picker
items to make the list, stack, or sequence of images, depending on the display style. For every picker you create, you build a function that iterates through the arrays, adding the elementsts as pickerItems. Add a function refreshPickerItems:

 func refreshPickerItems(){
 }

Add an empty array of picker items to the function.

 
func refreshPickerItems(){
    var pickerItems:[WKPickerItem] = []
}

Add a loop iterating through the titles.

 
func refreshPickerItems(){
    var pickerItems:[WKPickerItem] = []
    for item in titles{
    }
}

In the loop creates an instance of WKPickerItem. Add to pickerItem the title  from the corresponding element of the arrays. Add the literal caption Haptic. (If you want to experiment, with captions, try coding it as "Haptic-" + item ) Add the picker item to the pickerItems array.

func refreshPickerItems(){ 
    var pickerItems:[WKPickerItem] = [] 
    for item in titles{ 
        let pickerItem = WKPickerItem()
        pickerItem.title = item
        pickerItem.caption = "Haptic"
        pickerItems += [pickerItem]
    }
}

Once the pickerItems array is complete, set the items in the picker.

func refreshPickerItems(){ 
    var pickerItems:[WKPickerItem] = [] 
    for item in titles{ 
        let pickerItem = WKPickerItem()
        pickerItem.title = item
        pickerItems += [pickerItem]
    }
    picker.setItems(pickerItems)
}

In willActivate, call this function to refresh the list.

override funcwillActivate() {
    super.willActivate()
    refreshPickerItems()
}

Selecting a Picker Entry

If you were to run the project now, you would see the picker displayed correctly. You could not select anything though. That happens in the action. The pickerDidChange action has a Int parameter value, which is the current index on the picker.

I told you earlier to be careful getting the order correct in the titles  array.  This is why. Since WKHapticType is a enum you can select the type by the rawValue of the enum. That raw value matches the value parameter. Before we try this, add a property hapticType to store the type.

varhapticType:WKHapticType = .notification

I’ve initially set this to .notification. In the pickerDidChange action set the hapticType property with the raw
value we can get the value parameter.

@IBAction func pickerDidChange(_ value: Int) { 
    hapticType = WKHapticType(rawValue:value)!
}

Playing the Haptic

The last step is to play the haptics. To the action  pickerDidChange  add a .click haptic

WKInterfaceDevice.current().play(.click)

This demonstrates one use for haptics, feedback to the user.  In the action playHaptic, change the .success haptic to the property hapticType

 @IBAction fund playHaptic() {
        WKInterfaceDevice.current().play(hapticType)
    }

Run in the 38mm simulator. Select a haptic and press Play Haptic. You’ll hear the chime for it.

If you have a watch, load it on your watch, and try the application. When you tap Play haptic on the watch you will get the watch tap as well.

Don’t over use Haptics. Unless your app is an interval timer, don’t  have one go off every second or minute. That just gets annoying. Use haptics at the right times to attract a user’s attention to their Apple watch or give instant feedback.

The Whole Code

//
//  InterfaceController.swift
//  HapticPickerDemo WatchKit Extension
//
//  Created by Steven Lipton on 8/26/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {

    @IBOutlet var picker: WKInterfacePicker!
    
    //List of WKHapticType in rawValue order
    let titles:[String] = [
        "notification","directionUp",
        "directionDown","success",
        "failure","retry",
        "start","stop","click"]
    
    var hapticType:WKHapticType = .notification
    
    //Use the index from the picker as the rawValue for the WKHapticType
    @IBAction func pickerDidChange(_ value: Int) {
        hapticType = WKHapticType(rawValue: value)!
        WKInterfaceDevice.current().play(.click)
    }
    
    
    @IBAction func playHaptic(){
        WKInterfaceDevice.current().play(hapticType)
    }
    
    
    func refreshPickerItems(){
        var pickerItems:[WKPickerItem] = []
        for item in titles{
            let pickerItem = WKPickerItem()
            pickerItem.title = item
            pickerItem.caption = "Haptic"
            pickerItems += [pickerItem]
        }
        picker.setItems(pickerItems)
    }
    
    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        refreshPickerItems()
       
        
    }

}

Accessing the Digital Crown in WatchOS

On the Apple Watch, the digital crown seems to be a great way to control your watch. the Slider and picker controls do use it, but direct developer use was prohibited in Watch OS2. In a nice change, Watch OS3 does. In this lesson, we’ll show how to get data from the digital crown in your applications.

Start a new Xcode WatchOS project DigitalCrownDemo. Deselect the notifications and save the project.

2016-08-22_06-26-21

Add three labels to the watch interface. Title them as follows:

2016-08-22_05-51-19

In the assistant editor, Connect the labels to outlets like this:

@IBOutlet var statusLabel: WKInterfaceLabel!
@IBOutlet var valueLabel: WKInterfaceLabel!
@IBOutlet var rpsLabel: WKInterfaceLabel!

We’ll track two data values the rotation speed and a value based on the change in value from the watch. Add them to the controller

var rps = 0.0
var value = 0.0

Add a update method for the display:

func updateLabels(){
    valueLabel.setText(String(format:"Value:%1.3f",value))
    rpsLabel.setText(String(format:"RPS:%1.3f rps",rps))
}

The crown uses a delegate. In interface controller, Adopt the <code>WKCrownDelegate</code>
class InterfaceController: WKInterfaceController,WKCrownDelegate {

The crown must have focus to return information. In awake(withContext) set the focus for the crown

    
override func awake(withContext context: AnyObject?) {
    super.awake(withContext: context)
    crownSequencer.focus()
    crownSequencer.delegate = self
}

The interface controller has a property crownSequencer that monitors the activity on the crown. We set the focus to crownSequencer and set the delegate method locations to this class.

Add the crownDidRotate delegate method, which returns values when there is a change in the rotation of the crown.

//MARK: Delegates
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
   value += rotationalDelta
   rps = (crownSequencer?.rotationsPerSecond)!
   statusLabel.setText("Moving")
   updateLabels()        
}

We have two arguments in this delegate method: the crownSequencer and the rotationalDelta. The Rotational debts is the change between the current and last position of the crown's rotation. One of the properties of the crownSequencer is rotations per second, giving a speed to the rotations. The code places the rotational delta and the rotations per send into a string for output to the watch. Most likely we'll want a position from the rotational delta, so we've added it to value.

Add the crownDidbecomeIdle delegate method

 
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
    rps = (crownSequencer?.rotationsPerSecond)!
    statusLabel.setText("Stopped")
    updateLabels()
}

This delegate method fires when the crown stops. The code prints that the crown stopped and the last rotational speed before it did.

Run the code on the 42mm simulator. To use the crown hardware in the simulator, After a single click on the background of the watch face drag up and down two fingers. Stopping and starting the drag, you will see the display change.

The crownSequencer is smart enough to know your watches orientation from your settings. Up is always a positive value and down is always negative. there's a lot to do with this, from making new controls for your watch to an input for small games with sprite kit and scene kit.

The Whole Code

//
//  InterfaceController.swift
//  DigitalCrownDemo WatchKit Extension
//
//  Created by Steven Lipton on 8/22/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController, WKCrownDelegate {
    @IBOutlet var statusLabel: WKInterfaceLabel!
    @IBOutlet var valueLabel: WKInterfaceLabel!
    @IBOutlet var rpsLabel: WKInterfaceLabel!

    var value = 0.0
    var rps = 0.0
    
    func updateLabels(){
        valueLabel.setText(String(format:"Value:%1.3f",value))
        rpsLabel.setText(String(format:"RPS:%1.3f rps",rps))
    }
    
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        crownSequencer.focus()
        crownSequencer.delegate = self
    }
    //MARK: Delegates
    func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
        value += rotationalDelta
        rps = (crownSequencer?.rotationsPerSecond)!
        statusLabel.setText("Moving")
        updateLabels()
        
    }
    
    func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
        rps = (crownSequencer?.rotationsPerSecond)!
        statusLabel.setText("Stopped")
        updateLabels()

    }

    
}

Adding Actions to iOS and WatchOS Local Notifications

Even if you never want to make an app for the Apple Watch, there’s one place you might want to think about supporting: Notifications. In a previous lesson, we explored how to make local notifications for iOS devices. That lesson ended with a free bonus: If a phone is sleeping and you have an Apple Watch, the notification will go to the watch.

Starting in iOS8, notifications get one big change: they can have actions of their own. On phone, tablet or watch this gives you more flexibility in how to respond to a notification. Suppose you had an app that after an appointment comes up, nags you about it every ten seconds until you stop the nagging. You could of course go into the app and shut it off, but it might be easier to have a stop nagging button on the notification. In this lesson, we’ll make that stop nagging button first for iOS devices, then for the Apple Watch. Along the way we’ll discover why watch notifications are the one place iOS developers should seriously think about for their phone apps.

Making a Demo Program – A Review of Notifications

In a previous lesson we went into detail about local notifications. We’ll be using a lot of what was learned there. In this tutorial, we’ll build a simple app to make a series of notifications similar to the HIIT app at the end of that lesson. We’ll use this as a review and quick introduction to local notifications if you are not familiar with them.

Since we will eventually get to some watch related notifications, make a new project using the iOS with Watch OS App template.

2016-04-26_15-32-23

Name the project PhoneWatchNotificationDemo and make sure watch notifications is checked on and everything else is off.

2016-04-26_15-32-22

Once you save the project, add a new file by pressing Command-N called NaggyNotification subclassing NSObject. Add these properties to the class

class NaggyNotification: NSObject {
    private let basicNags = ["Nag", "Nag nag","Naggity nag"]
    private let emojiNags = ["😀","😉","🙄","😒","😠","😡"]
    private var nags = ["Nag", "Nag nag","Naggity nag"]
    private var counter = 0
    private var timer = NSTimer()
    private var backgroundTask = 0
    private var localNotification = UILocalNotification()
    var nagging:Bool {return timer.valid } //read only
}

If you don’t know how to type emojis, press Control-Command-Spacebar to get the symbols palette.

We are going to do something insane a little later with this class. To protect against some of the problems with that, all but one of our properties is private and inaccessible outside the class. There is one read-only property nagging which uses a computed read-only value.

Most of these properties we’ll get to. We’ll start with the most important. The localNotification will contain our notification. Add the following method to present it immediately:

    func presentNotificationNow(){
        UIApplication.sharedApplication().presentLocalNotificationNow(localNotification)
    }

There are two ways to present notifications. We can present immediately as we do with the presentLocalNotificationNow method above or at some scheduled time in the future with the scheduleLocalNotification method. We could schedule that time using the fireDate property of UILocalNotification, but that presents a problem. We can only schedule 64 notifications, which in a nagging app might be too little space. We will take a different approach: Use a NSTimer loop in the background. Add the following method to start a timer:

func startTimer(timeInterval:NSTimeInterval){
        if !timer.valid{ //prevent more than one timer on the thread
            counter = 0
            backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
            timer = NSTimer.scheduledTimerWithTimeInterval(
                timeInterval,
                target: self,
                selector: #selector(timerDidFire),
                userInfo: nil,
                repeats: true)
        }
        
    }

We do three things in this code. We reset a counter for counting how many nags the user has been subjected to, then set this task into the background. While on a simulator you can leave things in the background without this, your app will suspend operation without beginBackgroundTaskWithExpirationHandler on a live device. Finally we start a timing loop, getting the timing interval from the parameter timeInterval. The timing loop ends on a selector timerDidFire. Let’s add the code for timerDidfire. Add this to the class:

    func timerDidFire(timer:NSTimer){
            counter += 1
            let currentNag = counter % nags.count //cycle through messages
            let nagString = String(format:"# %i %@",counter,nags[currentNag])
            localNotification.alertBody = nagString
            localNotification.alertTitle = "Naggy Notification"
            localNotification.category = "Nags"
            localNotification.soundName = UILocalNotificationDefaultSoundName
            localNotification.userInfo = ["NagNum":counter,"NagString":nags[currentNag]]
            presentNotificationNow()
    }

If you are not familiar with timing loops you might want to refer to this lesson. Basically, the timer object is set to run the timerDidFire function every timeInterval seconds. Once in the timerDidFire function, we increment counter and compose a string we’ll post to the notification. The property alertBody of UILocalNotification sets the text for the notification. We also set the sound to the default sound, which will also set off vibrations and haptics. For later use, we all send a dictionary containing our count and our current nag as userInfo we can use later in this lesson. Finally we present the notification.

We’ll also need a way to shut down the timer. Add this method to your code:

func stopTimer(){
        timer.invalidate()
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
}

We shut down the timer and remove the task from the background.

Now for the small, but insane bit of code. Add this above the class declaration:

let naggingNotification = NaggyNotification()

class NaggyNotification: NSObject {
...

A variable declared outside a class is global. Global values or instances can be used anywhere in the target code. There are other, better ways of handling the NaggyNotification class, but I could write full tutorials on them. As we’ll see, this instance will show up in several classes. This is a simple way to get it into all of them. As I always do when I use a global, I warn people not to use globals except in special circumstances. They are notoriously hard to track down in code. It’s why I made all the properties private, only methods are accessible to other classes, which are easier to deal with.

In order to use a notification, you must tell the application you will be using the notification in the AppDelegate.swift file. Go to the AppDelegate class Change the application:didFinishLaunchingWithOptions:launchOptions: method to this:

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound],
                categories: nil
            )
        )
        return true
    }

We register the notification settings for the two types of notifications: Alerts(or banners) and sounds(or haptics and vibrations).

Our app has all the internal parts set, we need a user interface. We’ll make a very simple one of a single button. Go to the ViewController.swift file. Change ViewController to this:

class ViewController: UIViewController {  
    @IBOutlet weak var nagButton: UIButton!
    @IBAction func nagButton(sender: UIButton) {
    }
 }

We’ll have one button that we’ll assign a outlet and action to. Change the nagButton action to this:

@IBAction func nagButton(sender: UIButton) {
    if naggingNotification.nagging{
       naggingNotification.stopTimer()
    }else{
       naggingNotification.startTimer(10.0)
    }
      stateDidChange()
}

This code uses the naggingNotification read-only property nagging to determine if the timer is on. We toggle the timer depending on the result, using the naggingNotification methods stopTimer and startTimer. We’ve set up this demo with a 10 second timer.

Our last statement calls a method we have yet to define stateDidChange(). We’ll update the button title with this. Add this code to ViewController:

func stateDidChange(){
    if !naggingNotification.nagging{
       nagButton.setTitle"Nag", forState: .Normal)
    }else{
       nagButton.setTitle("Stop Nagging", forState: .Normal)
    }
}

We have our code. Go to the storyboard. Drag one button to the center of the storyboard. Title it Nag, and set the font to 46 point Bradley Hand.

2016-05-01_07-49-18

In the lower right corner of the storyboard, you will find the auto layout controls. With the Nag button selected, click the align alignment icon icon to get the align menu. Towards the bottom of this menu you will find the center Horizontally in Container and Vertically in container settings. Click both on with values of zero. Set the Update frames to Items of New constraints.

2016-04-26_15-32-25

Open the assistant editor. Drag from both circles in the code to the button to connect the action and the outlet to the button.  Close the assistant editor.

Before we test this, launch both the iPhone and watch simulators. If you have never launched these before running an app, check the directions here Especially with the watch, it helps to have them running before you run an app on them.

Build and run using a iPhone 6s plus simulator. You get the notification message the first time you run this:

2016-04-29_10-09-50

Press OK to allow notifications.You get a simple screen of a single button:

2016-05-01_07-54-37

Tap the Nag button.

2016-05-01_07-54-44

The button toggles but doesn’t do more than that. we need to be outside the app to see a notification.

Press Command-Shift-H or on the hardware simulator menu, Hardware>Home. Wait a few seconds and you will see this:

2016-04-29_10-09-53

Swipe down from the status bar once the banner disappears. Select Notifications and you will see the notifications appear.

2016-04-29_10-09-54

Tap one of the notifications and you are back in the app. Tap Stop Nagging and the notifications turn off.

Adding Categories and Actions to your Notifications

Introduced in iOS8, Notifications  now have their own actions, which appear as buttons on a notification. Often you’ll find these in the AppDelegate.  I’ll however keep everything on one class for the notification. Actions are of class UIUserNotificationAction and it’s subclass UIMutableUserNotificationAction. Since you can’t change anything in a UIUserNotificationAction, we define actions in UIMutableUserNotificationActions. We’ll define two actions, one to stop the timer and one to change the nag message to emoji. In the NaggyNotification class add the following method:

func makeActions()-> [UIMutableUserNotificationAction]{
    let stopAction = UIMutableUserNotificationAction()
    stopAction.title = "Stop"
    stopAction.identifier = "StopAction"
    stopAction.destructive = true
    stopAction.authenticationRequired = true
    stopAction.activationMode = .Foreground
     
    let moreAction = UIMutableUserNotificationAction()
    moreAction.title = "Emoji"
    moreAction.identifier = "EmojiAction"
    moreAction.destructive = false
    moreAction.authenticationRequired = false
    moreAction.activationMode = .Background
        
    return [stopAction,moreAction]
    }

The title parameter is the title that will appears on buttons of the notification. The identifier is the string used to internally identify the action. You’ll notice that there is no code here to execute the action. We’ll use that identifier to code the action in AppDelegate. We can add a red warning to a button if it is a destructive button, one that deletes something. I used it here as the Stop button, but left destructive as false for the Emoji button. The activationMode has two choices: .Foreground or .Background and indicates if the app will execute the action in the foreground or the background. To maintain security of the user’s phone we can also require an authentication before we execute the action. For actions in the foreground, this is mandatory and activationMode is always true. The statement in our code is redundant.

We return an array of the actions. We will use these actions in a category. Categories contain all information about a specific type of notification. Our category, which we’ll call Nags contains the actions that will show in the nag notification. Add the following code to the NaggyNotification class.

 //MARK: - Categories and Actions
    func makeCategory()-> UIMutableUserNotificationCategory {
        let category = UIMutableUserNotificationCategory()
        category.identifier = "Nags"
        category.setActions(makeActions(), forContext: .Default)
        return category
    }

We make set two properties on a UIMutableUserNotificationCategory. We set an identifier Nags and using the setActions:forContext: method add the actions to the category.

We’ve set up our category and actions. Now to use them. We’ll do that in AppDelegate. Find the method we changed earlier. Change the highlighted lines to add the category to our current code.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) ->; Bool {
        // Override point for customization after application launch.
        let category = naggingNotification.makeCategory()
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound, .Badge],
                categories: [category]
            )
        )
        return true
    }

If we ran our code now, we would have our actions listed in the notification, but nothing would happen. We need to add a callback method to AppDelegate containing the code to execute the actions. Add this to AppDelegate

    func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
        
        if notification.category == "Nags"{
            if identifier == "StopAction"{
                naggingNotification.stopTimer()
            }
            if identifier == "EmojiAction"{
                naggingNotification.toggleAlertMessages()
            }
        }
        completionHandler()
    }
  

The application:handleActionWithIdentifier:forlocalNotification method has a actionIdentifier and a UILocalNotification. for parameters.   The code checks the notification’s title. If it is our notification, we check the action identifier and perform the action we need.

We need one more method back in NaggyNotification to toggle the alert messages. Add this to the code.

func toggleAlertMessages(){
        if nags == basicNags {
            nags = emojiNags
        } else {
            nags = basicNags
        }
    }    

Fixing a Bug Before It Happens

This is all the code we need to run our application. However, there’s one bug that we’ll fix first. The problem is the Stop action when we are in the background. It will stop the the timer and shut down the nags. The button on our view controller does not know this. With the code as it is written the button still says Stop Nagging when we enter the foreground. We need to update the button any time we do enter the foreground. Find the applicationDidBecomeActive in AppDelegate. This method runs when we need to do that refresh of our user interface. Change it to this

    func applicationDidBecomeActive(application: UIApplication) {
        let rvc = window?.rootViewController as! ViewController
        rvc.stateDidChange() //Update the button
    }

Line 2 of the code gets the view controller. We have the view controller as our root view controller, so it’s easy to access. Once we have a reference to the root controller, we can call its method. statDidChange which checks the status of the timer and changes the title to the correct one.

Build and run.

2016-05-01_07-54-37

Press the nag button. Press Command-Shift-H. Eventually the notification appears:

2016-05-01_07-09-56

Drag the small oval at the bottom of the notification down.

2016-05-01_07-37-48

The actions appear.

2016-05-01_07-11-10

Tap the emoji button.  The next notification you get is an emoji.

2016-05-01_07-11-41

Drag down from the status bar and get the notifications. Swipe right to left on one of the notifications.

2016-05-01_07-15-52

Tap the Stop button. The app stops and opens up to the Nag screen again.

2016-05-01_07-54-37

The Apple Watch for All Developers

While that’s what you need to know for iOS, it’s not the whole story. Unless notifications are turned off by the user of an Apple watch, the watch will receive the notification if the phone is asleep.  Sadly, the watch simulator does not always reflect what happens on the watch.  You can try using the simulator if you  don’t have a watch handy, I’ll be using both since they have slightly different behaviors.

If your app is still running, get the watch and phone  simulator visible

2016-05-01_16-39-08

Start nagging with the phone, then press Command-L to Lock the phone. The simulator goes black. Wait a few seconds,  And you will see the Short-Look Notification appear:

Photo May 01, 9 26 23 AM

A second later, the  Long-Look notification appears.

2016-05-01_16-39-07

You’ll notice the buttons for the actions don’t quite fit on the watch. scroll up and you’ll find one more button.

2016-05-01_16-45-22

Test the emoji button.  Tap it, and the next notification changes to this:

2016-05-01_16-51-29

Press the stop button and you get a blank screen with the time. Actually, you are running a blank watch app.

2016-05-01_16-53-02

 You get slightly different  results with a real phone. Running the app on my phone, I start nagging by pressing the Nag button. I then lock my phone  by pressing the switch. SInce I turned on sounds and used the system sound in my actions, I’ll get a Haptic vibration on my wrist,  and then the notification.

Photo May 01, 9 25 40 AM

The notification includes the Emoji button and a dismiss button, which is cut off.  You can scroll up to get the rest of the dismiss button. On the simulator, we had the stop button, but on the watch it’s missing.

The difference has to do with activationMode. If you have an action with an activationMode of .Foreground the watch does not display it automatically. The foreground mode on the watch starts the watch app associated with this notification. In the simulator it lets us run that blank app, but on a physical watch it knows there is no app and doesn’t show the button.  On the other hand, an activationMode of .Background on the watch sends to the background of the phone. That’s why with no coding, the Emoji button works perfectly. Go to the NaggyNotification class. In makeActions, change our code to put the Stop button in the background

stopAction.activationMode = .Background

Now run the app again and when we lock the phone we get both buttons on the watch:

Photo May 01, 10 05 16 AM

With more buttons, the dismiss button is missing. Drag up and you will find it:

Photo May 01, 10 10 27 AM

Tap the Emoji Button. The next notification changes to emoji

Photo May 01, 10 05 31 AM

Tap the Stop button. On both the live watch and the simulator, the timer stops.  All of our actions work on the watch without a single new line of code.

Notifications will happen on an Apple Watch even if you don’t program for it. For Apple watch developer, this provides one way to launch your watch app. For iOS developers using notifications and actions, it means you have one more thing to think about when writing an app.  Think out what you want to show and not to show  for a watch user. If you are not writing a watch app, you can exclude actions from the watch but not the phone by making the  activationMode go to the foreground.

The whole code

NaggyNotification.swift

//
//  NaggyNotification.swift
//  PhoneWatchNotificationDemo
//
//  Created by Steven Lipton on 5/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

//Global variable -- not always a good idea,
// but makes running this code much simpler
//
let naggingNotification = NaggyNotification()

class NaggyNotification: NSObject {
    
    //All properties are private or read only 
    //Good insurance for using the global instance
    //nothing gets messed up. 
    
    private let basicNags = ["Nag", "Nag nag","Naggity nag"]
    private let emojiNags = ["😀","😉","🙄","😒","😠","😡"]
    private var nags = ["Nag", "Nag nag","Naggity nag"]
    private var counter = 0
    private var timer = NSTimer()
    private var backgroundTask = 0
    private var localNotification = UILocalNotification()
    var nagging:Bool {return timer.valid } //read only
    
    func presentNotificationNow(){
        UIApplication.sharedApplication().presentLocalNotificationNow(localNotification)
    }
    func toggleAlertMessages(){
        if nags == basicNags {
            nags = emojiNags
        } else {
            nags = basicNags
        }
    }
    
    func startTimer(timeInterval:NSTimeInterval){
        if !timer.valid{ //prevent more than one timer on the thread
            counter = 0
            backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
            timer = NSTimer.scheduledTimerWithTimeInterval(
                timeInterval,
                target: self,
                selector: #selector(timerDidFire),
                userInfo: nil,
                repeats: true)
        }
        
    }
    
    func timerDidFire(timer:NSTimer){
        counter += 1
        let currentNag = counter % nags.count //cycle through messages
        let nagString = String(format:"# %i %@",counter,nags[currentNag])
        localNotification.alertBody = nagString
        localNotification.alertTitle = "Naggy Notification"
        localNotification.category = "Nags"
        //localNotification.alertTitle = localNotification.category
        localNotification.soundName = UILocalNotificationDefaultSoundName
        localNotification.userInfo = ["NagNum":counter,"NagString":nags[currentNag]]
        presentNotificationNow()
        
    }
    
    func stopTimer(){
        timer.invalidate()
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
    }
    
    //MARK: - Actions and Categories
    func makeActions()-> [UIMutableUserNotificationAction]{
        let stopAction = UIMutableUserNotificationAction()
        stopAction.title = "Stop"
        stopAction.identifier = "StopAction"
        stopAction.destructive = true
        stopAction.authenticationRequired = true
        stopAction.activationMode = .Background
        
        let moreAction = UIMutableUserNotificationAction()
        moreAction.title = "Emoji"
        moreAction.identifier = "EmojiAction"
        moreAction.destructive = false
        moreAction.authenticationRequired = false
        moreAction.activationMode = .Background
        
        return [stopAction,moreAction]
    }
    
    func makeCategory()-> UIMutableUserNotificationCategory {
        let category = UIMutableUserNotificationCategory()
        category.identifier = "Nags"
        category.setActions(makeActions(), forContext: .Default)
        return category
    }
}

AppDelegate.swift

//
//  AppDelegate.swift
//  PhoneWatchNotificationDemo
//
//  Created by Steven Lipton on 5/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        let category = naggingNotification.makeCategory()
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound],
                categories: [category]
            )
        )
        return true
    }
    
    func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
        
        if notification.category == "Nags"{
            if identifier == "StopAction"{
                naggingNotification.stopTimer()
            }
            if identifier == "EmojiAction"{
                naggingNotification.toggleAlertMessages()
            }
        }
        completionHandler()
    }
    

      func applicationDidBecomeActive(application: UIApplication) {
        let rvc = window?.rootViewController as! ViewController
        rvc.stateDidChange() //Update the button
    }

   
}



ViewController.swift

//
//  ViewController.swift
//  PhoneWatchNotificationDemo
//
//  Created by Steven Lipton on 5/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit


class ViewController: UIViewController {
    @IBOutlet weak var nagButton: UIButton!
    @IBAction func nagButton(sender: UIButton) {
        if naggingNotification.nagging{
            naggingNotification.stopTimer()
        }else{
            naggingNotification.startTimer(10.0)
        }
        stateDidChange()
    }
    func stateDidChange(){
        if !naggingNotification.nagging{
            nagButton.setTitle("Nag", forState: .Normal)
        }else{
            nagButton.setTitle("Stop Nagging", forState: .Normal)
        }
    }
}

The Complete Table Tutorial for WatchOS2

In last week’s tutorial of this series, we made a simple dynamic table for the Apple Watch and demonstrated static scrolling tables.Based on some running pace data, we displayed the pace I ran at the mile splits. Many apps will need more than what we discussed. Apps might navigate from the table, insert and delete entries and select entries. In this lesson we’ll also introduce menus and text input.

Make a New Project

We’ll review what we did in the basic tables lesson by rebuilding the application with a few changes. If you built that app, you can modify it and save yourself some time. Along the way I’ll We’ll leave out some of the explanation this time, so if you want more clarification go to the last lesson. Make a new project using the WatchOS application template iOS App with WatchOS App Name the project SwiftWatchOS2TableDemo, 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 Controls

On the Interface.storyboard, add two buttons and a label. Make the button backgrounds Medium Blue(#8080FF). Title the top button Bottom and the bottom button Top.

2016-03-17_07-28-43

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

Add two labels to the bottom of the watch interface. The interface will expand as you add them in. Make their text look like this

2016-03-24_07-17-17

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

2016-03-24_06-57-45

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-24_07-16-35

Go to the storyboard and drag out an interface. Drag two labels to the interface. On the upper label, change the color to Yellow(#FFFF00), and a title of Average Pace. Make both labels have a width Relative to Container. Make the white one Align Center, with a title of 00:00:00. Click the view controller icon and set the title and identifier to Info. When done, you should have an interface that looks like this one.

2016-03-24_07-14-52

Adding and Connecting View Controller Classes

We have three view controllers we’ll need to hook up. We have the new Info interface, the row controller and the main InterfaceController

Connecting the Info controller

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

2016-03-24_07-25-52

Go to the storyboard. Click the view controller icon on the Info interface. In the Identity inspector, set the Class to InfoInterfaceController. Open the assistant editor. Control drag from the Info Interface to the code to make this outlet:

 @IBOutlet var paceLabel: WKInterfaceLabel!

Connecting the Table Row Controller

Tap Command-N to make a new file. Create a new WatchKit Class named TableRowController. Subclass NSObject for this file. Save the file in the WatchKit Extension Group. Row controllers don’t do a lot. Typically they contain the outlets for the controls in the row controller. In the code that appears, add two outlets for WKInterfaceLabel, splits and time.

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

Go to the storyboard. Click on the row controller named row in the document outline. In the identity inspector, change the Class to TableRowController. 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. From the circles to the left of the outlets, connect the outlets to the controls on the storyboard by dragging from the circle next to the code to the control. We’ve now connected the row controller to the storyboard.

Setting the View Controller

Change the assistant editor back to automatic and InterfaceController.swift should be back in the assistant editor. Add the following outlets and actions to the code:

@IBOutlet var totalTimeLabel: WKInterfaceLabel!
@IBOutlet var avgPaceLabel: WKInterfaceLabel!
@IBOutlet var table: WKInterfaceTable!
@IBAction func toBottomAction() {
    table.scrollToRowAtIndex(table.numberOfRows - 1)
}
@IBAction func toTopAction() {
    table.scrollToRowAtIndex(0)
}

Connect the controls to the outlets and actions. You’ll notice we included some code in the buttons. This will scroll to the top and bottom of the table using the scrollToRowAtIndex method. As we discussed in the previous lesson, we can place controls before and after the table as headers and footers. Close the assistant editor.

Adding a Model Class

We’ll use an array named data to hold our values. Add a file by pressing Command-N. Make a new Watchkit Class subclassing NSObject named RunData . When you save the class, be sure that you are saving in the extension group. When the code appears, add the following code:

class RunData: NSObject {
    var data = [654,862,860,802,774,716,892,775,748,886,835]
    var count:Int {
        get{
            return data.count
        }
    }
    //A function to change the seconds data from an integer to a string in the form 00:00:00
    class func paceSeconds(pace:Int) -> String{
        let hours = pace / 3600
        let remainingSeconds = pace % 3600
        let minutes = remainingSeconds / 60
        let seconds = pace % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)

    }
        func totalTimeFromSplit(split:Int, toSplit:Int) -> Int{
        var total = 0
        for index in split...toSplit{
            total+=data[index]
        }
        return total
    }
    
    func avgPaceFromSplit(split:Int, toSplit:Int) -> Int{
        let count = ((toSplit) - split) + 1
        return totalTimeFromSplit(split, toSplit: toSplit) / count
    }

We done several things to this model. We added the array data. For ease in use, we added the property count. We added a conversion method from seconds to a string. We’ll find that this method paceSeconds is very useful as a class method, so we made it one. We also added two methods to calculate average paces and total times for a range of splits.

Make the Table

Go to the InterfaceController.swift code. Add your model just under the outlets.

 let runData = RunData()

Add the following method to the class

func tableRefresh(){
    table.setNumberOfRows(runData.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:" + RunData.stringFromSeconds(runData.data[index])
        row.splits.setText(rowString)
        row.time.setText(paceString)
    }
    let totalPace = runData.totalTimeFromSplit(0, toSplit: runData.count - 1)
    let avgPace = runData.avgPaceFromSplit(0, toSplit: runData.count - 1)
    totalTimeLabel.setText(RunData.stringFromSeconds(totalPace))
    avgPaceLabel.setText(RunData.stringFromSeconds(avgPace))      
}

As discussed last lesson. WatchOS does away with all the delegates based table stuff. In its place is an array of table row controllers. We set the number of rows and the type of row in the table with setNumberOfRows. We loop through the array, assigning the current row to row and populate it with data. After the loop, we set the totals labels for the run. Add the method tableRefresh to viewWillAppear

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

Select a simulator of a 42mm watch. Build and run.

Photo Mar 29, 6 24 06 AM

Selecting Rows in a Table

Selecting row in a table is easy. You override the method table(table: didSelectRowAtIndex rowIndex:) method.
We’ll take our average pace and display it on a separate page when we select a row in the table. Add this selection method:

//table selection method
   override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        //build a context for the data
        
        let avgPace = runData.avgPaceFromSplit(0, toSplit:rowIndex)
        presentControllerWithName("Info", context: avgPace) //present the view controller
    }

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

class InfoInterfaceController: WKInterfaceController {

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

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

Build and run.

Click on the split for 5 miles,

Photo Mar 29, 6 24 06 AM

and you get an average pace for the split.

Photo Mar 29, 6 24 09 AM

Click Info to return to the table.

Adding a Context Menu

In the rest of the lesson, we’ll add a few new functions to control the table. This is a good place to use a context menu. Context menus will appear when you force touch on the watch. In the Object Library, find the context menu.

2016-03-29_06-33-56

On the storyboard, drag a menu from the object library to the table controller, and drop on top of the controller

2016-03-29_06-34-40

It disappears. You can only see it in the document outline. If the document outline is not open, open it by clicking the icon in the lower left corner of the storyboard.

2016-03-29_06-43-20

In the document outline, select the menu. In the attribute inspector, change the items to 3.

2016-03-29_06-35-30

Click on the arrow next to the menu. The document outline changes to have three menu Items.

2016-03-29_06-35-52

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

2015-08-11_05-57-03

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

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

Close the assistant editor for now.

Build and run. On a watch, hold down on your watch. In the simulator,  you’ll need to change the pressure. From the drop down menu for the simulator, select Hardware>Force Touch Pressure>Deep Press or use the keyboard combination Shift-Command-2

2016-03-29_06-50-24

Now give a click on the watch face. The menu should appear:

Photo Mar 29, 6 55 00 AM

Set the simulator back to Shallow Press (Shift-Command-1) to select a button.

Adding Reset

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

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

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

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

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

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

Selecting the Row

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

var selectedRow:Int! = nil

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

 selectedRow = rowIndex //for use with insert and delete

Adding An Alert

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

2015-08-11_06-59-51

Add two labels. Use an exclamation emoji at 56 point in the bottom one and The text No Splits were Selected. Set the width to Relative to Container and align it Centered. Add text to the label so the interface looks like this:

2016-03-29_13-37-55

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

Deleting Rows From a Table

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

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

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

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

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

Photo Mar 31, 6 27 47 AM

Go back to the table and select the 9 mile.

Photo Mar 31, 6 28 25 AM

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

Photo Mar 31, 7 35 53 AM

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

Adding a Row to the Table

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

Adding Items  in the Model

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

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

Make a Input Controller

We are going to do a big cheat just to keep this short. We’ll use a text input and select a pace from the selections on the text input. In a real app this is not how to do this, but it demonstrates a text input and saves us from doing a lot of extra work building another view controller with delegates. The right way to do this is build another table with times in it, or to build a controller with three sliders, then use a delegate to get back your input to the InterfaceController class.

Text input controllers are a system generated controller that takes a string array of possible responses. You can set your controller to also select emoji and animated emoji. The user always has the option for using dictation, by pressing the microphone button that appears and returning the dictation as  text. It’s the microphone that’s the problem with this code. A good developer needs to handle the code for a dictation response. In our code we will only handle the selection response.

Go to InterfaceController.swift. In the addRow method add the two following declarations

 //selections for the text input
        let timeSelections = [
            "08:00 480 Seconds",
            "09:00 540 Seconds",
            "10:00 600 Seconds",
            "11:00 660 Seconds",
            "12:00 720 Seconds",
            "13:00 780 Seconds",
            "14:00 840 Seconds",
            "15:00 900 Seconds"]
        var seconds = 0

The timeSelections array contains information we’ll use to select our time.  We’ll use these as selections to display by getting sneaky later. We’ll use the variable seconds to store our final selection.  Add the text input controller under these declarations:

// Present a text controller with suggested times
presentTextInputControllerWithSuggestions(
    timeSelections,
    allowedInputMode: .Plain,
    completion: {(results) -> Void in
// Add the completion code here in the closure 
})

The method presentTextInputControllerWithSuggestions does all of our UI for us. We add our string array timeSelections and we have our selections displayed in a table view. The parameter  allowedInputMode has three choices.  The .Plain we selected will only allow text and no emoji.  The selections .AllowEmoji and .AllowAnimatedEmoji will do exactly as they say – Allow you to have standard emoji or return an animated graphic. We’ll stick to plain text. The last parameter is the closure completion: Add the completion handler in the closure

if results != nil && results!.count > 0 {
//selection made
    let aResult = results?[0] as! String
    let times = aResult.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// we will make an assumption here and no one
// will use dictation. NOT a good idea
// but simplifies the code.
    seconds = Int(times[1])!
}

When the input controller completes, it returns [AnyObject]? into  results. This array will have only one element: our text string.  We extract that string into aResult. Then we get tricky. We turn that string into a string array, using the method componentsSeperatedByCharactersInSet.   I intentionally put a space between the time, seconds and word Seconds. Using the NSCharacterSet  which is all the white space characters, I break the text into a three element array. I know that the third element is the time in seconds, so I convert that to an integer.

We have the input. Add the time to the table:

if selectedRow != nil{
    runData.addItemAtIndex(selectedRow, item: seconds)
} else {
     runData.addItemAtIndex(runData.count - 1, item: seconds)
}
tableRefresh()

Make sure all of that code is inside the closure. The text input runs parallel to the table and wont update our table unless it is all in the closure.  We’ve set up everything. Build and run. Go to the menu and select Add Row. Scroll down and Select the time 12:00 720 seconds

Photo Mar 31, 7 38 19 AM

Tap the bottom button or scroll to the bottom. You will have a mile 12 with the finish data of 12:00.  The data appended to the table since we had no selected split.

Photo Mar 31, 7 41 21 AM

Select Mile 9, which has a 12:28 pace. We’ll find a 13:07 Average Pace,

Photo Mar 31, 6 28 30 AM (1)

Then exit from the average pace view and add a 8:00 pace.

Photo Mar 31, 6 28 55 AM (1)

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

Photo Mar 31, 7 45 36 AM (1)

Some of you have been itching to hit that microphone. If you have a watch to run the app on, go ahead and add another split. Tap the microphone and you get a voice input.

Photo Mar 31, 7 50 45 AM

Say ” Time eight three five seconds”

Photo Mar 31, 7 50 54 AM

Press Done. Time for mile 9 is now 13:55

Photo Mar 31, 7 51 04 AM

Try it again, this time saying “Time four thirty-five seconds”

Photo Mar 31, 7 51 46 AM

We crash since Int() returns nil. The words to/two and for/four confuse dictation.

You can add some protection like this.  Change the integer conversion to this:

// we will make an assumption here and no one
// will use dictation. NOT a good idea
// but simplifies the code.
// if we don't get an integer for the second element do nothing.
if let seconds = Int(times[1]){
    if self.selectedRow != nil{
        self.runData.addItemAtIndex(
             self.selectedRow,
               item: seconds)
    } else {
        self.runData.addItemAtIndex(
            self.runData.count - 1,
             item: seconds)
    }

Comment out the line

//var seconds = 0 

This will only add a row if we have valid data. there’s still several ways that data can be invalid,  such as an array less than two elements, but this solves a lot of them.

Subtotals and Multiple-Row Tables

We’ve looked at  the setNumberowRows:withRowType method to make a table with a single row type. For example we had this:

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

We used the count property of an array with our data to present and give one row type. For a multiple row types we use a different method:

table.setRowTypes(rowTypes)

where rowtypes is an array of strings identifying row types. Row types are the row identifiers in the storyboard for row controllers in a table. I could, for example, have a table set like this in my storyboard:

2016-03-31_08-01-22

I would take each of the groups in this table and identify them as a part of the table: Header, Sub Total, Row, and Total.
If I had 4 data points like this:

  var data = [740,745,750,740]

I could make a row header like this to show the correct type of row by the an element in row type.

var rowTypes = ["Header","Sub Total", "Row","Row","Sub Total", "Row","Row","Total"]

Swift would know how many rows we have here since it is rowTypes.count. If you are sharp and have followed along, you’ll notice the problem there is with this code. In our previous lessons, we would get a row with a loop like this:

 
for var index = 0; index < table.numberOfRows; index++ { 
    let row = table.rowControllerAtIndex(index) as! TableRowController //get the row
    let dataString = String(format:"%02",data[index])
    //Set the properties of the row Controller.
    row.label.setText(dataString)
} //end loop

We get a row, and downcast it to the correct row controller class. We take the data in the array, index to place the data in the row controller’s label. This works fine with a single row type table. Add more than one row type and things get a bit dicey. We don’t know what class or row controller to use, and we don’t know where the data is since there is not a one to one correspondence with the index. In the rowTypes array above, the value of data[0] is in rowtype[2]

Some people will only use a header and a footer.

var rowTypes = ["Header","Row","Row","Row","Row","Footer"]

For those cases you could put some code before and after the loop to handle the header and footer.  A better option is not use a header or footer, and make the header and footer information outside the table like was have our totals in our demo. Subheads and sub footers are not so easy though. How do you know when they exist, and what do you do when they show up in the array? They’ll also mess up correspondence to the data array even more than before.

For all these cases it’s easier to have a switch..case in our loop to parse the type. Read the rowtype array, and then react accordingly.

So far I’ve kept these two broken into two arrays. We could use one array with a class, struct or dictionary. We’ll use the dictionary [String:AnyObject] and have two keys: time and type. We’ll generate an array before we display it with the correct types.

Many runners like to break their runs into 5 kilometer chunks. While not exactly 5 Kilometers(3.11 miles), we can get a close estimate by using 3 miles. Every three miles we’ll add another row with the average pace for the three rows before it.

Change the Storyboard

Go to the storyboard in the App Extension. Select the table. In the attributes inspector, change the number of Prototype Rows from 1 to 2,

2016-03-31_08-05-37

In the document outline, you will see the two row controllers:

2016-03-31_08-08-53

We have one Row and one Table Row Controller. Click on the Table Row Controller in the document outline. In the attribute inspector, change the Identifier for the row controller to Split5K. Also deselect the Selectable check box.

2016-04-01_06-05-10

It will change in the document outline to Split5K.  Select the group in the table controller. Change the height to Size to Fit ContentChange the background color to  Green(#00CC00).

Drag  to the Split5K group on the storyboard a label. Change the width to Relative to Container.   Change the text on the top label to 5K – . Change the font to Subhead, and the text color to Black(#000000). When done, your table should look like this.

2016-03-31_08-20-47

Add TableRowControllers

We need a class for the new row controllers. On the drop-down menu, go to File>New>File or Tap Command-N on the keyboard. Make a new WatchOS class Split5kTableRowController subclassing NSObject. Save the file in the WatchKit extension group.

Go back to the storyboard. In the document outline select the Split5K table row controller. In the identity inspector, change the class to Split5KTableRowController. Open the assistant editor. In the assistant editor you will have to manually specify the Split5KTableRowController.

If not visible, open the outline for the Split5K row so you can see the label. This is one of those times it is much easier to select from the outline than the objects on the storyboard. Control drag the label to the row controller class. Make a label outlet named splits.

Close up the assistant editor.

Coding the Multiple Row View Controller

With a multiple row controllers, the data in the array the table does  not have a 1:1 index relationship since the 5k splits get in the way of the index count for the data.  We solve that by making several arrays which will have a common index. Add this to our code’s  property declarations

var tableTime = [Int]()
var tableRowType = [String]()
var tableSplit = [Int]()

These three arrays hold the data for the table, in the table’s order.  tableTime is the time in seconds. We keep track of the type  in the array tableRowType.  Our last array tableSplit does double duty.  For a row, it has the index of the data array. For a 5K split, it has the distance in kilometers.

The refreshTableData() method

Before we display a table, we’ll construct these three arrays with new data.  Add the following method and code to your InterfaceController class:

func refreshTableData(){
    var rowType = "row"
    var rowTime = 0
    var rowSplit = 0
    let subtotalEntry = 3
    var index = 0
    var counter = 0
    var counter5K = 1
    
// clear  the arrays
        tableTime = []
        tableRowType = []
        tableSplit = []

The variable rowType, rowTime,and rowSplit hold the values we’ll add to the array for each row in the table. Or constant subtotalEntry indicates that every three rows we’ll add a 5K Split row.  We’ll use several counters index, counter, and counter5k to keep track of all the counts and indices. We are going to use a while loop to construct our table, and thus the need for counters.  Add this to the code:

 
//populate the arrays
while index < runData.count{
//every three rows, add a 5k pace row
    if counter == subtotalEntry{
        counter = 0 //reset counting 3 rows
        counter5K += 1
//Add a regular row
            } else {
                index += 1
                counter += 1
            }

The variable counter counts to 3. When it reaches 3, we include a 5K split row, then reset counter. In that row we’ll use the current index to figure out the intervals to average.  To keep track of the distance measure for the 5k splits, we have another counter counter5K. For data rows, we grab the current index and  access the correct element in  runData.data, then increment our counters. Flesh this out by assigning rowType, rowTime,and rowSplit proper values:

//populate the arrays
while index < runData.count{
//every three rows, add a 5k pace row
    if counter == subtotalEntry{
        counter = 0 //reset counting 3 rows
        rowType = "Split5K"
        rowTime = runData.avgPaceFromSplit(
            (index - (subtotalEntry - 1)),
            toSplit: index)
        rowSplit = counter5K * 5
        counter5K += 1
//Add a regular row
    } else {
        rowType = "row"
        rowTime = runData.data[index]
        rowSplit = index
        index += 1
        counter += 1
    }

The variable rowType sets to one of our two controllers: row or Split5k.  We get our data and the index in rowTime and rowSplit respectively. Now add this inside the loop, under the if..else clause:

 //Add the data to the array
            tableTime += [rowTime]
            tableRowType += [rowType]
            tableSplit += [rowSplit]

This appends the data to the three arrays. We end up with three arrays usable by our table.

Modifying the tableRefresh Method

With good data we can modify the tableRefresh method in InterfaceController. In the code so far we had the table.setNumberOfRows method. Comment that out and replace it like this:

//refresh the data
refreshTableData()
//set the rows in the table
//table.setNumberOfRows(
//    runData.count,
//     withRowType: "row")
table.setRowTypes(tableRowType)

Instead of setting the number of rows, we give the table a string array with the table types. When using setRowTypes, I strongly suggest naming everything the same. Your storyboard identifier should be the same as your view controller name and the type name you use here. I used Split5k for all three. The compiler looks for a controller Split5KTableRowController when you have a type of Split5K.  If you are getting nil errors when accessing a table row, this may be the culprit.

We have to deal with both view controllers now. In many cases, it may be more, so I tend to use a switch statement to parse between the different controllers.  Change the for loop to this

for index in 0 ..< table.numberOfRows {
    switch tableRowType[index]{
    case "Split5K":
        let row = 
            table.rowControllerAtIndex(index) 
            as! Split5KTableRowController
        let paceString = 
           String(
               format: "%iK - ",
               tableSplit[index]) + 
           RunData.stringFromSeconds(
               tableTime[index]) 
           + " Pace"
          row.splits.setText(paceString)
    default:
        let row = 
            table.rowControllerAtIndex(index) 
                 as! TableRowController
        let rowString = String(
            format: "Split:%02i miles",
            tableSplit[index] + 1)
        let paceString = "Pace:" +
              RunData.stringFromSeconds(
                   tableTime[index])
         row.splits.setText(rowString)
         row.time.setText(paceString)
      }
}

In our earlier iteration, numberOfRows was set explicitly. In this version,  it is implicit as the size for the tableRowType array sets our size.  In the loop we get tableRowType[index] to parse the row type. For 5K Splits we have one code. In this example, I set us up to turn everything into a row that wasn’t anything else.  You could make a row case and an default case for an error, but I was trying to be compact here.

Notice we changed the code in the original version for the rows  to use the arrays, not index and runData.data

let rowString = String(
    format: "Split:%02i miles", 
    tableSplit[index] + 1)
let paceString = "Pace:" +
    RunData.stringFromSeconds(tableTime[index])

Similarly, we used the arrays in the 5K split code

let row = table.rowControllerAtIndex(index) 
    as! Split5KTableRowController
let paceString = String(format: "%iK - ", 
             tableSplit[index]) + 
     RunData.stringFromSeconds(tableTime[index])
     + " Pace"

 

Build and run.  The splits show up.

Photo Apr 01, 9 38 49 AM

But if you try to select a row, the app crashes.

Selection with Multiple Rows

Why did it fail?  Look at the  didSelectRowAtIndex code.


override func table(
    table: WKInterfaceTable,
    didSelectRowAtIndex rowIndex: Int) 
{
     selectedRow = rowIndex 
let avgPace = runData.avgPaceFromSplit(0, 
     toSplit:rowIndex)
presentControllerWithName("Info", 
     context: avgPace) //present the viewcontroller
}

row data mapping 1We access data from the rowIndex. In a single row table, this is  no problem.  However with a multi-Row table the index does not describe the data. Look at the illustration above. With our code, rowIndex 6 would try to get data from dataIndex 6, which causes an overflow error.

On the other hand, if we have a list somewhere that says row 2 is really row 0 , we are row data mapping pointer 1all set. That’s why TableSplit isn’t  cosmetic, but critical. For rows, it holds the index back to  runData.data.  We need to make only a few changes to get the selection code to work.

Change

selectionIndex = rowIndex

 to this:

selectionIndex = tableSplit[rowIndex]

With this small change, all our add and delete functions will work right too, since they depend on selectionIndex to work with the correct rows.

We also will need a change for the context. Change this

let avgPace = runData.avgPaceFromSplit(0, 
     toSplit:rowIndex)

to this:


let avgPace = runData.avgPaceFromSplit(0,
     toSplit:rowIndex)

Build and run. You can now select a row.

Photo Mar 29, 6 24 09 AM

With this code you can get rather sophisticated with table views in a watch. The question remains if you would want to. A watch is to be looked at for a seconds, not minutes, hence long tables are probably a bad idea. However, if your application calls for them, they are an option.

The Whole Code

Interfacecontroller.swift

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

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {

    @IBOutlet var totalTimeLabel: WKInterfaceLabel!
    @IBOutlet var avgPaceLabel: WKInterfaceLabel!
    @IBOutlet var table: WKInterfaceTable!
    let runData = RunData()
    var selectedRow:Int! = nil
    var tableTime = [Int]()
    var tableRowType = [String]()
    var tableSplit = [Int]()
    
    @IBAction func addRow() {
        //selections for the text input
        let timeSelections = [
            "08:00 480 Seconds",
            "09:00 540 Seconds",
            "10:00 600 Seconds",
            "11:00 660 Seconds",
            "12:00 720 Seconds",
            "13:00 780 Seconds",
            "14:00 840 Seconds",
            "15:00 900 Seconds"]
        // Present a text controller with suggested times
        presentTextInputControllerWithSuggestions(timeSelections,
            allowedInputMode: .Plain,
            completion: {(results) -> Void in
                if results != nil && results!.count > 0 {
                    //Selection made
                    let aResult = results?[0] as! String
                    let times = aResult.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
                    // We will make an assumption here and no one
                    // Will use dictation. NOT a good idea
                    // but simplifies the code.
                    // If we dont get an integer for the second element do nothing.
                    if let seconds = Int(times[1]){
                        if self.selectedRow != nil{
                            self.runData.addItemAtIndex(self.selectedRow, item: seconds)
                        } else {
                            self.runData.addItemAtIndex(self.runData.count, item: seconds)
                        }
                        self.tableRefresh()
                    }
                }
        })
    }
    
    @IBAction func deleteRow() {
        if let row:Int = selectedRow{
            runData.removeItemAtIndex(row)
            tableRefresh()
            selectedRow = nil
        } else {
            presentControllerWithName("No Splits Alert", context: nil)
        }
    }
    
    @IBAction func resetRows() {
        runData.data = RunData.resetData()
        tableRefresh()
        selectedRow = nil
    }
    @IBAction func toBottomAction() {
        table.scrollToRowAtIndex(table.numberOfRows - 1)
    }
    @IBAction func toTopAction() {
        table.scrollToRowAtIndex(0)
    }
    
    func refreshTableData(){
        var rowType = "row"
        var rowTime = 0
        var rowSplit = 0
        
        let subtotalEntry = 3
        var index = 0
        var counter = 0
        var counter5K = 1
        // clear  the arrays
        tableTime = []
        tableRowType = []
        tableSplit = []
        
//Populate the arrays
       while index < runData.count{
//Every three rows, add a 5k pace row
            if counter == subtotalEntry{
                counter = 0 //reset counting 3 rows
                rowType = "Split5K"
                rowTime = runData.avgPaceFromSplit( (index - (subtotalEntry - 1)), toSplit: index)
                rowSplit = counter5K * 5
                counter5K += 1
//Add a regular row
            } else {
                rowType = "row"
                rowTime = runData.data[index]
                rowSplit = index
                index += 1
                counter += 1
            }
//Add the data to the array
            tableTime += [rowTime]
            tableRowType += [rowType]
            tableSplit += [rowSplit]
        }
        
}
    
    func tableRefresh(){
        //Refresh the data
        refreshTableData()
        //Set the rows inthe table
        //table.setNumberOfRows(runData.count, withRowType: "row")
        table.setRowTypes(tableRowType)
        for index in 0 ..< table.numberOfRows {
            switch tableRowType[index]{
            case "Split5K":
                let row = table.rowControllerAtIndex(index) as! Split5KTableRowController
                let paceString = String(format: "%iK - ", tableSplit[index]) + RunData.stringFromSeconds(tableTime[index]) + " Pace"
                row.splits.setText(paceString)
            default:
                let row = table.rowControllerAtIndex(index) as! TableRowController
                let rowString = String(format: "Split:%02i miles", tableSplit[index] + 1)
                let paceString = "Pace:" + RunData.stringFromSeconds(tableTime[index])
                row.splits.setText(rowString)
                row.time.setText(paceString)
            }
        }
        let totalPace = runData.totalTimeFromSplit(0, toSplit: runData.count - 1)
        let avgPace = runData.avgPaceFromSplit(0, toSplit: runData.count - 1)
        totalTimeLabel.setText(RunData.stringFromSeconds(totalPace))
        avgPaceLabel.setText(RunData.stringFromSeconds(avgPace))
    }
    //Table selection method
    override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
        //for use with insert and delete
        selectedRow = tableSplit[rowIndex]
        
        //Build a context for the data
        let avgPace = runData.avgPaceFromSplit(0, toSplit:selectedRow)
        presentControllerWithName("Info", context: avgPace) //present the viewcontroller
           }
    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()
    }

}

RunData.swift

//
//  RunData.swift
//  SwiftWatchOSTableDemo
//
//  Created by Steven Lipton on 3/24/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit

class RunData: NSObject {
    var data = [654,862,860,802,774,716,892,775,748,886,835]
    var count:Int {
        get{
            return data.count
        }
    }
    //A function to change the seconds data from an integer to a string in the form 00:00:00
    class func stringFromSeconds(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)
    }
    //Reset the original array
    class func resetData() -> [Int]{
        return [654,862,860,802,774,716,892,775,748,886,835]
    }
    //Remove a data item
    func removeItemAtIndex(index:Int){
        data.removeAtIndex(index)
    }
    //Add a data item
    func addItemAtIndex(index:Int,item:Int){
        print ("inserting \(item) at index \(index)")
        data.insert(item, atIndex: index)
    }
    
    func totalTimeFromSplit(split:Int, toSplit:Int) -> Int{
        var total = 0
        for index in split...toSplit{
            total+=data[index]
        }
        return total
    }
    
    func avgPaceFromSplit(split:Int, toSplit:Int) -> Int{
        let count = ((toSplit) - split) + 1
        return totalTimeFromSplit(split, toSplit: toSplit) / count
    }
}

InfoInterfaceController.swift

//
//  InfoInterfaceController.swift
//  SwiftWatchOSTableDemo
//
//  Created by Steven Lipton on 3/24/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InfoInterfaceController: WKInterfaceController {
 @IBOutlet var paceLabel: WKInterfaceLabel!
    
    override func awakeWithContext(context: AnyObject?) {
        
        super.awakeWithContext(context)
        let pace = context as! Int
        paceLabel.setText(RunData.stringFromSeconds(pace))
    }

}

TableRowController.swift

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

import WatchKit

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

Split5KTableRowController.swift

//
//  Split5KTableRowController.swift
//  SwiftWatchOSTableDemo
//
//  Created by Steven Lipton on 3/31/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit

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

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