widgetkit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WidgetKit Development Skill

WidgetKit 开发技能

Build glanceable, timely experiences across Apple platforms using WidgetKit.
使用WidgetKit在Apple全平台构建一目了然、时效性强的体验。

Quick Start

快速入门

Create Widget Extension

创建小组件扩展

  1. File → New → Target → Widget Extension
  2. Deselect "Include Live Activity" and "Include Configuration App Intent" for static widgets
  3. Widget requires:
    Widget
    protocol,
    TimelineProvider
    , and SwiftUI views
  1. 文件 → 新建 → 目标 → 小组件扩展
  2. 若创建静态小组件,取消勾选“Include Live Activity”和“Include Configuration App Intent”
  3. 小组件需要:
    Widget
    协议、
    TimelineProvider
    ,以及SwiftUI视图

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

小组件类型与尺寸

FamilyPlatformsUse Case
systemSmall
iOS, iPadOS, macOS, visionOSSingle tap target, glanceable info
systemMedium
iOS, iPadOS, macOS, visionOSMultiple data points, interactive elements
systemLarge
iOS, iPadOS, macOS, visionOSRich content, multiple interactions
systemExtraLarge
iPadOS, macOS, visionOSDashboard-style layouts
accessoryCircular
iOS Lock Screen, watchOSMinimal info, gauge-style
accessoryRectangular
iOS Lock Screen, watchOS2-3 lines of text
accessoryInline
iOS Lock Screen, watchOSSingle line text + optional image
accessoryCorner
watchOS onlyCorner complications
类型适用平台使用场景
systemSmall
iOS、iPadOS、macOS、visionOS单点交互目标,信息一目了然
systemMedium
iOS、iPadOS、macOS、visionOS多数据展示,支持交互元素
systemLarge
iOS、iPadOS、macOS、visionOS丰富内容展示,多交互操作
systemExtraLarge
iPadOS、macOS、visionOS仪表盘式布局
accessoryCircular
iOS 锁屏、watchOS极简信息展示,仪表盘样式
accessoryRectangular
iOS 锁屏、watchOS2-3行文本展示
accessoryInline
iOS 锁屏、watchOS单行文本 + 可选图片
accessoryCorner
仅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:
ModeWhen UsedBehavior
fullColor
Home Screen (iOS 17-), macOS desktopFull color preserved
accented
Home Screen tinted/clear, visionOS, watchOSDivides into accent + primary groups
vibrant
Lock Screen, StandByDesaturated, 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()
    }
}
小组件会根据上下文采用不同的渲染方式:
模式适用场景表现
fullColor
主屏幕(iOS 17-)、macOS桌面保留完整色彩
accented
主屏幕 tinted/clear 样式、visionOS、watchOS分为强调色 + 主色两组
vibrant
锁屏、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

配置类型

TypeUse Case
StaticConfiguration
No user configuration needed
AppIntentConfiguration
User-configurable (iOS 17+)
ActivityConfiguration
Live Activities
类型使用场景
StaticConfiguration
无需用户配置
AppIntentConfiguration
支持用户配置(iOS 17+)
ActivityConfiguration
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 locked
swift
.privacySensitive() // 设备锁定时自动模糊内容