WidgetKit 的基础概念 #
WidgetKit 是 Apple 推出的框架,用于在 iOS、iPadOS、watchOS 和 macOS 上创建 小组件(Widgets)。这些小组件是你应用的扩展,可以出现在主屏幕、锁屏、智能叠放或通知中心,为用户提供 快速查看信息 的能力,而无需打开完整的 App。
WidgetKit 的核心特点: #
使用 SwiftUI 构建 UI:全部基于声明式编程。
生命周期由系统控制:小组件不能自由刷新的时间,必须由系统决定。
时间线驱动(Timeline):你需要为小组件提供一个“时间线”(
TimelineEntry
),系统会根据时间推进更新小组件内容。静态配置与动态配置:支持定义参数(如选择某个主题、过滤条件等)。
Widget 的结构: #
一个 Widget 通常包括以下几部分:
组件 | 作用 |
---|---|
TimelineProvider | 提供时间线和占位数据 |
TimelineEntry | 每一个特定时间点显示的数据 |
WidgetConfiguration | 声明 Widget 的基本信息与类型(静态/动态) |
View | 用 SwiftUI 构建显示内容 |
WidgetKit 与 ActivityKit、App Intents 的关系 #
1. WidgetKit vs ActivityKit #
维度 | WidgetKit | ActivityKit |
---|---|---|
用途 | 显示静态或半动态的 Widget(如天气、日历) | 显示正在进行的实时活动,如计时器、外卖配送 |
显示位置 | 主屏幕、锁屏、通知中心 | 锁屏、灵动岛(Dynamic Island) |
刷新机制 | 由时间线控制,受限 | 实时更新(Live Activity),通过 Activity<ContentState> |
内容类型 | 通常是周期性信息或快照 | 当前状态 + 动态进展(实时) |
使用技术 | WidgetKit + Timeline | ActivityKit(底层仍然使用 WidgetKit 渲染 UI) |
✅ 联系:Live Activity 本质上也是一种特殊类型的 Widget,它使用的是 WidgetKit 渲染 UI,但通过 ActivityKit 提供实时状态更新。
2. WidgetKit 与 App Intents #
App Intents 框架提供了一种让你的 App、Widget、Live Activity 等与 Siri、快捷指令、聚焦模式等系统功能集成的方式。
维度 | WidgetKit | App Intents |
---|---|---|
作用 | 显示数据 | 接收命令或提供动态内容(如 Siri Shortcuts、交互式 Widget) |
示例 | 展示一个任务列表 Widget | 用户通过 Siri 或点击 Widget 启动特定任务 |
关系 | Widget 可以通过绑定 AppIntent 提供交互能力 | Widget 和 Live Activity 都可与 App Intents 配合,实现深度交互 |
✅ 联系:
App Intents 可用于定义 Widget 的交互(如点击启动某个功能)。
也可用于配置 Widget 内容(如通过 IntentConfiguration 实现动态筛选)。
总结关系图: #
┌─────────────┐
│ App Intents │
└────┬────────┘
│ 提供交互、启动任务
┌───────────────┼─────────────────┐
│ │
┌───────▼─────────┐ ┌─────────▼────────┐
│ WidgetKit │ │ ActivityKit │
│ (小组件/信息卡) │ │ (Live Activity) │
└───────────────┘ └─────────────────┘
WidgetKit 是核心 UI 框架。
ActivityKit 基于 WidgetKit 实现实时信息展示(Live Activity)。
App Intents 提供交互能力,把用户行为与 Widget、Live Activity 连接起来。
如果你想,我也可以为你画一张逻辑图或者构建一个小例子(比如:带有 App Intent 的计时器 Widget + Live Activity)。需要吗?
当然,光看概念确实抽象,我们通过几个具体的 WidgetKit 实际使用例子 来深入理解它的应用场景和机制。这些例子覆盖从静态 Widget 到动态配置、交互、甚至与 ActivityKit 的结合。
🧭 示例 1:展示今天的任务列表(静态小组件) #
场景: #
你有一个 To-Do App,想在主屏幕展示用户今天的任务摘要。
特点: #
静态 Widget:展示固定数据,不能互动。
时间线刷新:例如每小时刷新。
关键代码片段: #
struct TaskEntry: TimelineEntry {
let date: Date
let tasks: [String]
}
struct TaskProvider: TimelineProvider {
func placeholder(in context: Context) -> TaskEntry {
TaskEntry(date: Date(), tasks: ["任务1", "任务2"])
}
func getTimeline(in context: Context, completion: @escaping (Timeline<TaskEntry>) -> Void) {
let entry = TaskEntry(date: Date(), tasks: fetchTodayTasks())
let timeline = Timeline(entries: [entry], policy: .after(Date().addingTimeInterval(3600)))
completion(timeline)
}
}
struct TaskWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "task_widget", provider: TaskProvider()) { entry in
VStack(alignment: .leading) {
ForEach(entry.tasks.prefix(3), id: \.self) { task in
Text("• \(task)").font(.headline)
}
}
.padding()
}
.configurationDisplayName("今日任务")
.description("展示今日的任务列表")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
⏱️ 示例 2:计时器 Live Activity(结合 ActivityKit) #
场景: #
一个 Pomodoro App 在锁屏或灵动岛显示当前倒计时进度。
特点: #
实时更新状态
使用
ActivityKit
创建LiveActivity<ContentState>
实例UI 依然是用
WidgetKit
渲染
简化结构: #
struct TimerAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var timeRemaining: TimeInterval
}
var timerName: String
}
struct TimerLiveActivityView: View {
let context: ActivityViewContext<TimerAttributes>
var body: some View {
VStack {
Text(context.attributes.timerName)
Text("\(Int(context.state.timeRemaining)) 秒")
}
}
}
启动 Live Activity 示例:
let attributes = TimerAttributes(timerName: "番茄钟")
let contentState = TimerAttributes.ContentState(timeRemaining: 1500)
let activity = try? Activity<TimerAttributes>.request(
attributes: attributes,
contentState: contentState,
pushType: nil
)
🧠 示例 3:动态内容 Widget + App Intents(可选项目) #
场景: #
用户可以选择一个分类,比如“学习”、“工作”,Widget 就展示对应分类的任务。
特点: #
使用
IntentConfiguration
实现自定义的
AppEntity
与 Intent
示例配置: #
struct CategoryIntent: AppEntity {
var id: String { name }
var name: String
static var defaultQuery = CategoryQuery()
static func getAllEntities(from query: CategoryQuery) async throws -> [CategoryIntent] {
// 从数据库读取所有分类
}
}
struct TaskByCategoryWidget: Widget {
var body: some WidgetConfiguration {
IntentConfiguration(
kind: "task_by_category",
intent: SelectCategoryIntent.self,
provider: TaskByCategoryProvider()
) { entry in
Text("分类:\(entry.category.name)")
}
}
}
☀️ 示例 4:天气 Widget(系统内置风格) #
场景: #
展示用户当前城市的天气和温度。
特点: #
时间线更新频繁(如每小时)
使用
URLSession
获取数据,缓存后展示
系统的“天气”App 就是一个典型的复杂 WidgetKit 应用,还结合了地理定位权限、图标展示等。
🧩 示例 5:交互式 Widget(iOS 17+) #
场景: #
用户点击 Widget 中的“开始计时”按钮,立即在主 App 启动计时器。
特点: #
iOS 17 开始支持 Widget 中的交互
使用
AppIntents
+Button(intent:)
struct StartTimerIntent: AppIntent {
static var title: LocalizedStringResource = "开始计时"
func perform() async throws -> some IntentResult {
// 触发计时逻辑
return .result()
}
}
struct InteractiveWidgetView: View {
var body: some View {
VStack {
Text("00:00")
Button(intent: StartTimerIntent()) {
Text("开始计时")
}
}
}
}
总结 #
示例 | 类型 | 技术点 |
---|---|---|
任务列表 | 静态 Widget | 时间线、VStack UI |
计时器 | Live Activity | ActivityKit + WidgetKit |
分类筛选 | 动态配置 Widget | AppEntity + IntentConfiguration |
天气 | 定时数据更新 | 网络请求 + Timeline |
交互式按钮 | 交互式 Widget | iOS 17+, AppIntents |