Chapters

Hide chapters

watchOS With SwiftUI by Tutorials

Second Edition · watchOS 9 · Swift 5.8 · Xcode 14.3

Section I: watchOS With SwiftUI

Section 1: 13 chapters
Show chapters Hide chapters

3. Digital Crown
Written by Scott Grosch

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the starter materials for this chapter, please open the NumberScroller project. Build and run the project. You’ll see a lonely number:

Scrolling the Digital Crown makes it easy for your users to edit a number’s value, such as the one shown.

If you’re debugging with a physical device, rotate the crown. If you’re debugging on the simulator, scrolling your mouse simulates turning the digital crown. Give it a spin, and watch the number change.

Binding a Value

Did nothing happen? That shouldn’t come as much of a surprise since you didn’t tell watchOS what to do when you rotated the crown.

To let the Digital Crown interact with a SwiftUI control, you use digitalCrownRotation. It takes a ridiculous number of parameters in its full form, but you’ll start with just one.

Open ContentView. Modify the Text field as follows:

Text("\(number, specifier: "%.1f")")
  .focusable()
  .digitalCrownRotation($number)

By default, a Text field won’t accept focus as it’s not an interactive element. When using the digitalCrownRotation modifier, typically you’ll also immediately precede the call with .focusable(), so watchOS lets you interact with the view you’re modifying.

digitalCrownRotation always takes a binding as the first parameter to anything which implements the BinaryFloatingPoint protocol, such as a Double. Build and run again. This time, when you scroll the Digital Crown, you’ll see the number change.

Limiting the Scroll Range

Generally, having the number scroll infinitely in either direction isn’t what you’ll want to accomplish. You can limit the range of values by adding two more parameters.

.digitalCrownRotation($number, from: 0.0, through: 12.0)
.digitalCrownRotation($number, from: 0.0, through: 12.0, by: 0.1)

The Joys of Floating-Point

Remember that a floating-point value is seldom exactly what you expect it to be. The OS tracks floating-point to many more decimal places than a human does. To see what the Digital Crown is doing, add this right before .focusable():

.onChange(of: number) { print($0) }
if (value * 10).rounded(.towardZero) / 10 == 9.7 {
  ...
}

Sensitivity

When scrolling the Digital Crown, you can specify how sensitive the update should be. Think of the sensitivity as how much you need to scroll the crown for a change to take effect. Add another parameter:

.digitalCrownRotation(
  $number,
  from: 0.0,
  through: 12.0,
  by: 0.1,
  sensitivity: .high
)

Wrapping Around

Until now, when you reached the from or through values, the number stopped changing. You could continue turning the Digital Crown, but it wouldn’t make any updates.

.digitalCrownRotation(
  $number,
  from: 0.0,
  through: 12.0,
  by: 0.1,
  sensitivity: .high,
  isContinuous: true
)

Haptic Feedback

By default, scrolling the Digital Crown provides a small amount of haptic feedback to the user. If that doesn’t make sense for your app, you can turn it off by using the final parameter to the digitalCrownRotation call:

.digitalCrownRotation(
  $number,
  from: 0.0,
  through: 12.0,
  by: 0.1,
  sensitivity: .high,
  isContinuous: true,
  isHapticFeedbackEnabled: false
)

Pong

While scrolling numbers is super fun, you won’t always show the actual numeric value to your app’s user.

Hooking Up the Digital Crown

Open ContentView. You’ll see that Pong is implemented as a SpriteKit game. The SpriteView(scene:) initializer makes adding a SpriteKit scene to your SwiftUI app easy. To provide the scene’s size to SpriteKit, the SpriteView is wrapped in a GeometryReader.

.focusable()
.digitalCrownRotation($crownPosition)

Paddle Movement

Open PongScene, which implements all of the SpriteKit scene logic. Scroll down to the overridden update(_:). SpriteKit will call update(_:) once per frame before it simulates any actions or physics.

let newPosition = crownPosition

// 1
defer { previousCrownPosition = newPosition }

// 2
guard newPosition != previousCrownPosition else { return }
// 1
let offset = newPosition - previousCrownPosition
let y = paddleBeingMoved.position.y + offset

// 2
guard minPaddleY ... maxPaddleY ~= y else { return }

// 3
paddleBeingMoved.position.y = y
guard y >= minPaddleY && y <= maxPaddleY else {
  return
}

Key Points

  • Remember to add the focusable method modifier when using the Digital Crown.
  • If you need to compare two floating-point values for equality, use the rounded(.towardZero) method.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now