Highlight
watchOS 团队的 Devon 和 iOS 团队的 Graham 介绍了 WidgetKit 在 iOS 16 和 watchOS 9 上的重大扩展。核心变化是:watchOS 的复杂功能(Complications)现在基于 WidgetKit 构建,而 iOS 16 新增了锁屏上的 accessory 小组件。
核心内容
Complication 的目标很直接:在表盘上给用户一个一眼可读的高价值信息,并把点击带回 App 的相关位置(01:07)。过去 watchOS 开发者主要围绕 ClockKit 工作。iOS 开发者则用 WidgetKit 做主屏幕 widget。两套入口相近,代码和心智模型却分开。
iOS 16 和 watchOS 9 把这件事合并到 WidgetKit。Apple 给 WidgetFamily 增加了 accessoryRectangular、accessoryCircular、accessoryInline,watchOS 还额外有 accessoryCorner。前 3 个 family 同时出现在 iOS 锁屏和 watchOS 表盘上,开发者能共享 timeline、SwiftUI 视图和现有 Home Screen widget 的基础设施(01:58)。
这场 session 以 Emoji Rangers 为例,从一个已有 iOS Home Screen widget 出发,把同一个 Widget Extension 复制到 watchOS,再逐步适配小尺寸 family。重点不在“把大 widget 缩小”,而在重新决定每个尺寸只显示什么信息:圆形只显示头像和恢复进度,矩形显示名字、等级和倒计时,inline 则用 ViewThatFits 在不同宽度里选择合适文本(09:56)。
最后还要处理锁屏和常亮表盘的隐私环境。iOS 锁屏可能在设备锁定时显示或隐藏内容;watchOS 常亮状态会降低亮度和刷新频率。WidgetKit 提供 isLuminanceReduced 和 privacySensitive(),让同一个视图针对低亮度和隐私遮盖做局部调整(15:02)。
详细内容
1. 用 accessory family 共享 iOS 锁屏和 watchOS 表盘视图
(02:11)新的 accessory family 都挂在 WidgetFamily 上。accessoryRectangular 适合多行文字、小图表;accessoryCircular 适合简短信息、仪表盘和进度;accessoryInline 是一行文字,出现在 watchOS 多个表盘位置以及 iOS 时间上方。
Emoji Rangers 在 Xcode Preview 中同时预览三个 family:
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .spouty))
.previewContext(WidgetPreviewContext(family: .accessoryCircular))
.previewDisplayName("Circular")
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .spouty))
.previewContext(WidgetPreviewContext(family: .accessoryRectangular))
.previewDisplayName("Rectangular")
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .spouty))
.previewContext(WidgetPreviewContext(family: .accessoryInline))
.previewDisplayName("Inline")
#if os(iOS)
关键点:
- 第 1 行创建同一个
EmojiRangerWidgetEntryView,输入同一份 timeline entry。 .accessoryCircular用圆形尺寸检查头像、仪表盘和进度内容是否塞得下。.accessoryRectangular用矩形尺寸检查多行文字和图片组合。.accessoryInline用一行文字尺寸检查是否会被截断。#if os(iOS)来自 demo 里的平台分支,原因是 watchOS 不支持所有 iOS system family(08:38)。
2. 用 widgetAccentable 和背景适配渲染模式
(03:25)Accessory widget 会被系统以 full color、accented、vibrant 三种模式渲染。watchOS 可以完整显示颜色,也可以把内容分成强调组和平常组;iOS 锁屏的 vibrant 模式会先去饱和,再根据锁屏背景映射成材质效果。
widgetAccentable() 用来告诉系统哪些内容属于强调组:
VStack(alignment: .leading) {
Text("Headline")
.font(.headline)
.widgetAccentable()
Text("Body 1")
Text("Body 2")
}.frame(maxWidth: .infinity, alignment: .leading)
关键点:
VStack(alignment: .leading)把多行文字按左侧对齐,符合矩形 accessory 的阅读方式。Text("Headline")是最重要的信息,例如角色名字或状态标题。.font(.headline)使用系统 font style,系统会在 iOS 和 watchOS 上给出各自合适的字重和字形(12:22)。.widgetAccentable()标记标题进入强调颜色组,accented 模式下系统会单独着色。frame(maxWidth: .infinity, alignment: .leading)让内容占满可用宽度,避免矩形区域里出现不必要的居中。
有些 accessory 需要一个系统调校过的背景。Session 用日历圆形样式演示了 AccessoryWidgetBackground:
ZStack {
AccessoryWidgetBackground()
VStack {
Text("MON")
Text("6")
.font(.title)
}
}
关键点:
ZStack把背景和文字叠在同一个圆形区域里。AccessoryWidgetBackground()由系统根据 full color、accented、vibrant 模式切换外观。- 第一个
Text放星期,第二个Text放日期数字。 .font(.title)让日期数字成为视觉主信息。
3. watchOS 需要 IntentRecommendation
(09:12)iOS 的 widget 编辑界面能完整配置 Intent。watchOS 需要开发者提供预配置列表。Emoji Rangers 通过重写 IntentTimelineProvider 的 recommendations 方法返回这些推荐项。
return recommendedIntents()
.map { intent in
return IntentRecommendation(intent: intent, description: intent.hero!.displayString)
}
关键点:
recommendedIntents()是项目已有的推荐 Intent 来源。.map { intent in ... }把每个 Intent 转成 watchOS 可展示的推荐项。IntentRecommendation(intent:description:)把 Intent 本身和编辑界面里显示的描述绑在一起。intent.hero!.displayString来自 Emoji Rangers 的角色配置,用于告诉用户这个推荐项对应哪个 hero。
4. 小尺寸界面依赖自动更新的 ProgressView 和日期文本
(10:37)圆形 accessory 适合显示一个头像,再用边缘进度表达恢复时间。普通做法需要频繁生成 timeline entry,才能让进度动起来。SwiftUI 的自动更新 ProgressView 接受一个日期区间,系统会保持进度更新,因此这里只需要一个 timeline entry。
ProgressView(interval: entry.character.injuryDate...entry.character.fullHealthDate,
countdown: false,
label: { Text(entry.character.name) },
currentValueLabel: {
Avatar(character: entry.character, includeBackground: false)
})
.progressViewStyle(.circular)
关键点:
interval:传入受伤时间到完全恢复时间的闭区间。countdown: false表示进度按区间推进,而非倒计时样式。label提供角色名字,给进度视图一个语义标签。currentValueLabel把当前值区域替换成角色头像。.progressViewStyle(.circular)让进度贴合圆形 accessory 的外观。
矩形 accessory 空间更大,适合放三行信息和头像:
case .accessoryRectangular:
HStack(alignment: .center, spacing: 0) {
VStack(alignment: .leading) {
Text(entry.character.name)
Text("Level \(entry.character.level)")
Text(entry.character.fullHealthDate, style: .timer)
}.frame(maxWidth: .infinity, alignment: .leading)
Avatar(character: entry.character, includeBackground: false)
}
关键点:
case .accessoryRectangular为矩形 family 单独提供布局。HStack左边放文字,右边放头像。- 第一行显示角色名,第二行显示等级。
Text(entry.character.fullHealthDate, style: .timer)使用自动更新的日期文本显示剩余时间。Avatar(... includeBackground: false)避免在小尺寸区域里叠加多余背景。
5. 用 ViewThatFits 处理 inline 宽度差异
(13:13)Inline accessory 是一行文本,watchOS 表盘里的可用宽度差异很大。ViewThatFits 接受一组候选 view,系统选择第一个能完整放下、不会截断或裁剪的版本。
ViewThatFits {
Text("\(entry.character.name) is resting, combat-ready in \(entry.character.fullHealthDate, style: .relative)")
Text("\(entry.character.name) ready in \(entry.character.fullHealthDate, style: .timer)")
Text("\(entry.character.avatar) \(entry.character.fullHealthDate, style: .timer)")
}
关键点:
- 第一个
Text信息最完整,适合 iOS 锁屏或较宽的 watch slot。 - 第二个
Text缩短文案,把核心保留为角色名和倒计时。 - 第三个
Text用头像替代名字,服务最窄的表盘位置。 - 候选顺序从长到短,确保有空间时优先展示更完整的信息。
6. 低亮度和隐私遮盖要拆开处理
(16:18)watchOS 常亮状态会进入低亮度和低刷新频率。开发者能用 isLuminanceReduced 判断当前环境,移除时间敏感内容,换成更适合常亮显示的内容。
@Environment(\.isLuminanceReduced)
var isLuminanceReduced
var body: some View {
if isLuminanceReduced {
Text("🙈").font(.title)
} else {
Text("🐵").font(.title)
}
}
关键点:
@Environment(\.isLuminanceReduced)从 SwiftUI 环境读取低亮度状态。if isLuminanceReduced进入 always-on 的低亮度分支。- 低亮度分支显示更静态的内容,避免依赖高频更新。
- 普通分支显示完整内容。
隐私遮盖则针对敏感信息。默认 privacy mode 会显示 placeholder 的 redacted 版本;如果只有部分内容敏感,可以给具体 view 加 .privacySensitive()(16:52)。
VStack(spacing: -2) {
Image(systemName: "heart")
.font(.caption.bold())
.widgetAccentable()
Text("\(currentHeartRate)")
.font(.title)
.privacySensitive()
}
关键点:
Image(systemName: "heart")是心率图标,本身不包含用户隐私值。.widgetAccentable()让图标适配 accessory 的强调颜色。Text("\(currentHeartRate)")显示具体心率,这一项是敏感数据。.privacySensitive()只标记心率文本,系统遮盖时仍保留图标。
核心启发
-
做什么:把已有 Home Screen widget 扩展到锁屏和 Apple Watch。 为什么值得做: 新的 accessory family 和 watchOS Widget Extension 能复用现有 WidgetKit timeline 与 SwiftUI 视图。 怎么开始: 复制现有 iOS widget extension 为 watchOS target,按平台分支声明
supportedFamilies,先补齐.accessoryCircular、.accessoryRectangular、.accessoryInline的预览。 -
做什么:为状态型 App 做一个圆形恢复进度 complication。 为什么值得做:
ProgressView(interval:)能由系统自动更新,适合健身恢复、专注计时、游戏角色冷却这类时间区间。 怎么开始: 在圆形 family 中把核心图标放进currentValueLabel,把开始和结束时间传给ProgressView(interval:countdown:label:currentValueLabel:)。 -
做什么:给一行状态文案写三档长度。 为什么值得做: Inline accessory 的宽度会随表盘和锁屏位置变化,单一长句很容易被截断。 怎么开始: 用
ViewThatFits按“完整句子、短句、图标加时间”的顺序提供候选 view,并在 Xcode Preview 中检查.accessoryInline。 -
做什么:为锁屏数据建立隐私分级。 为什么值得做: iOS 锁屏和 watchOS 常亮状态都可能展示在旁人可见的环境里,WidgetKit 支持只遮盖敏感 view。 怎么开始: 把心率、余额、消息内容等具体数值标记为
.privacySensitive(),图标、标题、类别名称保持可见。 -
做什么:为 always-on watch face 准备低亮度版本。 为什么值得做: 常亮状态刷新频率更低,时间敏感文字和动态进度需要降级。 怎么开始: 读取
@Environment(\.isLuminanceReduced),在低亮度分支中移除倒计时、动画或快速变化数据,保留静态图标和概括状态。
关联 Session
- Go further with Complications in WidgetKit — 继续看 watchOS 专属 family、gauge、widget label,以及从 ClockKit 迁移到 WidgetKit 的细节。
- What’s new in SwiftUI — 补齐 SwiftUI 2022 的布局、图形和界面结构更新,理解这些 view modifier 的上下文。
- Compose custom layouts with SwiftUI — 深入
ViewThatFits和自定义 layout,适合处理 accessory widget 的小尺寸适配。 - Build a productivity app for Apple Watch — 从完整 watchOS App 角度理解 SwiftUI、文本输入、图表和分享如何服务手腕场景。
评论
GitHub Issues · utterances