widgetkit について
widgetkitは、ホーム画面、ロック画面、コントロールセンターなどのWidgetKit機能を作るiOS開発者向けスキルです。設定、タイムライン、レイアウト、操作方法を扱います。
主な機能
- 01複数のWidgetKit画面の構築
- 02静的・設定可能なウィジェットの設定
- 03タイムラインプロバイダーの実装
- 04ウィジェットファミリーに応じたレイアウト
- 05操作機能とコントロールセンターコントロール
- 06ディープリンクと関連性の設定
活用シーン
- 01ホーム画面またはロック画面ウィジェットの開発
- 02コントロールセンターコントロールの追加
- 03タイムライン更新やレイアウトの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill widgetkitインストール後、Claude Code で/widgetkitを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/widgetkit-advanced.md
詳細
WidgetKit
iOS 26 以降のホーム画面ウィジェット、ロック画面ウィジェット、コントロールセンターコントロール、スタンバイまたは CarPlay のウィジェットサーフェスを構築します。
隣接するフレームワークのガイダンスは、WidgetKit との統合に限定してください。ActivityKit と App Intents は、WidgetKit サーフェスに直接関連する場合にのみ含めます。ライフサイクル全体、APNs のコンテンツ状態、Siri/ショートカット/Spotlight、またはエンティティのモデル化に関する作業は、関連する activitykit または app-intents のスキルに引き継いでください。
タイムライン戦略、プッシュベースの更新、Xcode の設定、高度なパターンについては、references/widgetkit-advanced.md を参照してください。
目次
- ワークフロー
- Widget プロトコルと WidgetBundle
- 構成タイプ
- TimelineProvider
- AppIntentTimelineProvider
- ウィジェットファミリー
- インタラクティブなウィジェット(iOS 17 以降)
- ActivityConfiguration への引き継ぎ
- コントロールセンターウィジェット(iOS 18 以降)
- ロック画面ウィジェット
- スタンバイモード
- ウィジェットの URL 処理とディープリンク
- Smart Stack の関連性
- デザインパターン
- iOS 26 の追加機能
- よくある間違い
- レビュー用チェックリスト
- 参考資料
ワークフロー
1. 新しいウィジェットを作成する
- Xcode で Widget Extension ターゲットを追加します(File > New > Target > Widget Extension)。
- アプリとウィジェット拡張機能の間でデータを共有するために、App Groups を有効にします。
dateプロパティと表示データを持つTimelineEntry構造体を定義します。TimelineProvider(静的)またはAppIntentTimelineProvider(構成可能)を実装します。- SwiftUI を使ってウィジェットビューを作成し、
WidgetFamilyごとにレイアウトを調整します。 - 構成とサポートするファミリーを指定して、
Widgetに準拠する構造体を宣言します。 @mainを付けたWidgetBundleに、すべてのウィジェットを登録します。
2. 隣接するサーフェスを統合する
- アプリに Live Activity がある場合は、ウィジェットバンドルに
ActivityConfigurationを登録します。ただし、ActivityAttributes、リクエスト/更新/終了、APNs のcontent-state、Dynamic Island のレイアウトの詳細はactivitykitに委ねてください。 - WidgetKit のビューまたはコントロールに
Button、Toggle、ControlWidgetButton、ControlWidgetToggleを配置します。ただし、インテントのモデル化、エンティティ、クエリ、Siri、ショートカット、Spotlight はapp-intentsに委ねてください。
3. コントロールセンターコントロールを追加する
- ボタンには
AppIntent/OpenIntentを、トグルにはSetValueIntentを再利用します。 - ウィジェットバンドルに
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("Order Status")
.description("Track your current order.")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
WidgetBundle
WidgetBundle を使うと、1 つの拡張機能から複数のウィジェットを公開できます。
@main
struct MyAppWidgets: WidgetBundle {
var body: some Widget {
OrderStatusWidget()
FavoritesWidget()
DeliveryActivityWidget() // ActivityConfiguration handoff
QuickActionControl() // Control Center
}
}
構成タイプ
構成できないウィジェットには StaticConfiguration を使用します。AppIntentTimelineProvider と組み合わせる構成可能なウィジェットには、(推奨される)AppIntentConfiguration を使用します。
// Static
StaticConfiguration(kind: "MyWidget", provider: MyProvider()) { entry in
MyWidgetView(entry: entry)
}
// Configurable
AppIntentConfiguration(kind: "ConfigWidget", intent: SelectCategoryIntent.self,
provider: CategoryProvider()) { entry in
CategoryWidgetView(entry: entry)
}
共通のモディファイア
| モディファイア | 目的 |
|---|---|
.configurationDisplayName(_:) | ウィジェットギャラリーに表示される名前 |
.description(_:) | ウィジェットギャラリーに表示される説明 |
.supportedFamilies(_:) | WidgetFamily 値の配列 |
.supplementalActivityFamilies(_:) | Live Activity のサイズ(.small、.medium) |
TimelineProvider
静的(構成不可)ウィジェット向けです。完了ハンドラーを使用します。必須のメソッドは 3 つです。
struct WeatherProvider: TimelineProvider {
typealias Entry = WeatherEntry
func placeholder(in context: Context) -> WeatherEntry {
WeatherEntry(date: .now, temperature: 72, condition: "Sunny")
}
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: "Sample", 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 はウィジェット拡張機能に Live Activity の表示面を登録します。このセクションでは、登録と描画の引き継ぎに限定してください。ActivityAttributes、ライフサイクル、プッシュ更新、Dynamic Island の完全なパターンについては activitykit を使います。
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 が管理します。コントロールのアクションと値のインテントは app-intents に属します。
struct OpenCameraControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "OpenCamera") {
ControlWidgetButton(action: OpenCameraIntent()) {
Label("Camera", systemImage: "camera.fill")
}
}
.displayName("Open Camera")
}
}
struct FlashlightControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "Flashlight", provider: FlashlightValueProvider()) { value in
ControlWidgetToggle(isOn: value, action: ToggleFlashlightIntent()) {
Label("Flashlight", systemImage: value ? "flashlight.on.fill" : "flashlight.off.fill")
}
}
.displayName("Flashlight")
}
}
ロック画面ウィジェット
アクセサリーファミリーと 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, etc.
ウィジェットの URL 処理とディープリンク
ウィジェット全体のフォールバック経路には、.widgetURL(_:) を 1 つ使います。.accessoryRectangular、.systemSmall、それより大きなシステムウィジェットなど、ファミリーとレイアウトが対応している場合に限り、意図的なサブターゲットに Link を使います。小型のウィジェットでは、明確なフォールバックを 1 つ用意してください。視覚的な手がかりとタップ領域が明確に区別できない限り、複数の Link ターゲットは避けます。
階層内に複数の widgetURL 修飾子を設定しないでください。
Smart Stack の関連性
タイムラインエントリーに TimelineEntryRelevance(score:duration:) を使い、タイミングに応じて iPhone と iPad の Smart Stack での関連性を設定します。スコアは一貫した正のスケールにしてください。0 以下は関連性がないことを意味します。
設定可能なウィジェットでは、intent.donate() や IntentDonationManager などを使い、ユーザー操作やウィジェットパラメーターに対応する App Intents をアプリ側のコードから寄付します。AppEntity と EntityQuery の設計については app-intents で扱います。
watchOS では、プロバイダーの relevance() コールバックから WidgetRelevance([WidgetRelevanceAttribute(...)]) を使って、コンテキストに応じた関連性を設定します。この方法は iPhone や iPad の Smart Stack では使いません。
設計パターン
- 手動の弧より
Gaugeを優先します。 ロック画面の円形ウィジェットには.gaugeStyle(.accessoryCircular)を、ホーム画面の容量バーには.linearCapacityを使います。スタイル、アクセシビリティ、描画モードへの適応はシステムが処理します。 - ウィジェットの背景には、パディングや背景の修飾子ではなく
.containerBackground(_:for: .widget)(iOS 17 以降)を使います。 - スパークラインやミニ棒グラフなど、密度の高い視覚化には
Canvasを使います。 ウィジェット全体が単一のタップターゲットであるため、要素ごとのアクセシビリティがなくても問題ありません。 - タイムラインの更新頻度をデータの粒度に合わせます。 予算は動的で、状況に応じて変化します。役立つ将来のエントリーをスケジュールし、不要な再読み込みを避け、リアルタイムのカウントダウンには
Text(timerInterval:countsDown:)を使います。現在の予算に関するガイダンスは、詳細リファレンスを確認してください。
各パターンのコード例と詳細なガイダンスについては、references/widgetkit-advanced.md を参照してください。
iOS 26 の追加機能
Liquid Glass のサポート
@Environment(\.widgetRenderingMode)、.widgetAccentable()、Image.widgetAccentedRenderingMode(_:) を使って、ウィジェットを Liquid Glass に適応させます。.vibrant では、システムがコンテンツをマテリアルのスタイルに変換するため、元の色だけに頼らないでください。
プッシュによる再読み込みハンドラー
ウィジェットのプッシュ再読み込み:
- ウィジェット拡張ターゲットにプッシュ通知機能を追加します。
WidgetPushHandler型は、ウィジェット拡張ターゲット内、またはウィジェット拡張にリンクされる共有コード内に配置します。メインアプリのターゲットだけに配置しないでください。- ウィジェットの構成で
.pushHandler(...)を使ってハンドラーを登録します。 - ウィジェットのプッシュトークンを取得するために、ユーザー通知への登録を使わないでください。WidgetKit は
pushTokenDidChange(_:widgets:)を通じてトークンを提供します。 apns-push-type: widgets、トピックのサフィックス.push-type.widgets、およびaps.content-changedを使います。- プッシュは、予算に応じて利用されるベストエフォートの再読み込みシグナルとして扱い、状態の配信や唯一の最新性維持手段として扱わないでください。タイムライン、再読み込みポリシー、共有ストレージまたは再取得、アプリからの
WidgetCenter再読み込みは、引き続きフォールバック手段として利用できます。
コントロールのプッシュ再読み込み:
ControlWidgetConfigurationで.pushHandler(...)を使ってControlPushHandlerを登録します。pushTokensDidChange(controls:)は[ControlInfo]を受け取ります。各コントロールのpushInfoからトークンを読み取ります。apns-push-type: controls、トピックのサフィックス.push-type.controls、およびaps.content-changedを使います。
CarPlay ウィジェット
小型のシステムウィジェットは、iOS 26 以降で CarPlay に表示できます。一目で内容がわかるレイアウトにしてください。タップやコントロールは、車両のタッチ操作への対応状況と、アプリを開く場合は CarPlay との連携状況によって異なります。
よくある間違い
-
AppIntentTimelineProvider ではなく IntentTimelineProvider を使う。
IntentTimelineProviderは、SiriKit Intents ベースの旧式のプロバイダーです。新しいウィジェットでは、App Intents フレームワークとAppIntentTimelineProviderを使ってください。 -
更新予算を超過する。 ウィジェットには1日あたりの更新上限があります。データが少し変わるたびに
WidgetCenter.shared.reloadTimelines(ofKind:)を呼び出さないでください。更新をまとめ、適切なTimelineReloadPolicyの値を使ってください。 -
共有データ用の App Groups を忘れる。 ウィジェット拡張は別のプロセスで実行されます。ウィジェットが読み取るデータには、
UserDefaults(suiteName:)または共有 App Group コンテナを使ってください。 -
placeholder() 内でネットワーク呼び出しを行う。
placeholder(in:)は、サンプルデータを使って同期的に返す必要があります。非同期処理にはgetTimelineまたはtimeline(for:in:)を使ってください。 -
WidgetKit のプッシュペイロードを状態として扱う。 ウィジェットとコントロールのプッシュは、再読み込みシグナルです。状態は共有ストレージに保存するか、プロバイダー内で再取得してください。
-
ユーザー通知経由でウィジェットのプッシュを登録する。 ウィジェットのプッシュトークンは
UNUserNotificationCenterではなく、WidgetKit ハンドラーから取得します。 -
ウィジェットビューに重いロジックを配置する。 ウィジェットビューは、サイズが制限されたプロセス内で描画されます。タイムラインプロバイダーでデータを事前計算し、表示に適した値をエントリー経由で渡してください。
-
アクセサリーの描画モードを無視する。 ロック画面ウィジェットは
.fullColorではなく、.vibrantまたは.accentedモードで描画されます。@Environment(\.widgetRenderingMode)を使ってテストし、色だけに頼らないでください。 -
実機でテストしない。 StandBy、CarPlay、アクセサリーの描画は、シミュレーターと大きく異なります。必ず実機で確認してください。
レビューチェックリスト
- ウィジェット拡張ターゲットに、メインアプリと一致する App Groups エンタイトルメントが設定されています
-
@mainは個々のウィジェットではなく、WidgetBundleに付けられています -
placeholder(in:)は同期的に値を返し、isPreviewの場合、getSnapshot/snapshot(for:in:)は高速に処理されます - タイムラインの再読み込みポリシーは更新頻度に合っており、
reloadTimelines(ofKind:)はデータ変更時にのみ呼び出されます - レイアウトは
WidgetFamilyごとに適応し、アクセサリーウィジェットは.vibrantモードでテストされています - インタラクティブウィジェットは拡張機能から利用可能な App Intents を使用し、
Button/Toggleのみを使用します - フォールバックとして単一の
.widgetURL(_:)を使用し、Linkのサブターゲットはファミリーに適したものです - ウィジェットのプッシュハンドラーはウィジェット拡張機能または共有コードに配置され、User Notifications のトークン登録を使用しません
- ウィジェット/コントロールのプッシュは、タイムラインと共有状態/再取得のフォールバックを補完します
- Smart Stack の関連性にはタイムラインの関連性を使用し、有用な場合はアプリ側でインテントの寄付を行います
- Live Activity のライフサイクルと App Intent のモデリングは、関連するスキルに引き継がれています
- コントロールには
StaticControlConfiguration/AppIntentControlConfigurationを使用します - タイムラインエントリーと Intent 型は Sendable であり、実機でテストされています
参考資料
- 応用ガイド:references/widgetkit-advanced.md
- Apple のドキュメント:WidgetKit | ウィジェットを最新の状態に保つ | Smart Stack での表示