widgetkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWidgetKit Development Skill
WidgetKit 开发技能
Build glanceable, timely experiences across Apple platforms using WidgetKit.
使用WidgetKit在Apple全平台构建一目了然、时效性强的体验。
Quick Start
快速入门
Create Widget Extension
创建小组件扩展
- File → New → Target → Widget Extension
- Deselect "Include Live Activity" and "Include Configuration App Intent" for static widgets
- Widget requires: protocol,
Widget, and SwiftUI viewsTimelineProvider
- 文件 → 新建 → 目标 → 小组件扩展
- 若创建静态小组件,取消勾选“Include Live Activity”和“Include Configuration App Intent”
- 小组件需要:协议、
Widget,以及SwiftUI视图TimelineProvider
Minimal Widget Structure
最简小组件结构
swift
@main
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.app.mywidget",
provider: Provider()
) { entry in
MyWidgetView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("Shows key information")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: .now)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
completion(SimpleEntry(date: .now))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
let entry = SimpleEntry(date: .now)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}swift
@main
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.app.mywidget",
provider: Provider()
) { entry in
MyWidgetView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("Shows key information")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: .now)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
completion(SimpleEntry(date: .now))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
let entry = SimpleEntry(date: .now)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}Widget Families & Sizes
小组件类型与尺寸
| Family | Platforms | Use Case |
|---|---|---|
| iOS, iPadOS, macOS, visionOS | Single tap target, glanceable info |
| iOS, iPadOS, macOS, visionOS | Multiple data points, interactive elements |
| iOS, iPadOS, macOS, visionOS | Rich content, multiple interactions |
| iPadOS, macOS, visionOS | Dashboard-style layouts |
| iOS Lock Screen, watchOS | Minimal info, gauge-style |
| iOS Lock Screen, watchOS | 2-3 lines of text |
| iOS Lock Screen, watchOS | Single line text + optional image |
| watchOS only | Corner complications |
| 类型 | 适用平台 | 使用场景 |
|---|---|---|
| iOS、iPadOS、macOS、visionOS | 单点交互目标,信息一目了然 |
| iOS、iPadOS、macOS、visionOS | 多数据展示,支持交互元素 |
| iOS、iPadOS、macOS、visionOS | 丰富内容展示,多交互操作 |
| iPadOS、macOS、visionOS | 仪表盘式布局 |
| iOS 锁屏、watchOS | 极简信息展示,仪表盘样式 |
| iOS 锁屏、watchOS | 2-3行文本展示 |
| iOS 锁屏、watchOS | 单行文本 + 可选图片 |
| 仅watchOS | 角落复杂功能 |
Adapt to Widget Family
适配小组件类型
swift
struct MyWidgetView: View {
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall: CompactView()
case .systemMedium: MediumView()
case .systemLarge: DetailedView()
case .accessoryCircular: GaugeView()
case .accessoryRectangular: RectangularView()
default: CompactView()
}
}
}swift
struct MyWidgetView: View {
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall: CompactView()
case .systemMedium: MediumView()
case .systemLarge: DetailedView()
case .accessoryCircular: GaugeView()
case .accessoryRectangular: RectangularView()
default: CompactView()
}
}
}Rendering Modes
渲染模式
Widgets render differently based on context:
| Mode | When Used | Behavior |
|---|---|---|
| Home Screen (iOS 17-), macOS desktop | Full color preserved |
| Home Screen tinted/clear, visionOS, watchOS | Divides into accent + primary groups |
| Lock Screen, StandBy | Desaturated, blurred effect |
swift
@Environment(\.widgetRenderingMode) var renderingMode
var body: some View {
switch renderingMode {
case .fullColor: FullColorView()
case .accented: AccentedView()
case .vibrant: VibrantView()
@unknown default: FullColorView()
}
}小组件会根据上下文采用不同的渲染方式:
| 模式 | 适用场景 | 表现 |
|---|---|---|
| 主屏幕(iOS 17-)、macOS桌面 | 保留完整色彩 |
| 主屏幕 tinted/clear 样式、visionOS、watchOS | 分为强调色 + 主色两组 |
| 锁屏、StandBy | 去饱和、模糊效果 |
swift
@Environment(\.widgetRenderingMode) var renderingMode
var body: some View {
switch renderingMode {
case .fullColor: FullColorView()
case .accented: AccentedView()
case .vibrant: VibrantView()
@unknown default: FullColorView()
}
}Interactivity
交互性
Buttons & Toggles (iOS 17+)
按钮与开关(iOS 17+)
swift
Button(intent: RefreshIntent()) {
Label("Refresh", systemImage: "arrow.clockwise")
}
Toggle(isOn: $isEnabled, intent: ToggleIntent()) {
Text("Enable")
}swift
Button(intent: RefreshIntent()) {
Label("Refresh", systemImage: "arrow.clockwise")
}
Toggle(isOn: $isEnabled, intent: ToggleIntent()) {
Text("Enable")
}Deep Links
深度链接
swift
MyWidgetView()
.widgetURL(URL(string: "myapp://detail/123")!)
// Or for multiple links in larger widgets:
Link(destination: URL(string: "myapp://item/1")!) {
ItemView()
}swift
MyWidgetView()
.widgetURL(URL(string: "myapp://detail/123")!)
// Or for multiple links in larger widgets:
Link(destination: URL(string: "myapp://item/1")!) {
ItemView()
}Configuration Types
配置类型
| Type | Use Case |
|---|---|
| No user configuration needed |
| User-configurable (iOS 17+) |
| Live Activities |
| 类型 | 使用场景 |
|---|---|
| 无需用户配置 |
| 支持用户配置(iOS 17+) |
| Live Activities 场景 |
Reference Documentation
参考文档
- Timeline & Updates: Timeline providers, reload policies, push updates
- Interactivity: Buttons, toggles, App Intents, deep links
- Live Activities: ActivityKit, Dynamic Island, Lock Screen
- Design Guidelines: HIG best practices, layout, typography
- Platform Specifics: iOS, watchOS, macOS, visionOS differences
- Dimensions: Exact sizes for all widget families per device
- 时间线与更新: 时间线提供器、刷新策略、推送更新
- 交互性: 按钮、开关、App Intents、深度链接
- Live Activities: ActivityKit、灵动岛、锁屏
- 设计指南: HIG最佳实践、布局、排版
- 平台差异: iOS、watchOS、macOS、visionOS的区别
- 尺寸规格: 各设备所有小组件类型的精确尺寸
Key Constraints
关键限制
- No real-time updates: Use timelines; system batches updates
- Limited budget: ~40-70 refreshes/day depending on usage
- No continuous animations: Only transition animations up to 2 seconds
- SwiftUI only: UIKit views not supported
- Stateless: No persistent state between renders
- No network in views: Fetch data in timeline provider only
- 无实时更新: 使用时间线机制;系统会批量处理更新
- 刷新次数限制: 每日约40-70次刷新,具体取决于使用情况
- 无连续动画: 仅支持最长2秒的过渡动画
- 仅支持SwiftUI: 不支持UIKit视图
- 无状态: 渲染之间不保留持久化状态
- 视图中禁止网络请求: 仅可在时间线提供器中获取数据
Common Patterns
常见模式
Share Data with Main App
与主应用共享数据
swift
// Use App Groups
let sharedDefaults = UserDefaults(suiteName: "group.com.app.shared")
// Or shared container
let containerURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.app.shared"
)swift
// 使用App Groups
let sharedDefaults = UserDefaults(suiteName: "group.com.app.shared")
// 或共享容器
let containerURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.app.shared"
)Force Widget Refresh
强制刷新小组件
swift
import WidgetKit
WidgetCenter.shared.reloadTimelines(ofKind: "com.app.mywidget")
WidgetCenter.shared.reloadAllTimelines()swift
import WidgetKit
WidgetCenter.shared.reloadTimelines(ofKind: "com.app.mywidget")
WidgetCenter.shared.reloadAllTimelines()Placeholder for Sensitive Content
敏感内容占位符
swift
.privacySensitive() // Redacts when device lockedswift
.privacySensitive() // 设备锁定时自动模糊内容