WWDC22: watchOS 上用 WidgetKit 构建 Complications

发布
更新
字数 825
阅读 5 分钟
阅读量 1631

自 iOS 16 开始,iPhone 锁屏界面上增加了类似 watchOS 中 Complications 风格的 widget,同时自 watchOS 9 起使用 WidgetKit 构建手表上的 Complications。

WidgetKit on watchOS 9

iOS 16 新增了三种 family,.accessoryRectangular .accessoryCircular .accessoryInline,而在 watchOS 9 中还额外支持一种 family,.accessoryCorner

WidgetKit .accessoryCorner family 是 watchOS 中独有的,用于在表盘角落展示辅助内容,需要注意的是该部分内容虽然通过 SwiftUI 实现,但系统会根据表盘样式渲染,不可以自定义。此外 .accessoryInline family 在表盘上也有独特的渲染方式,会根据表盘样式使用不同的布局:平整(flat)或弯曲(curved)。

.accessoryCorner

.accessoryCorner 主要有两种效果:一种是外圈内容,位于表盘底部两个角落,仅显示图标;一种包含内圈内容,位于上部两个角落,可以显示图标和辅助内容。watchOS 9 新增了一个视图修改器,用以标记可以隐藏的辅助内容。

struct CornerView: View {
    let value: Double

    var body: some View {
        // 1. 外圈,使用 ZStack
        ZStack {
            // 设置背景
            AccessoryWidgetBackground()
            // 外圈图标
            Image(systemName: "cup.and.saucer.fill")
                .font(.title.bold())
                .widgetAccentable()
        }
        // 2. 内圈,使用新的 widgetLabel 修改器
        .widgetLabel {
            Gauge(value: value,
              in: 0...500) {
                Text("MG")
            } currentValueLabel: {
                Text("\(Int(value))")
            } minimumValueLabel: {
                Text("0")
            } maximumValueLabel: {
                Text("500")
            }
        }

    }
}

注意,这里外圈显示的 SF Symbol 图标和内圈现实的 Gauge 内容,系统会自动调整并对齐,包括位置、大小以及颜色,以适应表盘样式。

.accessoryCircular

在 infograph 表盘上除了 corner complications,在表盘内部还有四个位置用来显示 circular complications。circular 与 corner complications 样式会由相似的地方,曲线形的辅助内容。

struct CircularView: View {
    let value: Double

    var body: some View {
        
        // 1. 首先使用 Gauge 显示一个进度信息
        Gauge(value: value,
              in: 0...500) {
            Text("MG")
        } currentValueLabel: {
            Text("\(Int(value))")
        }
        // 2. 环形样式
        .gaugeStyle(.circular)
        // 3. 使用 widgetLabel 添加辅助内容
        .widgetLabel {
            Text("\(mg, formatter: mgFormatter) Caffeine")
        }
    }
    
    var mgFormatter: Formatter {
        let formatter = MeasurementFormatter()
        formatter.unitOptions = [.providedUnit]
        return formatter
    }
}

extension Double {
    func inMG() -> Measurement<UnitMass> {
        Measurement<UnitMass>(value: self, unit: .milligrams)
    }
}

在 watchOS 9 增加了新的环境变量 .showWidgetLabel,我们可以根据该值动态调整 complication 的样式

struct CircularView: View {
    let value: Double
    // 1. 获取 `.showsWidgetLabel` 变量值
    @Environment(\.showsWidgetLabel) var showsWidgetLabel

    var body: some View {
        let mg = value.inMG()
        if showsWidgetLabel {
            // 2. 如果可以展示辅助信息
            ZStack {
                AccessoryWidgetBackground()
                // 3. 则仅显示图标
                Image(systemName: "cup.and.saucer.fill")
                    .font(.title.bold())
                    .widgetAccentable()
            }
            // 4. 和辅助信息
            .widgetLabel {
                Text("\(mg, formatter: mgFormatter) Caffeine")
            }
        }
        else {
            // 5. 否则展示进度内容
            Gauge(value: value,
                  in: 0...500) {
                Text("MG")
            } currentValueLabel: {
                Text("\(Int(value))")
            }
            .gaugeStyle(.circular)
        }

    }

    var mgFormatter: Formatter {
        let formatter = MeasurementFormatter()
        formatter.unitOptions = [.providedUnit]
        return formatter
    }
}

extension Double {
    func inMG() -> Measurement<UnitMass> {
        Measurement<UnitMass>(value: self, unit: .milligrams)
    }
}

值得注意的是,用户可能会选择 extra large 的表盘布局,这是 complication 会渲染为单独的,但是更大尺寸的样式,然而开发者不应该借由更多的空间来放下更多的内容,那样就不是 extra large size 了。

另外两种 family

watchOS 没有合适的表盘展示带有 widgetLabel 的 rectangular compications,而 .accessoryInline family 已经借由 widgetLabel 去表达了。