数码表冠
数码表冠(Digital Crown)是 Apple Watch 的主要硬件输入,让人们可以滚动屏幕内容、切换到不同的应用程序以及使用 Siri。
我们将了解在开发中,如何控制数码表冠。
需要注意的点
根据 Apple Human Interface Guidelines,我们尝试为 watchOS App 添加与数码表冠的交互时,需要注意:
-
提供视觉反馈以响应数码表的交互。例如,当用户使用 Digital Crown 时,选择器会更改当前显示的值。如果开发中跟踪数码表的旋转,请使用接收到的旋转数据来更新 App 的界面。如果开发中不提供视觉反馈,用户可能会认为旋转数码表对 App 有任何影响。
-
以数码表旋转相对应的速度更新界面。数字表冠的旋转应该让用户对界面进行精确控制。考虑使用旋转速度来确定进行更改的速度。避免用户难以舒适的更新内容。
-
在 App 中使用默认的触觉反馈。例如可以调整表格视图的触觉反馈行为,让它们使用线性定位而不是基于行的定位,给用户带来更一致的体验。
Swift & SwiftUI
数码表冠绑定数字
构建项目
我们创建一个独立的 watchOS App 项目 DigitalCrownDemo
。并将 Interface
选择为 SwiftUI
。
调整 ContentView.swift
代码,以展示当前数字:
struct ContentView: View {
@State var number: Float = 0
var body: some View {
Text("\(number, specifier: "%.1f")")
}
}
与数码表冠绑定
调整代码:
Text("\(number, specifier: "%.1f")")
.focusable()
.digitalCrownRotation($number)
默认情况下,Text 不会接受焦点,因为它不是交互式元素。 使用 digitalCrownRotation
修饰符时,需要在调用之前立即使用 .focusable()
。
digitalCrownRotation
始终将绑定作为任何实现 BinaryFloatingPoint
协议的第一个参数,例如 Float、Double。
构建并运行项目。 这一次,当我们滚动数字表冠时,我们会看到数字发生了变化。
限制滚动范围
通常上述的表现不是我们希望的。我们更多的是给定一个可操作的范围。请继续调整代码:
.digitalCrownRotation($number, from: 0.0, through: 10.0)
我们添加了数码表冠的滚动限制,从 0.0 ~ 10.0,构建并运行项目,我们会发现实际情况有些不符合预期:
我们发现滚动会显示下限值 -0.1 和上限值 10.1。 我们必须再添加一个参数来告诉数码表冠更改值的幅度。
.digitalCrownRotation($number, from: 0, through: 10, by: 0.1)
再次构建并运行项目。 这次我们会得到预期的结果。
我们深入看一下这里,继续调整代码,并滚动数码表冠:
Text("\(number, specifier: "%.1f")")
.focusable()
.digitalCrownRotation($number, from: 0, through: 10, by: 0.1)
.onChange(of: number) { newValue in
print(newValue)
}
我们会发现,绑定属性更新的值我们想象的要多得多。同时旋转停止时,绑定属性通常不受by:参数的限制。
如果我们需要对浮点数执行相等性检查,请使用舍入方法,如下所示:
if (value * 100).rounded(.towardZero) / 100 == 9.75 {
...
}
developer.apple.com/documentati…
(5.2).rounded(.towardZero)// 5.0
(5.5).rounded(.towardZero)// 5.0
(-5.2).rounded(.towardZero) // -5.0
(-5.5).rounded(.towardZero)// -5.0
rounded(.towardZero)
向零舍入来将提供的值舍入为整数值。你乘以 10^X 并除以 10^X ,其中 X 是我们要比较的小数位数。
如事例代码,9.75 有两位小数,我们的 value 为 9.755,9.755 * 10^2 = 975.5 -> 975 -> 975 / 100 = 9.75,此时,结果为 true。
灵敏度、循环与触觉反馈
有时候,我们需要调整数字表冠的灵敏度来使交互更加流畅。继续调整代码:
构建并运行项目,可以将值从 .high 更改为 .medium 再更改为 .low 并进行体验。当值为 .low时,我们必须将数码表冠转动得更多。 这里的默认值为 .high。
对于数字范围停止是有意义的。 在其他情况下我们可能希望值是循环的,用户可以正常滚动到 10.0,但如果用户继续滚动,该值将变为 0.0 并再次计算。 苹果称之为连续滚动(Continuous scrolling)。 默认为 false:
Text("\(number, specifier: "%.1f")")
.focusable()
.digitalCrownRotation(
$number,
from: 0,
through: 10,
by: 0.1,
sensitivity: .low,
isContinuous: true
)
再次构建并运行。 连续滚动数字表冠,你会看到数字可以进行循环。
默认情况下,滚动数码表冠会向用户提供少量触觉反馈。 如果对我们的 App 没有意义,我们可以使用 相关参数将其关闭:
.digitalCrownRotation(
$number,
from: 0,
through: 10,
by: 0.1,
sensitivity: .low,
isContinuous: true,
isHapticFeedbackEnabled: false
)
Swift & Storyboard
数码表冠绑定数字
我们将尝试使用 Storyboard 实现上述 Demo。首先创建项目,将 Interface
设置为 Storyboard
。
拖入 Lable 并进行样式的调整:
在 InterfaceController.swift
中,进行 numberLabel
text 的设置:
class InterfaceController: WKInterfaceController {
private var number = 0.0
@IBOutlet var numberLabel: WKInterfaceLabel!
override func awake(withContext context: Any?) {
// Configure interface objects here.
updateUI()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
}
private func updateUI() {
numberLabel.setText(String(format: "%.1f", number))
}
}
向 InterfaceController
类添加代码,需要为 CrownSequencer
添加代理,并使他聚焦:
override func didAppear() {
crownSequencer.delegate = self
crownSequencer.focus()
}
WKCrownSequencer
是报告数字表冠当前状态的对象,包括其运动时的旋转速度。我们不要自己创建此类的实例,而是从当前 InterfaceController
的属性中检索一个表冠序列器对象。
在其可以接收数据之前,我们必须调用它的 focus()
方法。在任何给定时间,我们的界面中只有一个对象可以具有焦点,因此如果我们的界面还包含选择器对象或具有可滚动的场景,您必须相应地协调焦点的变化。
因为表冠序列器不绑定到特定的界面对象,所以我们可以将其用作应用程序的一般输入。
WKCrownSequencer
提供两个状态属性:
-
var rotationsPerSecond: Double表冠的旋转速度,以每秒转数为单位。转速是绝对值。无论旋转方向如何,它始终为正。
-
var isIdle: Bool一个布尔值,指示表冠是否处于静止状态。
WKCrownSequencer
有一个触觉反馈属性:
- var isHapticFeedbackEnabled: Bool { get set } 一个布尔值,用于确定是否启用数码表冠的触觉反馈。
继续修改我们的 InterfaceController
使其符合 WKCrownDelegate
协议。
extension InterfaceController : WKCrownDelegate {}
在 WKCrownDelegate
的 extension
添加代码:
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
print(rotationalDelta)
}
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
print("End")
}
crownDidRotate(rotationalDelta:)
当用户旋转数字表冠时调用。自上次更新以来表冠旋转的量。1.0 的值表示一整圈。值的符号表示旋转的方向,但符号会根据表冠的方向进行调整。正值始终表示向上滚动手势,而负数表示向下滚动手势。用户可以在手表的设置中更改数字表冠的方向。
crownDidBecomeIdle()
当用户停止旋转表冠时调用。
运行项目并查看输出。
调整代码,计算我们当前需要展示的值:
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
number = number + rotationalDelta
if number < 0.0 { number = 10.0 }
if number > 10.0 { number = 0.0 }
updateUI()
}
运行项目,此时我们的数码表冠已经和数字绑定: