Swift WatchKit: Using Images on an Apple Watch(Part 2: Code)

2015-05-05_09-58-16In our last lesson we did the layout for a watch app that included images. However, we did not yet code those images. In this lesson we’ll add the code to the application to change a button background and show images.  If you have not done so, head over to here to get the application to this point.

Add the Outlets and Actions

Open the SwiftWatchImage file in Xcode if not already open.  Go to the storyboard in the SwiftWatchImage WatchKit App Folder.  Open the assistant editor and make sure it is on Automatic, so InterfaceController.swift shows in the assistant editor.  Control drag from the Walking label to add an outlet named statusLabel. Control drag from the image to the assistant editor and add an outlet named statusImage. Add one more outlet runWalkEatButton by dragging from the button to the assistant editor.  You should now have these three lines of code:


@IBOutlet weak var statusLabel: WKInterfaceLabel!
    @IBOutlet weak var statusImage: WKInterfaceImage!
    @IBOutlet weak var runWalkEatButton: WKInterfaceButton!

Control drag from the button again to the assistant editor and this time add an action didPressRunWalkButton

    @IBAction func didPressRunWalkEatButton() {
    }

A Three-Way Toggle Button

Our interval watch app will cycle through running walking and eating. We don’t want to eat too much, so we will eat only after five intervals.

We’ll do this using the modulo operator %. We’ll increment an activity counter, and then check  which activity that counter indicates.

Close the assistant editor and in the file navigator, open the interfaceController.swift file in the SwiftWatchImage WatchKit Extension.  Between the outlets and the action add the following:

enum IntervalActivites: Int{
    case Walk = 1
    case Run = 2
    case Eat = 10
}
var myActivity:Int = IntervalActivites.Walk.rawValue

We’ll use an enumeration to better document the activity.  We also set a variable myActivity to .Walk to show we are currently walking. Since enumerations are not their value, we use the rawvalue property to get the value.

Add the following code to the didPressRunWalkEatButton button:

@IBAction func didPressRunWalkEatButton() {
        myActivity++ //increment the activity
        if myActivity % IntervalActivites.Eat.rawValue == 0 { //evenly divisible by .Eat
            //eat
            updateDisplay(.Eat)
        } else if myActivity % IntervalActivites.Run.rawValue == 0 { //evenly divisible by .Run
            //run
            updateDisplay(.Run)
        } else{ //
            //walk
            updateDisplay(.Walk)
        }
    }

This will give us the structure we need to change the display. We used a little math trick. The modulo operator % gives you a remainder. If the remainder is 0, the value is evenly divisible and thus a multiple of the number we are looking for. This is an easy way to get every Nth number if you have a simple increment. We use this test to check if we have an eat, run or walk activity.  Once we know, we update the display. Let’s make a method to update the display:

func updateDisplay(activity:IntervalActivites){
        switch activity{
        case .Eat:
            statusLabel.setText("Eat")
            runWalkEatButton.setTitle("Walk")
        case .Run:
            statusLabel.setText("Run")
            runWalkEatButton.setTitle("Walk")
        case .Walk:
            statusLabel.setText("Walk")
            runWalkEatButton.setTitle("Run")
        default:
            statusLabel.setText("Error")
            runWalkEatButton.setBackgroundColor(UIColor.redColor())
        }

Using Images in Code

This code takes an activity and changes the label and button title.  We can do the same with images as well.  For an image, we use the setImage method.  This method takes as a parameter a UIImage.   I tend to load the UIImage into constants which makes it easier to use in code, and speeds up code since we only load these images once. The downside is the use of more memory to do so.  Add the following  just above the outlets:

//preload images
let pancakesImage = UIImage(named: "pancakes")
let runnerImage = UIImage(named: "runner")
let walkerImage = UIImage(named: "walker")
let greenGradient = UIImage(named: "greenGradient")
let blueGradient = UIImage(named: "blueGradient")

There’s one small problem with using the UIImage(named:) initializer.  It only looks in the local bundle.  We’ll need to load these images into the Images.xcassets for the extension. The ones in the WatchKit App folder we made last time wont reach here.  Open the Images.xcassets folder and in Finder open the folder where you stored the images from the last lesson. Select the images and drag them to the into the images folder.

2015-05-05_06-48-00

We have images.  Change the updateDisplay to this:

func updateDisplay(activity:IntervalActivites){
switch activity{
     case .Eat:
          statusLabel.setText("Eat")
          statusImage.setImage(pancakesImage)
          runWalkEatButton.setTitle("Walk")
     case .Run:
          statusLabel.setText("Run")
          statusImage.setImage(runnerImage)
          runWalkEatButton.setTitle("Walk")
     case .Walk:
         statusLabel.setText("Walk")
         statusImage.setImage(walkerImage)
         runWalkEatButton.setTitle("Run")
     default:
          statusLabel.setText("Error")
          runWalkEatButton.setBackgroundColor(UIColor.redColor())
}

Build and Run. We start with the first screen from last time.

2015-05-12_06-58-29

Press Run. We get the run, and the button has changed to walk:

2015-05-12_06-59-24

Pressing Walk will bring us back to our first interval. Tap through a few run and walk intervals, and you will get to the eat interval:

2015-05-12_06-59-58

If you noticed, the button said Run before we had an Eat interval. It should read Eat. At the end of the didPressRunWalkEatButton code, add this code:


if myActivity % (IntervalActivites.Eat.rawValue - 1) == 0 {
            runWalkEatButton.setTitle("Eat")
        }

After we displayed everything, we check if we are at an activity count that is one before eating. If so, we change the button title to Eat.

Using Images as Backgrounds

Images also can show up on backgrounds for buttons, groups and interfaces. Let’s do that for the button. We’ll change the button to a blue gradient for walking and eating and green for running. The method setBackground does this for us.

runWalkEatButton.setBackgroundImage(blueGradient)

Change the display code to this, to add in the gradients.

func updateDisplay(activity:IntervalActivites){
        switch activity{
        case .Eat:
            statusLabel.setText("Eat")
            statusImage.setImage(pancakesImage)
            runWalkEatButton.setBackgroundImage(blueGradient)
            runWalkEatButton.setTitle("Walk")
        case .Run:
            statusLabel.setText("Run")
            statusImage.setImage(runnerImage)
            runWalkEatButton.setBackgroundImage(blueGradient)
            runWalkEatButton.setTitle("Walk")
        case .Walk:
            statusLabel.setText("Walk")
            statusImage.setImage(walkerImage)
            runWalkEatButton.setBackgroundImage(greenGradient)
            runWalkEatButton.setTitle("Run")
        default:
            statusLabel.setText("Error")
            runWalkEatButton.setBackgroundColor(UIColor.redColor())
        }
    }

For an Eat button, we need to change this in the button’s action didPressRunWalkEatButton:

if myActivity % (IntervalActivites.Eat.rawValue - 1) == 0 {
            runWalkEatButton.setTitle("Eat")
            runWalkEatButton.setBackgroundImageNamed("blueGradient")
        }

I used a different setter here. If you don’t pre-load your image you can call it directly with setBackgroundImageNamed where you just name the image in the xcassets folder with a string.

Build and run. We now get blue buttons for walk and eat:
2015-05-12_07-39-58

2015-05-12_07-47-49

We’ll discuss more about color gradient buttons in a later lesson. These we made with a .png image. We can generate them completely in code as well.

You may notice that the screen shots have two dots on the bottom.  The  Xcode Watch simulator has a lot of problems, as it seems to not reconcile the layout right, and will fail to load on occasions. One workaround is to load a blank page interface first, then swipe to the real interface. In our next session, I’ll show you how and we’ll explore the page and hierarchical interfaces.

The Whole Code

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

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    
    //preload images
    let pancakesImage = UIImage(named: "pancakes")
    let runnerImage = UIImage(named: "runner")
    let walkerImage = UIImage(named: "walker")
    let greenGradient = UIImage(named: "greenGradient")
    let blueGradient = UIImage(named: "blueGradient")

    @IBOutlet weak var statusLabel: WKInterfaceLabel!
    @IBOutlet weak var statusImage: WKInterfaceImage!
    @IBOutlet weak var runWalkEatButton: WKInterfaceButton!
    
    enum IntervalActivites: Int{
        case Walk = 1
        case Run = 2
        case Eat = 10
    }
    
    var myActivity:Int = IntervalActivites.Walk.rawValue
    
    @IBAction func didPressRunWalkEatButton() {
        myActivity++ //increment the activity
        if myActivity % IntervalActivites.Eat.rawValue == 0 { //evenly divisible by .Eat
            //eat
            updateDisplay(.Eat)
        } else if myActivity % IntervalActivites.Run.rawValue == 0 { //evenly divisible by .Run
            //run
            updateDisplay(.Run)
        } else{ //
            //walk
            updateDisplay(.Walk)
        }
        if myActivity % (IntervalActivites.Eat.rawValue - 1) == 0 {
            runWalkEatButton.setTitle("Eat")
            runWalkEatButton.setBackgroundImageNamed("blueGradient")
        }
        
    }
    
    func updateDisplay(activity:IntervalActivites){
        switch activity{
        case .Eat:
            statusLabel.setText("Eat")
            statusImage.setImage(pancakesImage)
            runWalkEatButton.setBackgroundImage(blueGradient)
            runWalkEatButton.setTitle("Walk")
        case .Run:
            statusLabel.setText("Run")
            statusImage.setImage(runnerImage)
            runWalkEatButton.setBackgroundImage(blueGradient)
            runWalkEatButton.setTitle("Walk")
        case .Walk:
            statusLabel.setText("Walk")
            statusImage.setImage(walkerImage)
            runWalkEatButton.setBackgroundImage(greenGradient)
            runWalkEatButton.setTitle("Run")
        default:
            statusLabel.setText("Error")
            runWalkEatButton.setBackgroundColor(UIColor.redColor())
        }
    }
 
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        
        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

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

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s