Make App Pie

Training for Developers and Artists

Accessing the Digital Crown in WatchOS5

[Updated for WatchOS5 Beta 6/22/18]

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 WKCrownDelegate

class InterfaceController: WKInterfaceController,WKCrownDelegate {

As for any delegate, set the delegate to self, which in this case is on the crownSequencer. Do this in awake(withContext)

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

Several objects, including pickers, scrolling views and tables can have the focus of the crown. For the crownSequencer to work as an input in your code, must set the focus to it. Set the focus for the crown as late as possible to make sure it gets control. After the interface appears is best, so I set it in didAppear()

override func didAppear() {
        crownSequencer.focus()
}

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.
//  Modified 6/22/18 WatchOS5
//  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.delegate = self
    }

    override func didAppear() {
        crownSequencer.focus()
    }
    //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()

    }

}

10 responses to “Accessing the Digital Crown in WatchOS5”

  1. […] 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. (They did..see here) […]

  2. Remy Da Costa Faro Avatar
    Remy Da Costa Faro

    Thanks

  3. Have you tried putting code like this into a push or modal segue? Every time I run code with this form and go back to the root Interface controller the digital crown stops working completely.

    1. I’ll have to take a look at that.

  4. I can’t seem to get this to work. I’ve found similar instructions on a different site, but no matter what I do the crown sequencer doesn’t work. are there any steps before this that need to be done?

    1. Yes I’m getting that behavior too. I happened to have the code handy for the Lynda.com video version and I tried it out. Let me look into it.

    2. This is looking like a bug in watchOS. The documentation doesn’t mention any changes but the callback are not firing and the isIdle property of crownSequencer is acting weird. I’ll keep digging.

      1. I remember now. I was looking at code for watchOS3. There was a change in WatchOS4, and it drove me batty then too. The focus had to be one of the last things you did, so nothing else took it away. Put the focus line not in the awake but in didAppear like this.

        override func awake(withContext context: Any?) {
                super.awake(withContext: context)
                crownSequencer.delegate = self
                // Configure interface objects here.
            }
            override func didAppear() {
                crownSequencer.focus()
            }
        

        I’ll change the lesson to reflect this. Thanks for the catch.

  5. That did the trick! thanks for the help!

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 )

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: