實作、審查或改善 WidgetKit 的小工具與控制項目。用於建置主畫面、鎖定畫面、待機模式或 CarPlay 的小工具,搭配時間軸提供者;可設定的小工具搭配 AppIntentTimelineProvider;互動式小工具或控制中心的按鈕/切換開關;WidgetKit 推播重新整理、更新配額、深層連結、智慧堆疊關聯性、Liquid Glass/強調色渲染、小工具擴充設定、WidgetBundle、App Groups 與權利。
WidgetKit
建置 iOS 26+ 的主畫面小工具、鎖定畫面小工具、控制中心控制項,以及待機模式或 CarPlay 小工具介面。
將相鄰框架的指引範圍限定在 WidgetKit 整合。僅在 ActivityKit 和 App Intents 直接連接到 WidgetKit 介面時才納入;將完整的生命週期、APNs content-state、Siri/捷徑/Spotlight 或實體建模工作交給同級的 activitykit 或 app-intents 技能。
請參閱 references/widgetkit-advanced.md 了解時間軸策略、推播更新、Xcode 設定及進階模式。
目錄
- 工作流程
- Widget 協定與 WidgetBundle
- 設定類型
- TimelineProvider
- AppIntentTimelineProvider
- 小工具系列
- 互動式小工具 (iOS 17+)
- ActivityConfiguration 交接
- 控制中心小工具 (iOS 18+)
- 鎖定畫面小工具
- 待機模式
- 小工具 URL 處理與深層連結
- 智慧堆疊關聯性
- 設計模式
- iOS 26 新增功能
- 常見錯誤
- 審查檢查清單
- 參考資料
工作流程
1. 建立新小工具
- 在 Xcode 中新增 Widget Extension 目標(File > New > Target > Widget Extension)。
- 啟用 App Groups 以在 App 與小工具擴充之間共享資料。
- 定義一個包含
date屬性與顯示資料的TimelineEntry結構。 - 實作
TimelineProvider(靜態)或AppIntentTimelineProvider(可設定)。 - 使用 SwiftUI 建置小工具檢視,並根據
WidgetFamily調整佈局。 - 宣告符合
Widget協定的結構,包含設定與支援的系列。 - 在標註
@main的WidgetBundle中註冊所有小工具。
2. 整合相鄰介面
- 當 App 有即時活動時,在小工具 bundle 中註冊
ActivityConfiguration,但將ActivityAttributes、請求/更新/結束、APNscontent-state及動態島佈局深度保留在activitykit中。 - 在 WidgetKit 檢視或控制項中放置
Button、Toggle、ControlWidgetButton和ControlWidgetToggle,但將意圖建模、實體、查詢、Siri、捷徑和 Spotlight 保留在app-intents中。
3. 新增控制中心控制項
- 為按鈕重複使用
AppIntent/OpenIntent,或為切換開關使用SetValueIntent。 - 在小工具 bundle 中建立
ControlWidgetButton或ControlWidgetToggle。 - 使用
StaticControlConfiguration或AppIntentControlConfiguration。
4. 審查現有小工具程式碼
按照文件末尾的審查檢查清單進行。
Widget 協定與 WidgetBundle
Widget
每個小工具都符合 Widget 協定,並從其 body 回傳 WidgetConfiguration。
struct OrderStatusWidget: Widget {
let kind: String = "OrderStatusWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: OrderProvider()) { entry in
OrderWidgetView(entry: entry)
}
.configurationDisplayName("訂單狀態")
.description("追蹤您目前的訂單。")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
WidgetBundle
使用 WidgetBundle 從單一擴充中公開多個小工具。
@main
struct MyAppWidgets: WidgetBundle {
var body: some Widget {
OrderStatusWidget()
FavoritesWidget()
DeliveryActivityWidget() // ActivityConfiguration 交接
QuickActionControl() // 控制中心
}
}
設定類型
對不可設定的小工具使用 StaticConfiguration。對可設定的小工具(搭配 AppIntentTimelineProvider)建議使用 AppIntentConfiguration。
// 靜態
StaticConfiguration(kind: "MyWidget", provider: MyProvider()) { entry in
MyWidgetView(entry: entry)
}
// 可設定
AppIntentConfiguration(kind: "ConfigWidget", intent: SelectCategoryIntent.self,
provider: CategoryProvider()) { entry in
CategoryWidgetView(entry: entry)
}
共享修飾詞
| 修飾詞 | 用途 |
|---|---|
.configurationDisplayName(_:) |
小工具圖庫中顯示的名稱 |
.description(_:) |
小工具圖庫中顯示的描述 |
.supportedFamilies(_:) |
WidgetFamily 值的陣列 |
.supplementalActivityFamilies(_:) |
即時活動尺寸(.small、.medium) |
TimelineProvider
用於靜態(不可設定)小工具。使用 completion handler。需要三個方法:
struct WeatherProvider: TimelineProvider {
typealias Entry = WeatherEntry
func placeholder(in context: Context) -> WeatherEntry {
WeatherEntry(date: .now, temperature: 72, condition: "晴朗")
}
func getSnapshot(in context: Context, completion: @escaping (WeatherEntry) -> Void) {
let entry = context.isPreview
? placeholder(in: context)
: WeatherEntry(date: .now, temperature: currentTemp, condition: currentCondition)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) {
Task {
let weather = await WeatherService.shared.fetch()
let entry = WeatherEntry(date: .now, temperature: weather.temp, condition: weather.condition)
let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: .now)!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
}
AppIntentTimelineProvider
用於可設定小工具。原生支援 async/await。接收使用者意圖設定。
struct CategoryProvider: AppIntentTimelineProvider {
typealias Entry = CategoryEntry
typealias Intent = SelectCategoryIntent
func placeholder(in context: Context) -> CategoryEntry {
CategoryEntry(date: .now, categoryName: "範例", items: [])
}
func snapshot(for config: SelectCategoryIntent, in context: Context) async -> CategoryEntry {
let items = await DataStore.shared.items(for: config.category)
return CategoryEntry(date: .now, categoryName: config.category.name, items: items)
}
func timeline(for config: SelectCategoryIntent, in context: Context) async -> Timeline<CategoryEntry> {
let items = await DataStore.shared.items(for: config.category)
let entry = CategoryEntry(date: .now, categoryName: config.category.name, items: items)
return Timeline(entries: [entry], policy: .atEnd)
}
}
小工具系列
| 系列 | 平台 |
|---|---|
.systemSmall |
iOS、iPadOS、macOS、CarPlay (iOS 26+) |
.systemMedium |
iOS、iPadOS、macOS |
.systemLarge |
iOS、iPadOS、macOS |
.systemExtraLarge |
僅 iPadOS |
.accessoryCircular |
iOS、watchOS |
.accessoryRectangular |
iOS、watchOS |
.accessoryInline |
iOS、watchOS |
.accessoryCorner |
僅 watchOS |
使用 @Environment(\.widgetFamily) 根據系列調整佈局:
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall: CompactView(entry: entry)
case .systemMedium: DetailedView(entry: entry)
case .accessoryCircular: CircularView(entry: entry)
default: FullView(entry: entry)
}
}
互動式小工具 (iOS 17+)
使用 Button 和 Toggle,搭配小工具擴充或共享程式碼可用的意圖類型。WidgetKit 負責檢視放置;app-intents 負責意圖建模與行為。
struct InteractiveWidgetView: View {
let entry: FavoriteEntry
var body: some View {
Button(intent: ToggleFavoriteIntent(itemID: entry.itemID)) {
Image(systemName: entry.isFavorite ? "star.fill" : "star")
}
}
}
ActivityConfiguration 交接
WidgetKit 在小工具擴充中註冊即時活動介面。將此部分保留為註冊與渲染交接;使用 activitykit 處理 ActivityAttributes、生命週期、推播更新及完整的動態島模式。
struct DeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
DeliveryLiveActivityView(context: context)
} dynamicIsland: { context in
DeliveryDynamicIsland(context: context)
}
}
}
控制中心小工具 (iOS 18+)
WidgetKit 負責控制設定、放置、kind、顯示名稱、推播處理器及擴充註冊。控制動作與值意圖屬於 app-intents。
struct OpenCameraControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "OpenCamera") {
ControlWidgetButton(action: OpenCameraIntent()) {
Label("相機", systemImage: "camera.fill")
}
}
.displayName("開啟相機")
}
}
struct FlashlightControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "Flashlight", provider: FlashlightValueProvider()) { value in
ControlWidgetToggle(isOn: value, action: ToggleFlashlightIntent()) {
Label("手電筒", systemImage: value ? "flashlight.on.fill" : "flashlight.off.fill")
}
}
.displayName("手電筒")
}
}
鎖定畫面小工具
使用 accessory 系列與 AccessoryWidgetBackground。
struct StepsWidget: Widget {
let kind = "StepsWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: StepsProvider()) { entry in
ZStack {
AccessoryWidgetBackground()
VStack {
Image(systemName: "figure.walk")
Text("\(entry.stepCount)").font(.headline)
}
}
}
.supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryInline])
}
}
待機模式
小型系統小工具可出現在待機模式與 CarPlay 中。使用 @Environment(\.widgetLocation) 進行條件式渲染:
@Environment(\.widgetLocation) var location
// location == .standBy, .homeScreen, .lockScreen, .carPlay 等
小工具 URL 處理與深層連結
使用一個 .widgetURL(_:) 作為整個小工具的備用路由。僅在系列與佈局支援的情況下,使用 Link 作為明確的子目標,包括 .accessoryRectangular、.systemSmall 及更大的系統小工具。對於小型小工具,偏好一個明確的備用路由;除非視覺提示與點擊區域保持明確,否則避免使用多個 Link 目標。
切勿在層級中附加多個 widgetURL 修飾詞。
智慧堆疊關聯性
在時間軸條目上使用 TimelineEntryRelevance(score:duration:) 以實現 iPhone 與 iPad 智慧堆疊的即時關聯性。將分數保持在一致的正數範圍內;零或更低表示不相關。
對於可設定小工具,從 App 端程式碼捐贈對應於使用者動作或小工具參數的 App Intents,例如使用 intent.donate() 或 IntentDonationManager。將 AppEntity 與 EntityQuery 設計保留在 app-intents 中。
在 watchOS 上,上下文關聯性使用來自提供者 relevance() 回呼的 WidgetRelevance([WidgetRelevanceAttribute(...)])。該路徑不適用於 iPhone 或 iPad 智慧堆疊。
設計模式
- 偏好使用
Gauge而非手動繪製弧形。 使用.gaugeStyle(.accessoryCircular)用於鎖定畫面圓形小工具,使用.linearCapacity用於主畫面容量條。系統會處理樣式、無障礙功能及渲染模式適應。 - 使用
.containerBackground(_:for: .widget)(iOS 17+)作為小工具背景,取代 padding 與背景修飾詞。 - 使用
Canvas進行密集視覺化,例如迷你折線圖或迷你長條圖。由於整個小工具表面是一個點擊目標,因此缺乏逐元素無障礙功能是可接受的。 - 將時間軸重新整理與資料粒度匹配。 預算為動態且機會主義的;排程有用的未來條目,避免不必要的重新載入,並使用
Text(timerInterval:countsDown:)進行即時倒數。請參閱進階參考以獲取當前預算指引。
請參閱 references/widgetkit-advanced.md 以取得每個模式的程式碼範例與詳細指引。
iOS 26 新增功能
Liquid Glass 支援
使用 @Environment(\.widgetRenderingMode)、.widgetAccentable() 和 Image.widgetAccentedRenderingMode(_:) 讓小工具適應 Liquid Glass。在 .vibrant 模式下,系統會將內容映射到材質樣式中,因此避免僅依賴原始顏色。
推播重新整理處理器
小工具推播重新整理:
- 為小工具擴充目標新增 Push Notifications 功能。
- 將
WidgetPushHandler類型保留在小工具擴充目標或連結到其中的共享程式碼中,而不僅在主 App 目標中。 - 使用
.pushHandler(...)在小工具設定上註冊處理器。 - 不要使用 User Notifications 註冊來取得小工具推播權杖;WidgetKit 透過
pushTokenDidChange(_:widgets:)提供權杖。 - 使用
apns-push-type: widgets、主題後綴.push-type.widgets和aps.content-changed。 - 將推播視為有預算的、機會主義的重新整理訊號,而非狀態傳遞,也非唯一的新鮮度模型。時間軸、重新載入策略、共享儲存或重新擷取,以及 App 觸發的
WidgetCenter重新載入,仍然是備用路徑。
控制項推播重新整理:
- 使用
.pushHandler(...)在ControlWidgetConfiguration上註冊ControlPushHandler。 pushTokensDidChange(controls:)接收[ControlInfo];從每個控制項的pushInfo讀取權杖。- 使用
apns-push-type: controls、主題後綴.push-type.controls和aps.content-changed。
CarPlay 小工具
小型系統小工具可在 iOS 26+ 的 CarPlay 中出現。確保佈局一目了然;點擊與控制項取決於車輛觸控支援,以及開啟 App 的 CarPlay 整合。
常見錯誤
-
使用 IntentTimelineProvider 而非 AppIntentTimelineProvider。
IntentTimelineProvider是較舊的 SiriKit Intents 基礎提供者。對於新小工具,建議使用AppIntentTimelineProvider搭配 App Intents 框架。 -
超出重新整理預算。 小工具有每日重新整理限制。不要對每個微小資料變更都呼叫
WidgetCenter.shared.reloadTimelines(ofKind:)。批次更新並使用適當的TimelineReloadPolicy值。 -
忘記為共享資料設定 App Groups。 小工具擴充在獨立行程中執行。使用
UserDefaults(suiteName:)或共享的 App Group 容器來儲存小工具讀取的資料。 -
在 placeholder() 中執行網路呼叫。
placeholder(in:)必須同步回傳範例資料。使用getTimeline或timeline(for:in:)進行非同步工作。 -
將 WidgetKit 推播承載視為狀態。 小工具與控制項推播是重新整理訊號。將狀態持久化到共享儲存中,或在提供者中重新擷取。
-
透過 User Notifications 註冊小工具推播。 小工具推播權杖來自 WidgetKit 處理器,而非
UNUserNotificationCenter。 -
在小工具檢視中放置大量邏輯。 小工具檢視在大小受限的行程中渲染。在時間軸提供者中預先計算資料,並透過條目傳遞準備好顯示的值。
-
忽略 accessory 渲染模式。 鎖定畫面小工具以
.vibrant或.accented模式渲染,而非.fullColor。使用@Environment(\.widgetRenderingMode)測試,並避免僅依賴顏色。 -
未在裝置上測試。 待機模式、CarPlay 和 accessory 渲染與模擬器有顯著差異。務必在實體硬體上驗證。
審查檢查清單
- [ ] 小工具擴充目標具有與主 App 相符的 App Groups 權利
- [ ]
@main位於WidgetBundle上,而非個別小工具 - [ ]
placeholder(in:)同步回傳;getSnapshot/snapshot(for:in:)在isPreview時快速 - [ ] 時間軸重新載入策略符合更新頻率;僅在資料變更時使用
reloadTimelines(ofKind:) - [ ] 佈局根據
WidgetFamily調整;accessory 小工具已在.vibrant模式下測試 - [ ] 互動式小工具使用擴充可用的 App Intents,僅搭配
Button/Toggle - [ ] 使用一個
.widgetURL(_:)備用路由;Link子目標符合系列要求 - [ ] 小工具推播處理器位於小工具擴充/共享程式碼中,且不使用 User Notifications 權杖註冊
- [ ] 小工具/控制項推播補充時間軸與共享狀態/重新擷取備用方案
- [ ] 智慧堆疊關聯性使用時間軸關聯性,並在適當時使用 App 端意圖捐贈
- [ ] 即時活動生命週期與 App Intent 建模已移交給同級技能
- [ ] 控制項使用
StaticControlConfiguration/AppIntentControlConfiguration - [ ] 時間軸條目與 Intent 類型為 Sendable;已在裝置上測試
參考資料
- 進階指南:references/widgetkit-advanced.md
- Apple 文件:WidgetKit | 保持小工具更新 | 智慧堆疊可見性




