WidgetKit Complications 进阶:accessoryCorner、widgetLabel 与 ClockKit 迁移指南
Go further with Complications in WidgetKit
2022年6月6日
一句话判断
如果你已经在用 WidgetKit 做表盘 Complications,这个 session 帮你搞定 watchOS 独有的 accessoryCorner 家族和 widgetLabel 辅助内容,并给出了完整的 ClockKit 迁移方案——12 个 ClockKit 家族精简到 4 个 WidgetKit 家族。
这场 Session 讲了什么
watchOS 9 把 WidgetKit 带到了手表的 Complications 上,和 iOS 16 的锁屏 Widget 共享同一套 API 基础。但手表有几个独有的需求:表盘角落的位置(accessoryCorner)需要同时显示圆形内容和弧形辅助标签;圆形 Complication 在某些表盘上有额外的表圈空间可以显示文字(比如 Infograph 表盘)。
Session 引入了 widgetLabel 这个 view modifier 来处理辅助内容。在 accessoryCorner 上,你可以用 widgetLabel 添加弧形文字、进度条或 Gauge,系统会自动把主内容缩小来腾出空间。在 accessoryCircular 上,widgetLabel 用于在表圈的额外空间里显示信息。新增的 showsWidgetLabel 环境变量让你根据当前表盘是否显示辅助内容来动态调整布局——这在 Infograph(有表圈)和其他表盘(没有表圈)之间切换时特别有用。
迁移部分覆盖了从 ClockKit 到 WidgetKit 的完整流程。ClockKit 的 12 个 Complication 家族被精简为 4 个:accessoryRectangular、accessoryCircular、accessoryCorner 和 accessoryInline。新增的 CLKComplicationWidgetMigrator 协议让你的 App 更新后,用户表盘上已有的旧 Complication 自动迁移到新的 WidgetKit 版本,无需手动操作。
值得深挖的点
accessoryCorner 是 watchOS 独有的第五个 Widget 家族。 和其他三个 iOS 共享的家族不同,accessoryCorner 有独特的视觉形态:圆形主内容 + 弧形辅助内容。它的渲染由表盘控制——你只负责提供 SwiftUI 内容和 widgetLabel,表盘决定最终的弧形样式。这种设计意味着同一个 Complication 在不同表盘上可能看起来完全不同。我觉得这个设计很聪明,开发者只需要关心内容,不用关心几十种表盘的渲染差异。
ClockKit 到 WidgetKit 的迁移不只是重写代码,还涉及用户体验的无缝过渡。 widgetMigrator 协议的关键在于,当你的 App 更新到 Watch 后,系统会自动检查你的 Widget Extension,然后启动 ClockKit 的数据源来生成迁移配置。这意味着即使你已经完全用 WidgetKit 重写了 Complication,你的 ClockKit 代码还不能删——至少在所有用户都迁移完毕之前。Session 特别提到,如果有人分享了包含你旧 ClockKit Complication 的自定义表盘,系统会再次请求迁移。所以你的迁移逻辑需要保持一致性,不能依赖临时状态。
代码片段
构建 accessoryCorner Complication:
// accessoryCorner:圆形内容 + 弧形辅助标签
struct CoffeeCornerEntry: View {
var entry: CoffeeProvider.Entry
var body: some View {
ZStack {
// 圆形主内容,自动裁剪为圆形
Image(systemName: "cup.and.saucer.fill")
.foregroundColor(.brown)
}
.widgetLabel {
// 弧形辅助内容:可以是 Text、Gauge 或 ProgressView
Gauge(value: entry.caffeineLevel) {
Text("\(entry.caffeineLevel, specifier: "%.0f")mg")
}
}
}
}
用 showsWidgetLabel 适配不同表盘:
struct CoffeeCircularEntry: View {
var entry: CoffeeProvider.Entry
@Environment(\.showsWidgetLabel) var showsWidgetLabel
var body: some View {
if showsWidgetLabel {
// Infograph 表盘:有表圈空间,显示简洁图标
// 详细信息放在 widgetLabel 里
Image(systemName: "cup.and.saucer.fill")
.widgetLabel {
Text("Caffeine: \(entry.caffeineLevel, specifier: "%.0f")mg")
}
} else {
// 没有表圈的表盘:在圆形区域内显示所有信息
Gauge(value: entry.caffeineLevel) {
Text("\(entry.caffeineLevel, specifier: "%.0f")mg")
}
}
}
}
ClockKit 迁移:自动迁移用户表盘上的旧 Complication:
// 在 CLKComplicationDataSource 中实现 WidgetMigrator
class ComplicationController: NSObject, CLKComplicationDataSource {
// 新增的 widgetMigrator 属性
var widgetMigrator: (any CLKComplicationWidgetMigrator)? {
ComplicationMigrator()
}
}
struct ComplicationMigrator: CLKComplicationWidgetMigrator {
func migration(
for complicationDescriptor: CLKComplicationDescriptor
) -> CLKComplicationStaticWidgetMigrationConfiguration? {
// 把旧的 ClockKit descriptor 映射到新的 WidgetKit configuration
return CLKComplicationStaticWidgetMigrationConfiguration(
kind: "CoffeeTrackerWidget",
extensionBundleIdentifier: "com.example.watch.CoffeeTracker.extension"
)
}
}
最佳实践
- 如果你同时支持 ClockKit 和 WidgetKit,先确保 WidgetKit Complication 完善,然后添加
widgetMigrator实现自动迁移。 - 使用
showsWidgetLabel环境变量来适配有表圈和无表圈的表盘,而不是创建两个不同的 Widget。 - Extra Large 表盘使用 accessoryCircular 家族并自动放大内容,不要利用大画布堆砌信息——保持和普通圆形 Complication 相同的内容层级。
- 为 intent-based 的 Complication 迁移时,确保 intent 定义同时包含在 Watch App 和 Widget Extension 的 bundle 中。
- 测试时用不同表盘验证 accessoryCorner 的辅助内容渲染效果。
还有什么值得关注
- accessoryInline 的内容由表盘提取渲染——你提供 Text 和 Image,表盘自己决定渲染样式(平面或弧形)。
- ClockKit 的 12 个家族精简映射:Graphic Bezel/Circular/Corner Large → accessoryCircular,Modular Large/Utilitarian Small Flat → 不再需要独立家族。
widgetLabel在 accessoryRectangular 上不生效,因为矩形 Complication 本身就有足够的显示空间。