mcpField
W
widgetkit

widgetkit

github.com/dpearson2699開発1.2k4.0k 回インストール13 週間前に更新
リポジトリ

widgetkit について

widgetkitは、ホーム画面、ロック画面、コントロールセンターなどのWidgetKit機能を作るiOS開発者向けスキルです。設定、タイムライン、レイアウト、操作方法を扱います。

主な機能

  1. 01複数のWidgetKit画面の構築
  2. 02静的・設定可能なウィジェットの設定
  3. 03タイムラインプロバイダーの実装
  4. 04ウィジェットファミリーに応じたレイアウト
  5. 05操作機能とコントロールセンターコントロール
  6. 06ディープリンクと関連性の設定

活用シーン

  1. 01ホーム画面またはロック画面ウィジェットの開発
  2. 02コントロールセンターコントロールの追加
  3. 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 を参照してください。

目次

ワークフロー

1. 新しいウィジェットを作成する
  1. Xcode で Widget Extension ターゲットを追加します(File > New > Target > Widget Extension)。
  2. アプリとウィジェット拡張機能の間でデータを共有するために、App Groups を有効にします。
  3. date プロパティと表示データを持つ TimelineEntry 構造体を定義します。
  4. TimelineProvider(静的)または AppIntentTimelineProvider(構成可能)を実装します。
  5. SwiftUI を使ってウィジェットビューを作成し、WidgetFamily ごとにレイアウトを調整します。
  6. 構成とサポートするファミリーを指定して、Widget に準拠する構造体を宣言します。
  7. @main を付けた WidgetBundle に、すべてのウィジェットを登録します。
2. 隣接するサーフェスを統合する
  1. アプリに Live Activity がある場合は、ウィジェットバンドルに ActivityConfiguration を登録します。ただし、ActivityAttributes、リクエスト/更新/終了、APNs の content-state、Dynamic Island のレイアウトの詳細は activitykit に委ねてください。
  2. WidgetKit のビューまたはコントロールに Button、Toggle、ControlWidgetButton、ControlWidgetToggle を配置します。ただし、インテントのモデル化、エンティティ、クエリ、Siri、ショートカット、Spotlight は app-intents に委ねてください。
3. コントロールセンターコントロールを追加する
  1. ボタンには AppIntent/OpenIntent を、トグルには SetValueIntent を再利用します。
  2. ウィジェットバンドルに ControlWidgetButton または ControlWidgetToggle を作成します。
  3. 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)
    }
}

ウィジェットファミリー

ファミリープラットフォーム
.systemSmalliOS、iPadOS、macOS、CarPlay(iOS 26 以降)
.systemMediumiOS、iPadOS、macOS
.systemLargeiOS、iPadOS、macOS
.systemExtraLargeiPadOS のみ
.accessoryCirculariOS、watchOS
.accessoryRectangulariOS、watchOS
.accessoryInlineiOS、watchOS
.accessoryCornerwatchOS のみ

@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 との連携状況によって異なります。

よくある間違い

  1. AppIntentTimelineProvider ではなく IntentTimelineProvider を使う。 IntentTimelineProvider は、SiriKit Intents ベースの旧式のプロバイダーです。新しいウィジェットでは、App Intents フレームワークと AppIntentTimelineProvider を使ってください。

  2. 更新予算を超過する。 ウィジェットには1日あたりの更新上限があります。データが少し変わるたびに WidgetCenter.shared.reloadTimelines(ofKind:) を呼び出さないでください。更新をまとめ、適切な TimelineReloadPolicy の値を使ってください。

  3. 共有データ用の App Groups を忘れる。 ウィジェット拡張は別のプロセスで実行されます。ウィジェットが読み取るデータには、UserDefaults(suiteName:) または共有 App Group コンテナを使ってください。

  4. placeholder() 内でネットワーク呼び出しを行う。 placeholder(in:) は、サンプルデータを使って同期的に返す必要があります。非同期処理には getTimeline または timeline(for:in:) を使ってください。

  5. WidgetKit のプッシュペイロードを状態として扱う。 ウィジェットとコントロールのプッシュは、再読み込みシグナルです。状態は共有ストレージに保存するか、プロバイダー内で再取得してください。

  6. ユーザー通知経由でウィジェットのプッシュを登録する。 ウィジェットのプッシュトークンは UNUserNotificationCenter ではなく、WidgetKit ハンドラーから取得します。

  7. ウィジェットビューに重いロジックを配置する。 ウィジェットビューは、サイズが制限されたプロセス内で描画されます。タイムラインプロバイダーでデータを事前計算し、表示に適した値をエントリー経由で渡してください。

  8. アクセサリーの描画モードを無視する。 ロック画面ウィジェットは .fullColor ではなく、.vibrant または .accented モードで描画されます。@Environment(\.widgetRenderingMode) を使ってテストし、色だけに頼らないでください。

  9. 実機でテストしない。 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 であり、実機でテストされています

参考資料

コメント