Highlight
iOS 17 让小组件从只读展示变成可交互控件:开发者可以通过 Button 和 Toggle 在小组件上直接触发 AppIntents,配合新的动画 API 让时间线条目切换时有平滑的内容过渡。
核心内容
以前的小组件:只能看,不能碰
WidgetKit 在 iOS 14 引入小组件时,它们的作用很单一:展示信息。用户点击小组件只会打开 App,小组件本身不响应任何手势。你想让用户在小部件上完成一个操作——比如勾选一个待办、记录一杯咖啡——必须把用户带进 App。
这对开发者来说意味着:小组件只是一个展示窗口,不是一个功能入口。用户每次操作都要经历”点小组件 -> 进 App -> 操作 -> 返回”的流程。
iOS 17 的改变:小组件支持交互了
iOS 17 为 WidgetKit 加入了两个关键能力:
- 交互控件:小组件可以包含 Button 和 Toggle,用户点击后直接执行 AppIntent,不需要打开 App。
- 动画过渡:时间线条目切换时可以自定义动画和内容过渡,不再是一闪而过。
这两个能力结合起来,小组件从一个”信息展示板”变成了一个”迷你 App 操作面板”。
这一切背后的机制:AppIntents
小组件交互的核心是 AppIntents 框架。Button 和 Toggle 的初始化方法接受一个 AppIntent 对象,用户点击时系统执行该 Intent 的 perform() 方法。perform() 返回后,WidgetKit 自动刷新小组件 UI。
这个设计意味着:小组件不需要管理自己的状态——它只是触发了 App 的业务逻辑,然后显示最新的结果。
详细内容
自定义小组件背景:containerBackground
iOS 17 提供了新的 containerBackground 修饰符,专门用于小组件的背景设置(03:54):
.containerBackground(for: .widget) {
Color.cosmicLatte
}
关键点:
containerBackground(for: .widget)是 WidgetKit 新增的修饰符,取代了旧的background用法- 系统会根据小组件所在位置(主屏幕、锁屏、待机模式)自动调整背景的渲染方式
- 在待机模式下,系统会为背景添加合适的圆角和边距
预览小组件动画:#Preview 宏
Xcode 15 提供了新的 #Preview 宏,可以直接预览小组件的时间线动画效果(04:22):
#Preview(as: WidgetFamily.systemSmall) {
CaffeineTrackerWidget()
} timeline: {
CaffeineLogEntry.log1
CaffeineLogEntry.log2
CaffeineLogEntry.log3
CaffeineLogEntry.log4
}
关键点:
#Preview(as:)指定小组件尺寸,支持systemSmall、systemMedium、systemLarge等timeline:闭包返回多个时间线条目,预览会自动在这些条目之间切换- 切换时会播放你定义的动画和过渡效果,不需要在真机上测试
数值变化的平滑过渡:contentTransition
小组件中的数字变化可以使用 .contentTransition(.numericText(value:)) 实现平滑的数值滚动效果(05:41):
struct TotalCaffeineView: View {
let totalCaffeine: Measurement<UnitMass>
var body: some View {
VStack(alignment: .leading) {
Text("Total Caffeine")
.font(.caption)
Text(totalCaffeine.formatted())
.font(.title)
.minimumScaleFactor(0.8)
.contentTransition(.numericText(value: totalCaffeine.value))
}
.foregroundColor(.espresso)
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
}
}
关键点:
.contentTransition(.numericText(value:))接受一个 Double 值,当这个值变化时,文本会以数字滚动的动画方式过渡totalCaffeine.value来自Measurement<UnitMass>的值属性- 这种过渡只对数字文本生效,比默认的淡入淡出更有信息量
- 适合展示计数、金额、度量值等场景
条目之间的推入过渡:transition 和 animation
当一个时间线条目切换到另一个时,可以用 .transition 和 .animation 控制过渡效果(06:21):
struct LastDrinkView: View {
let log: CaffeineLog
var body: some View {
VStack(alignment: .leading) {
Text(log.drink.name)
.bold()
Text("\(log.date, format: Self.dateFormatStyle) · \(caffeineAmount)")
}
.font(.caption)
.id(log)
.transition(.push(from: .bottom))
.animation(.smooth(duration: 1.8), value: log)
}
var caffeineAmount: String {
log.drink.caffeine.formatted()
}
static var dateFormatStyle = Date.FormatStyle(
date: .omitted, time: .shortened)
}
关键点:
.id(log)告诉 SwiftUI 当log对象变化时,这是不同的视图,应触发过渡动画.transition(.push(from: .bottom))定义过渡方式:旧条目向上推出,新条目从底部推入.animation(.smooth(duration: 1.8), value: log)指定动画曲线和时长,且仅在log变化时触发duration: 1.8给出较长的动画时间,让过渡在小组件上看起来更自然
手动刷新小组件时间线
当 App 内的数据发生变化后,需要通知 WidgetKit 刷新小组件(09:18):
WidgetCenter.shared.reloadTimelines(ofKind: "LocationForecast")
关键点:
WidgetCenter.shared.reloadTimelines(ofKind:)刷新指定种类的小组件- 应该在你的 App 修改了小组件依赖的数据后调用
- 适合在 AppIntent 的
perform()方法中修改数据后调用,但 Button/Toggle 的 AppIntent 会自动触发刷新
用 AppIntent 实现交互:Button
这是小组件交互的核心机制。首先定义一个 AppIntent(13:06):
import AppIntents
struct LogDrinkIntent: AppIntent {
static var title: LocalizedStringResource = "Log a drink"
static var description = IntentDescription("Log a drink and its caffeine amount.")
@Parameter(title: "Drink", optionsProvider: DrinksOptionsProvider())
var drink: Drink
init() {}
init(drink: Drink) {
self.drink = drink
}
func perform() async throws -> some IntentResult {
await DrinksLogStore.shared.log(drink: drink)
return .result()
}
}
关键点:
AppIntent协议来自AppIntents框架,需要import AppIntentsstatic var title和description用于 Siri 和快捷指令的展示@Parameter标记的属性能从外部传入值,optionsProvider提供可选值列表perform()方法是实际执行业务逻辑的地方,DrinksLogStore.shared.log(drink:)是自定义的数据存储方法perform()返回.result()后,WidgetKit 自动重新加载小组件
然后在小组件视图中使用 Button 绑定这个 Intent(15:10):
struct LogDrinkView: View {
var body: some View {
Button(intent: LogDrinkIntent(drink: .espresso)) {
Label("Espresso", systemImage: "plus")
.font(.caption)
}
.tint(.espresso)
}
}
关键点:
Button(intent:label:)是 iOS 17 新增的初始化方法- 点击按钮时执行
LogDrinkIntent的perform()方法,不需要打开 App - 可以用
.tint()设置按钮颜色 - 同一个小组件可以有多个按钮,每个绑定不同的 Intent
减少感知延迟:invalidatableContent
当用户点击按钮触发 AppIntent 后,数据更新到小组件刷新之间有时间差。.invalidatableContent() 可以让小组件先标记内容即将变化(16:28):
struct TotalCaffeineView: View {
let totalCaffeine: Measurement<UnitMass>
var body: some View {
VStack(alignment: .leading) {
Text("Total Caffeine")
.font(.caption)
Text(totalCaffeine.formatted())
.font(.title)
.minimumScaleFactor(0.8)
.contentTransition(.numericText(value: totalCaffeine.value))
.invalidatableContent()
}
.foregroundColor(.espresso)
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
}
}
关键点:
.invalidatableContent()标记该视图内容会在新数据到达前变化- 当 AppIntent 执行中时,被标记的视图会显示过渡状态,而不是卡住不动
- 配合
.contentTransition(.numericText(value:))使用效果更好 - 适合需要给用户即时反馈的场景
核心启发
-
做一个咖啡/饮水记录小组件:在小组件上放几个饮料按钮(浓缩咖啡、拿铁、水),点击后记录到 HealthKit 或本地存储,总摄入量用数字滚动动画展示。为什么值得做:这个 session 的演示项目就是咖啡因追踪器,Button + AppIntent + contentTransition 三个能力全用上了。怎么开始:定义 Drink 类型和
LogDrinkIntent,用Button(intent:)在小组件上创建几个饮料按钮,perform()中调用WidgetCenter.shared.reloadTimelines。 -
做一个番茄钟小组件:小组件显示当前任务和剩余时间,用 Toggle 开始/暂停计时,时间用
.contentTransition(.numericText(value:))做倒数动画。为什么值得做:Toggle 是小组件支持的第二个交互控件,倒计时是最自然的数字过渡场景。怎么开始:创建一个TimerIntent,用Toggle(isOn:intent:label:)绑定计时状态,在perform()中启动/暂停后台计时器。 -
做一个习惯打卡小组件:显示今日待完成习惯列表,每个习惯旁边放一个 Button,点击后标记完成,完成条目用
.transition(.push(from: .bottom))向下移出。为什么值得做:习惯追踪是小组件的高频使用场景,动画过渡让打卡体验更流畅。怎么开始:为每个习惯创建MarkHabitIntent,用 ForEach 在小组件中列出习惯,每个配一个 Button。 -
做一个快捷支付小组件:在小组件上放几个常用金额按钮(早餐 15 元、咖啡 25 元、午餐 40 元),点击后记录到记账 App,月总支出用数字动画展示。为什么值得做:记账最大的摩擦是打开 App 输入金额,小组件按钮把操作简化到一次点击。怎么开始:创建
RecordExpenseIntent,@Parameter接收金额和分类,perform()中写入本地数据库。 -
做一个智能家居控制小组件:用 Toggle 控制灯光开关,用 Button 切换场景模式,按钮颜色用
.tint()匹配设备状态。为什么值得做:智能家居控制是小组件交互的典型场景——用户不需要打开 App 就能开关设备。怎么开始:为每个设备创建ToggleDeviceIntent,在 HomeKit 或 MQTT 层执行操作,用.invalidatableContent()处理命令执行中的延迟。
关联 Session
- Bring widgets to new places — 小组件在 iOS 17 中扩展到锁屏、待机模式和 Mac 桌面,了解如何适配不同环境
- Meet SwiftUI for spatial computing — SwiftUI 在空间计算中的使用方式,与小组件的交互机制共享相同的 AppIntents 基础
- Explore enhancements to App Intents — AppIntents 框架的最新增强,小组件交互的底层引擎
- Design dynamic Live Activities — 实时活动也支持同样的 Button 和 Toggle 交互,动态岛上也可以操作
- What’s new in AppKit — macOS 上的小组件交互支持,桌面小组件同样可以用 Button 和 Toggle
评论
GitHub Issues · utterances