WWDC22: watchOS 上用 WidgetKit 构建 Complications
自 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
去表达了。