iOS Training from beginner to advanced
[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.
Add three labels to the watch interface. Title them as follows:
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.
// // 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() } }
Pingback: Swift WatchKit: Programming Sliders on the Apple Watch | Making App Pie
Thanks
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.
I’ll have to take a look at that.
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?
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.
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.
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 indidAppear
like this.I’ll change the lesson to reflect this. Thanks for the catch.
That did the trick! thanks for the help!
You are welcome.