ドキュメント

Widget Extensionの作成

Extensionを追加および構成して、ホーム画面、「今日の表示」または通知センターにAppのコンテンツを表示します。

最新の英語ドキュメント

Creating a Widget Extension

概要

ウィジェットには、関連性のあるコンテンツが一目でわかる形で表示されるので、ユーザーはAppにすばやくアクセスして詳細情報を確認できます。Appはさまざまなウィジェットを提供できるため、ユーザーは自分にとって最も重要な情報に集中できます。同じウィジェットのコピーを複数追加し、固有のニーズやレイアウトに合わせてそれぞれのウィジェットをカスタマイズできます。カスタムIntentをウィジェットに含めれば、ユーザーはそれぞれのウィジェットを個別にカスタマイズすることもできます。ウィジェットは複数のサイズに対応しているため、お使いのAppのコンテンツに最も適したサイズを選択できます。空きスペースが限られているため、ウィジェットには必ず、ユーザーにとって最も価値のある情報が表示されるようにしてください。

ウィジェットをAppに追加するには、最小限の設定が必要で、ユーザーインターフェイスの構成とスタイルについていくつかの決定を行う必要があります。ウィジェットではSwiftUIビューを使ってコンテンツを表示します。詳しくは、SwiftUIを参照してください。

Appへのウィジェットターゲットの追加

Widget Extensionテンプレートはウィジェットを作成するための出発点です。単一のWidget Extensionに複数のウィジェットを含めることができます。たとえば、スポーツAppの1つのウィジェットにチーム情報を表示し、もう1つのウィジェットにゲームのスケジュールを表示する場合、両方のウィジェットを単一のWidget Extensionに含めることができます。

  1. XcodeでAppプロジェクトを開き、「File(ファイル)」>「New(新規)」>「Target(ターゲット)」の順に選択します。

  2. 「Application Extension(アプリケーションExtension)」グループから、「Widget Extension」を選択して「Next(次へ)」をクリックします。

  3. Extensionの名前を入力します。

  4. ユーザーが構成可能なプロパティをウィジェットが提供する場合は、「Include Configuration Intent(構成Intentを含める)」チェックボックスをオンにします。

  5. 「Finish(完了する)」をクリックします。

Widget Extensionが選択されたXcodeの新しいターゲットシートを示すスクリーンショット

Appに複数のExtensionが含まれている場合でも、通常、すべてのウィジェットを単一のWidget Extensionに含めます。たとえば、位置情報を利用するウィジェットと、位置情報を利用しないウィジェットがある場合、位置情報を利用するウィジェットを別個のExtensionに保持します。これにより、位置情報を利用するExtensionのウィジェットに対してのみ、システムは位置情報を利用する許可をユーザーに求めます。

構成の詳細の追加

Widget Extensionテンプレートは、Widgetプロトコルに適合する初期ウィジェット実装を提供します。このウィジェットのbodyプロパティは、ユーザーが構成可能なプロパティをウィジェットが含むかどうかを決定します。次の2種類の構成が使用できます。

  • StaticConfiguration:ユーザーが構成可能なプロパティをウィジェットが含まない場合です。例として、一般的な市場情報を表示する株式市場ウィジェットや、話題のヘッドラインを表示するニュースウィジェットがあります。

  • IntentConfiguration:ユーザーが構成可能なプロパティをウィジェットが含む場合です。SiriKitのカスタムIntentを使って、プロパティを定義します。例として、都市の郵便番号が必要な天気ウィジェットや、追跡番号が必要な荷物追跡ウィジェットがあります。

「Include Configuration Intent(構成Intentを含める)」チェックボックスは、Xcodeでどちらの構成を使うかを決定します。このチェックボックスをオンにすると、XcodeではIntent構成が使用され、オフにすると、Static構成が使用されます。構成を初期化するには、次の情報を指定します。

  • kind:ウィジェットを識別する文字列です。これは、ユーザーが選択する識別子であり、ウィジェットが表す内容をわかりやすく説明するものです。

  • provider:TimelineProviderに適合して、ウィジェットをレンダリングするタイミングをWidgetKitに知らせるタイムラインを生成するオブジェクトです。タイムラインには、定義したカスタムのTimelineEntryタイプが含まれます。タイムラインエントリは、WdgetKitがウィジェットのコンテンツを更新する日時を識別します。ウィジェットのビューをカスタムタイプでレンダリングするのに必要なプロパティを含めます。

  • コンテンツクロージャ: SwiftUIビューを含むクロージャです。WidgetKitは、このクロージャを呼び出してウィジェットのコンテンツをレンダリングし、プロバイダからのTimelineEntryパラメータを渡します。

  • カスタムIntent:ユーザーが構成可能なプロパティを定義するカスタムIntentです。カスタマイズの追加について詳しくは、「構成可能なウィジェットの作成」を参照してください。

修飾子を使って、表示名、説明、ウィジェットが対応するファミリーなど、追加の構成の詳細を指定します。次のコードは、ゲームの構成ができない一般的なステータスを提供するウィジェットを示しています。


@main
struct GameStatusWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.mygame.game-status",
            provider: GameStatusProvider(),
        ) { entry in
            GameStatusView(entry.gameStatus)
        }
        .configurationDisplayName("Game Status")
        .description("Shows an overview of your game status")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

このウィジェットのプロバイダがウィジェットのタイムラインを生成し、game-statusの詳細を各エントリに含めます。各タイムラインエントリの日時に達すると、WidgetKitはcontentクロージャを呼び出してウィジェットのコンテンツを表示します。最後に、修飾子が、ウィジェットギャラリーに表示される名前と説明を指定し、ユーザーがウィジェットの小/中/大のバージョンを選択できるようにします。

このウィジェットでの@main属性の使い方に注目してください。この属性は、GameStatusWidgetがWidget Extensionのエントリポイントであることを示し、Extensionに1つのウィジェットが含まれていることを意味しています。複数のウィジェットをサポートするには、「App Extensionでの複数のウィジェットの宣言」を参照してください。

タイムラインエントリの提供

タイムラインプロバイダはタイムラインエントリから構成されるタイムラインを生成します。それぞれのエントリはウィジェットのコンテンツを更新する日時を指定します。次に示すように、ゲームステータスのウィジェットでは、ゲームのステータスを表す文字列が含まれるようにタイムラインエントリを定義できます。


struct GameStatusEntry: TimelineEntry {
    var date: Date
    var gameStatus: String
}

ウィジェットをウィジェットギャラリーに表示するために、WidgetKitはプロバイダにプレビューのスナップショットを求めます。このプレビューリクエストを識別するには、getSnapshot(in:completion:)メソッドに渡されるcontextパラメータのisPreviewプロパティをチェックします。isPreviewがtrueの場合、WidgetKitはウィジェットをウィジェットギャラリーに表示します。これに対応して、ユーザーはプレビュースナップショットをすぐに作成する必要があります。ウィジェットに必要なアセットや情報を生成またはサーバから取得するのに時間がかかる場合は、代わりにサンプルデータを使ってください。

次のコードでは、ゲームステータスのウィジェットのプロバイダが、サーバからのステータスの取得が完了していない場合に空のステータスを表示することで、スナップショットメソッドを実装しています。


struct GameStatusProvider: TimelineProvider {
    var hasFetchedGameStatus: Bool
    var gameStatusFromServer: String


    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) {
        let date = Date()
        let entry: GameStatusEntry


        if context.isPreview && !hasFetchedGameStatus {
            entry = GameStatusEntry(date: date, gameStatus: "—")
        } else {
            entry = GameStatusEntry(date: date, gameStatus: gameStatusFromServer)
        }
        completion(entry)
    }

最初のスナップショットのリクエスト後に、WidgetKitはgetTimeline(in:completion:)を呼び出して、プロバイダに標準のタイムラインをリクエストします。このタイムラインは、複数のタイムラインエントリと、次のタイムラインをリクエストするタイミングをWidgetKitに通知する再読み込みポリシーで構成されています。

次の例は、game-statusウィジェットのプロバイダが、サーバからの現在のゲームステータスを含む1つのエントリと、15分後に新しいタイムラインをリクエストする再読み込みポリシーで構成されるタイムラインを生成する方法を示しています。


struct GameStatusProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> Void) {
        // Create a timeline entry for "now."
        let date = Date()
        let entry = GameStatusEntry(
            date: date,
            gameStatus: gameStatusFromServer
        )


        // Create a date that's 15 minutes in the future.
        let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!


        // Create the timeline with the entry and a reload policy with the date
        // for the next update.
        let timeline = Timeline(
            entries:[entry],
            policy: .after(nextUpdateDate)
        )


        // Call the completion to pass the timeline to WidgetKit.
        completion(timeline)
    }
}

この例では、ウィジェットにサーバから現在のステータスが提供されなかった場合、ウィジェットは、completionへの参照を格納し、サーバに対する非同期リクエストを実行してゲームステータスを取得し、リクエストの完了時にcompletionを呼び出すことができます。

ウィジェットでのネットワークリクエストの処理など、タイムラインの生成について詳しくは、「ウィジェットを最新の状態に維持」を参照してください。

プレースホルダーウィジェットの表示

WidgetKitは、初めてウィジェットを表示するときに、ウィジェットのビューをプレースホルダーとしてレンダリングします。プレースホルダービューには、ウィジェットの一般的なプレゼンテーションが表示されるので、ユーザーはウィジェットが示す内容の概要を把握できます。WidgetKitはplaceholder(in:)を呼び出して、ウィジェットのプレースホルダー構成を表すエントリをリクエストします。たとえば、ゲームステータスのウィジェットでは、このメソッドを次のようにして実装します。


struct GameStatusProvider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        GameStatusEntry(date: Date(), gameStatus: "—")
    }
}

ウィジェットのビューをプレースホルダーとしてレンダリングするために、WidgetKitは、reasonplaceholderを指定したredacted(reason:)ビュー修飾子を使います。ウィジェットのビュー階層にあるビューが自動的にプレースホルダーとしてレンダリングされないようにするには、unredacted()ビュー修飾子を使います。

Widget Extensionで「Data Protection(データ保護)」の機能を有効化にすると、データ保護エンタイトルメントに次の値が指定され、関連する条件が満たされた場合に、WidgetKitはウィジェットをプレースホルダーとしてレンダリングします。

  • NSFileProtectionCompleteまたはNSFileProtectionCompleteUnlessOpen、およびデバイスがロックされていること。

  • NSFileProtectionCompleteUntilFirstUserAuthentication、およびユーザーがまだ認証されていないこと。

「Data Protection(データ保護)」の構成について詳しくは、Data Protection Entitlementを参照してください。

ウィジェットのコンテンツの表示

ウィジェットでは、SwiftUIビューを使って、一般的に他のSwiftUIビューを作成することでそのコンテンツを定義します。「構成の詳細の追加」セクションで示したとおり、ウィジェットの構成には、WidgetKitがウィジェットのコンテンツをレンダリングするために呼び出すクロージャが含まれます。

ユーザーがウィジェットギャラリーからウィジェットを追加するときは、ウィジェットが対応しているものの中から特定のファミリー(小、中、大)を選択します。ウィジェットのコンテンツクロージャは、ウィジェットが対応しているそれぞれのファミリーのレンダリングをサポートしている必要があります。WidgetKitはSwiftUI環境で、対応するファミリーと色スキーム(濃淡の配色)などの追加のプロパティを設定します。

上記のgame-statusウィジェットの構成では、コンテンツクロージャでGameStatusViewを使ってステータスを表示します。このウィジェットは3つすべてのウィジェットファミリーに対応しているため、次に示すとおり、widgetFamilyを使って、表示する特定のSwiftUIビューを決定します。


struct GameStatusView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var gameStatus: GameStatus


    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall: GameTurnSummary(gameStatus)
        case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
        case .systemLarge: GameStatusWithStatistics(gameStatus)
        default: GameDetailsNotAvailable()
        }
    }
}

「小」ファミリーの場合、ウィジェットでは、ゲームの順番をまとめて表示するビューを使用します。「中」の場合、前回の順番の結果を示すステータスが表示されます。「大」の場合、使用可能なスペースが増えるため、各プレーヤーの実行統計情報が表示されます。ファミリーが不明なタイプの場合は、ゲームステータスが利用不可であることを示すデフォルトビューが表示されます。

構成可能なウィジェットでは、プロバイダがIntentTimelineProviderに適合します。このプロバイダはTimelineProviderと同じ機能を実行しますが、ウィジェットでユーザーがカスタマイズする値を組み込みます。このカスタマイズは、getSnapshot(for:in:completion:)getTimeline(for:in:completion:)の両方に渡されるconfigurationパラメータのタイムラインプロバイダのIntentで利用できます。通常、ユーザーが構成した値をカスタムのタイムラインエントリタイプのプロパティとして含めるため、詳細をウィジェットのビューで利用できます。

WidgetKitがサポートしているビューのリストについては、「SwiftUIビュー」を参照してください。

ウィジェットへのダイナミックコンテンツの追加

ウィジェットの表示はビューのスナップショットに基づいていますが、ウィジェットが表示されている間に継続して更新される各種のSwiftUIビューを使うことができます。ダイナミックコンテンツの提供について詳しくは、「ウィジェットを最新の状態に維持」を参照してください。

ユーザーの操作に応答

ユーザーがウィジェットを操作するとき、システムはAppを起動してリクエストを処理します。システムがAppをアクティブ化すると、ウィジェットのコンテンツに対応する詳細に移動します。ウィジェットでは、URLを指定して、表示するコンテンツをAppに通知することができます。ウィジェットでカスタムURLを構成するには、次の手順を実行します。

  • すべてのウィジェットで、ウィジェットのビュー階層にあるビューにwidgetURL(_:)ビュー修飾子を追加します。ウィジェットのビュー階層に複数のwidgetURL修飾子が含まれている場合、動作は定義されていません。

  • WidgetFamily.systemMediumまたはWidgetFamily.systemLargeを使うウィジェットでは、1つ以上のLinkコントロールをウィジェットのビュー階層に追加します。widgetURLコントロールとLinkコントロールの両方を使うことができます。操作がLinkコントロールを対象とする場合、システムはそのコントロールのURLを使います。ウィジェットの他の場所で操作が行われた場合、システムはwidgetURLビュー修飾子に指定されたURLを使用します。

たとえば、ゲームの1つのキャラクターの詳細を表示するウィジェットで、widgetURLを使って、そのキャラクターの詳細をAppで開くことができます。


@ViewBuilder
var body: some View {
    ZStack {
        AvatarView(entry.character)
            .widgetURL(entry.character.url)
            .foregroundColor(.white)
    }
    .background(Color.gameBackground)
}

ウィジェットにキャラクターのリストが表示される場合、リスト内のそれぞれのアイテムをLinkコントロールに指定できます。各Linkコントロールは、表示する特定のキャラクターのURLを指定します。

ウィジェットが操作を受け取ると、システムは含まれるAppをアクティブ化し、Appで使われるライフサイクルに応じて、URLをonOpenURL(perform:)application(_:open:options:)、またはapplication(_:open:)に渡します。

ウィジェットでwidgetURLコントロールとLinkコントロールをどちらも使わない場合、システムは含まれるAppをアクティブ化し、NSUserActivityonContinueUserActivity(_:perform:)application(_:continue:restorationHandler:)、またはapplication(_:continue:restorationHandler:)に渡します。ユーザーアクティビティのuserInfoディクショナリには、ユーザーが操作したウィジェットに関する詳細が含まれています。これらの値にSwiftコードからアクセスするには、WidgetCenter.UserInfoKeyのキーを使用します。Objective-CからuserInfoの値にアクセスするには、代わりにWGWidgetUserInfoKeyKindキーとWGWidgetUserInfoKeyFamilyキーを使用します。

IntentConfigurationを使用するウィジェットの場合、ユーザーアクティビティのinteractionプロパティにウィジェットのINIntentが含まれます。

App Extensionでの複数のウィジェットの宣言

上記のGameStatusWidgetの例では、@main属性を使用してWidget Extensionの単一のエントリポイントを指定しています。複数のウィジェットをサポートするには、WidgetBundleに適合して、複数のウィジェットをbodyプロパティにまとめる構造体を宣言します。このwidget-bundle構造体で@main属性を追加して、Extensionが複数のウィジェットをサポートすることをWidgetKitに知らせます。

たとえば、ゲームAppに、キャラクターの体力を表示する2つ目のウィジェットと、リーダーボードを表示する3つ目のウィジェットが存在する場合、次に示すように、それらのウィジェットを1つにまとめます。


@main
struct GameWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        GameStatusWidget()
        CharacterDetailWidget()
        LeaderboardWidget()
    }
}

関連項目

ウィジェットの作成