Make App Pie

Training for Developers and Artists

Swift WatchKit: Programming  Sliders on the Apple Watch

dual slidersIn the last few lessons, we’ve covered some of the controls a developer can use in an Apple Watch app. As we’ve learned, we do not have full access to the properties of the controls. Instead, we have a few attributes we can only control through the story board, a single action method for some, and a few write-only properties from a getter method. This lesson adds another control which is again limiting, and an alternative solution which works much better.

Set Up The Project

Make a new project named SwiftWatchSlider with Swift as the language. Since we are working on the watch only, there is no need to Choose a device here. Save the project and under Editor in the drop down menu, select Add Target… In the box that pops up select the apple watch extension.

You get another dialogue box. Uncheck the notifications checkbox and press Finish.

You now have extra folders for the extension. Select the folder and the storyboard there. Find the slider in the Object library:

2015-04-28_05-59-13

Drag a slider to the storyboard. Drag a label under the slider on the storyboard. Set the text to 000 with text alignment of Center. Change the width of the label to be Relative to Container. Your storyboard should look something like this:

Screenshot 2015-04-27 05.42.21

Working with Slider Attributes

There are several attributes in the slider that can only be set in the storyboard. If not selected, click on the slider you added to the storyboard. In the attributes editor take a look at the attributes for a slider:

2015-04-28_05-45-12

The four critical attributes are Maximum, Minimum, Steps and Continuous. Set those as we have in the illustration. Minimum is the smallest value for the slider, Maximum the largest. Steps give the slider into discrete steps. Continuous displays segments for each step if off, a continuous line if on. Above these is Value. Set this to an starting value for the slider. Sliders are of type Float, so you can use decimal points.

Open the assistant editor. Create an outlet for the label and the slider named mySlider and myLabel respectively by control dragging from the controls on the storyboard to the code in the assistant editor.

@IBOutlet weak var mySlider: WKInterfaceSlider!
@IBOutlet weak var myLabel: WKInterfaceLabel! 

Create an action from the slider named sliderDidChange

@IBAction func sliderDidChange(value: Float) {

}

Add the following code to sliderDidChange

myLabel.setText("\(value)")

The action’s parameter is the current value of the slider. We display it in the label so we can see the numerical values.

Build and run.

2015-04-28_05-35-33

Change the values of the slider by tapping on the plus and minus. Stop the simulator and check on Continuous. Build and run again and you have this:

2015-04-28_05-33-02

Stop the simulator and in the attributes inspector change the Steps to 12 and uncheck Continuous. Build and run again. The steps are now in increments of 5 with longer segements.

2015-04-28_05-37-13

Digital Crown and Digits not Included

As of this writing, one missing component of a slider is the use of the watch’s digital crown. You have to tap. It would be nice to have a control for the crown, either included in the slider or as a separate control we can use with the slider. Apple has often kept hardware related controls secret until the hardware gets released. With the release of the watch we may see some changes on this.

A Control That Doesn’t Do Much

Let’s also introduce another object for the watch: the separator. Find the separator in the Object library:

2015-04-28_05-58-26

Drag the separator under the label. Set the vertical position to Center. That’s it — it’s a cosmetic line. You can change the color at run-time, but other than that, there is nothing you can do with a separator.

A Custom Slider-Like Control

Another missing component is a slider with numerical instead of graphic values. The Fitness app on the watch uses this to set goals for your activities. Above we added a label underneath the slider, but that isn’t the best use of space. We can do something like the numerical slider this with a group of controls.

In the object library, find Group. Drag a group above the separator. In the attributes inspector, Set the vertical position of the group to Bottom. Change the background color to the same as the slider. Click the Slashed rectangle next to the background color to get the color palette. On the RGB sliders palette, add the color 1f1f1f to the Hex Color Number. We can also change the corners a bit. Just under the background color, make a custom radius of 10.

2015-04-28_06-19-53

We now have a background for our custom control:

2015-04-28_06-22-35

Now drag a button into the group. It will take up all the space in the group. In the attribute inspector, change the width to Size to Fit Content. Set the vertical position to Center. Set the horizontal position to Left if not already set. Change the title to a minus(-) sign. Make the title white, and the background Clear. Make the system font size 40 points.

Drag another button to the right of the first button. Again it will take over the group. In the attribute inspector, change the width to Size to fit Content. Set the vertical position to Center. Set the horizontal position to Right. Change the title to a plus(+) sign. Make the title white, and the background clear. Make the system font size 30 points.

Drag a label between the two buttons. Set the Vertical Position to Center, and the Horizontal position to Center. Change the text to 000, with a font of System and a size of 24 points. You will have this:

2015-04-28_06-37-44

The bottom version is taller than the original. Change the Vertical size on the group to Relative to Container. It gets very big.

2015-04-28_06-43-38

Change the value of 1 under the size to 0.3. The group shrinks:

2015-04-28_06-47-35

We could have used a absolute size. Using a proportional size instead of an absolute one makes it easier to translate between the two watch sizes.

Connect up the label by control draggin it to the code in teh assistant editor. Add an outlet named numberSliderLabel.

Control drag from the plus button to the the code. Make an action called numberSliderDidIncrease. Control drag from the plus button to the the code. Make an action called numberSliderDidDecrease.

Add the following variables to the class:

 //MARK: - Number Slider Code
    var sliderValue:Float = 0.0
    var steps:Int = 0
    var maxVal:Float = 60.0
    var minVal:Float = 0.0
    var increment: Float {
        get {
                return Float(steps)/(maxVal-minVal)
            }
    }

For our number slider we want the same attributes as the native one, which we assigned as variables. We added to this code one read-only variable: increment. We’ve used a computed property to figure out our increment or decrement for sliderValue.

A bit of a warning here: For instructional purposes, I kept this simple. You should implement some checks to make sure maxVal is alwys greater then minVal.

Now add this code to increment and decrement the counter using our two buttons:

 @IBAction func numberSliderDidIncrease() {

        sliderValue = sliderValue + increment
        if sliderValue > maxVal {sliderValue = maxVal} //enforce upper boundary
        updateLabel()
    }

    @IBAction func numberSliderDidDecrease() {
        sliderValue = sliderValue - increment
        if sliderValue < minVal {sliderValue = minVal} //enforce lower boundary
        updateLabel()
    }

We update the slider value, check if it crosses the minVal or maxVal boundary and then update the label. Next add the update method

func updateLabel(){
        numberSliderLabel.setText("\(sliderValue)")
        myLabel.setText("\(sliderValue)")
        mySlider.setValue(sliderValue)
    }

Your own update method may do many things. In this case it does three. It update the label so we have feedback to our value. Secondly we update the label for the native slider, and then update the segments using one of the few WKInterfaceSlider methods setValue.

Build and run:

dual sliders

You can customize this as you wish. Unfortunately, as I mentioned before, Apple has not yet released a way to get digital crown input for this. Hopefully at WWDC 2016 they will. ( 8/2016 They did..see here)

The Whole Code

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

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {

        @IBOutlet weak var myLabel: WKInterfaceLabel!
    @IBOutlet weak var mySlider: WKInterfaceSlider!
    
    @IBAction func sliderDidChange(value: Float) {
        myLabel.setText("\(value)")
    }
    
    //MARK: - Number Slider Code
    
    // Note -- this is bare bones code for instructional purposes.
    //You need to implement some value validation to this to work for production. Add the following tests if implementing in production code:
    // maxVal must be greater than minVal
    // minVal must be less than maxVal
    // steps must be less than maxVal-minVal
    // I suggest making them property observers, but you could do checks elsewhere in code.
    
    @IBOutlet weak var numberSliderLabel: WKInterfaceLabel!
    var sliderValue:Float = 0.0
    var steps:Int = 12
    var maxVal:Float = 60.0
    var minVal:Float = 0.0
    var increment: Float {
        get {
                return (maxVal-minVal)/Float(steps)
            }
    }
    
    @IBAction func numberSliderDidIncrease() {
        
        sliderValue = sliderValue + increment
        if sliderValue &gt; maxVal {sliderValue = maxVal} //enforce upper boundary
        updateLabel()
    }
    
    @IBAction func numberSliderDidDecrease() {
        sliderValue = sliderValue - increment
        if sliderValue &lt; minVal {sliderValue = minVal}  //enforce lower boundary
        updateLabel()
    }
    func updateLabel(){
        numberSliderLabel.setText("\(sliderValue)")
        myLabel.setText("\(sliderValue)")
        mySlider.setValue(sliderValue)
    }
}

3 responses to “Swift WatchKit: Programming  Sliders on the Apple Watch”

  1. When using multiple sliders, how do I get the value of them out of the function to be used by another calculation. i.e. I have 2 sliders that update 2 different labels and I want to perform a calculation using the values of the 2 with the results in a third label?

    1. Short answer is you’ll need three variables. I don’t regualrly code the answers to question, but its quicker to code than to explain. You can do something like this:

      var a:Float = 0.0
      var b:Float = 0.0
      var c:Float = 0.0 
      @IBAction func aSliderDidChange(_ value:Float){
          a = value
          update()
      }
      @IBAction func bSliderDidChange(_ value:Float){
          b = value
          update()
      }
      
      func update(){
          c = a + b
      }

      I left out updating the labels with the new values in this case. The variable c could also be a computed property, whihc is probably how I would do it if c does not change by any othere factor. Something like this and then you don’t need update() in the code above.

      var c:Float{
          get{
              return a+b
          }
      }
      

      That all said, from a UI/UX perspective, I’d get shy about putting two sliders on the same interface. The sliders will be hard to control accurately without clicking the wrong slider. Three labels and a two sliders may be too big for use without scrolling, which detracts from the user experience.

  2. Um, I have multiple Interface Controllers and I’m trying to make an app that helps someone lose weight (totally original ik) and I for some reason it’s going to a file called WKInterfaceController.h. Can someone help? thx

Leave a Reply to Steven Lipton Cancel 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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: