「Apple Watch 应用开发系列」数码表冠

2,462 阅读5分钟

数码表冠

image

数码表冠(Digital Crown)是 Apple Watch 的主要硬件输入,让人们可以滚动屏幕内容、切换到不同的应用程序以及使用 Siri。

我们将了解在开发中,如何控制数码表冠。

需要注意的点

developer.apple.com/design/huma…

根据 Apple Human Interface Guidelines,我们尝试为 watchOS App 添加与数码表冠的交互时,需要注意:

  • 提供视觉反馈以响应数码表的交互。例如,当用户使用 Digital Crown 时,选择器会更改当前显示的值。如果开发中跟踪数码表的旋转,请使用接收到的旋转数据来更新 App 的界面。如果开发中不提供视觉反馈,用户可能会认为旋转数码表对 App 有任何影响。

  • 以数码表旋转相对应的速度更新界面。数字表冠的旋转应该让用户对界面进行精确控制。考虑使用旋转速度来确定进行更改的速度。避免用户难以舒适的更新内容。

  • 在 App 中使用默认的触觉反馈。例如可以调整表格视图的触觉反馈行为,让它们使用线性定位而不是基于行的定位,给用户带来更一致的体验。

Swift & SwiftUI

数码表冠绑定数字

构建项目

我们创建一个独立的 watchOS App 项目 DigitalCrownDemo。并将 Interface 选择为 SwiftUI

image

image

调整 ContentView.swift 代码,以展示当前数字:

struct ContentView: View {
    @State var number: Float = 0
    var body: some View {
        Text("\(number, specifier: "%.1f")")
    }
}

image

与数码表冠绑定

调整代码:

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

默认情况下,Text 不会接受焦点,因为它不是交互式元素。 使用 digitalCrownRotation 修饰符时,需要在调用之前立即使用 .focusable()

digitalCrownRotation 始终将绑定作为任何实现 BinaryFloatingPoint 协议的第一个参数,例如 Float、Double。

构建并运行项目。 这一次,当我们滚动数字表冠时,我们会看到数字发生了变化。

image

限制滚动范围

通常上述的表现不是我们希望的。我们更多的是给定一个可操作的范围。请继续调整代码:

.digitalCrownRotation($number, from: 0.0, through: 10.0)

我们添加了数码表冠的滚动限制,从 0.0 ~ 10.0,构建并运行项目,我们会发现实际情况有些不符合预期:

image

image

我们发现滚动会显示下限值 -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)
    }

image

我们会发现,绑定属性更新的值我们想象的要多得多。同时旋转停止时,绑定属性通常不受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。

灵敏度、循环与触觉反馈

有时候,我们需要调整数字表冠的灵敏度来使交互更加流畅。继续调整代码:

image

构建并运行项目,可以将值从 .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

image

拖入 Lable 并进行样式的调整:

image

image

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))
    }
}

image

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 {}

WKCrownDelegateextension 添加代码:

func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
    print(rotationalDelta)
}

func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
    print("End")
}
  • crownDidRotate(rotationalDelta:) 当用户旋转数字表冠时调用。自上次更新以来表冠旋转的量。1.0 的值表示一整圈。值的符号表示旋转的方向,但符号会根据表冠的方向进行调整。正值始终表示向上滚动手势,而负数表示向下滚动手势。用户可以在手表的设置中更改数字表冠的方向。

developer.apple.com/documentati…

  • crownDidBecomeIdle() 当用户停止旋转表冠时调用。

developer.apple.com/documentati…

运行项目并查看输出。

image

调整代码,计算我们当前需要展示的值:

func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
    number = number + rotationalDelta
    if number < 0.0 { number = 10.0 }
    if number > 10.0 { number = 0.0 }
    updateUI()
}

运行项目,此时我们的数码表冠已经和数字绑定:

image

image