ドキュメント

ウィジェットを最新の状態に維持

ダイナミックビューを使って関連する情報が適切なタイミングで表示されるようにウィジェットのタイムラインを計画し、状況が変化したときにタイムラインを更新します。

最新の英語ドキュメント

Keeping a Widget Up To Date

概要

ウィジェットではSwiftUIビューを使って、そのコンテンツを表示します。WidgetKitは個別のプロセスでユーザーに代わってビューをレンダリングします。結果として、ウィジェットが画面上にある場合でも、Widget Extensionは常時アクティブであるわけではありません。ウィジェットが常にアクティブとは限らないにもかかわらず、ユーザーはいくつかの方法でウィジェットのコンテンツを最新の状態に維持できます。

バジェットに収まる再読み込みの計画

ウィジェットを再読み込みすると、追加のネットワーク接続や処理が必要になるため、システムのリソースが消費され、バッテリーが消耗します。このようなパフォーマンスへの影響を軽減し、バッテリーの駆動時間を一日中維持するには、リクエストする更新の頻度と回数を必要な分だけに制限します。

システム負荷を管理するために、WidgetKitはバジェットを使って、ウィジェットの再読み込みを1日を通じて分散させます。バジェットの割り当ては動的で、次のような多くの要因が考慮されます。

  • ウィジェットがユーザーに表示される頻度と回数。

  • ウィジェットの前回の再読み込み日時。

  • ウィジェットを収容したアプリがアクティブかどうか。

WidgetKitは、ユーザーが各自のデバイスに追加するアクティブなウィジェットごとに異なるバジェットを割り当てます。たとえば、ユーザーが、構成可能なスポーツウィジェットの2つのインスタンスを追加して、2つの異なるチームの情報を表示する場合、ウィジェットごとにバジェットが割り当てられます。

ウィジェットのバジェットは24時間単位で適用されます。WidgetKitはユーザーの毎日の使用パターンに合わせて24時間のウィンドウを調整します。つまり、1日単位のバジェットは必ずしも午前0時ちょうどにリセットされるとは限りません。ユーザーが頻繁に確認するウィジェットの場合、通常、1日単位のバジェットに40〜70回の更新が含まれます。この割合は、概算で15〜60分おきにウィジェットが再読み込みされることを意味しますが、一般的に、関連する多くの要因によってこの間隔は変化します。

以下のような場合、WidgetKitは再読み込みをウィジェットのバジェットにカウントしません。

  • ウィジェットを収容したアプリがフォアグラウンドにある。

  • ウィジェットを収容したアプリでオーディオまたはナビゲーションセッションがアクティブである。

  • システムロケールの変更。

  • Dynamic Typeまたは「アクセシビリティ」設定の変更。

システムの外観の変更やシステムロケールの変更などが発生した場合、タイムラインの再読み込みをアプリにリクエストしないでください。システムが自動的にウィジェットを更新します。

ウィジェットのタイムラインプロバイダが再読み込みスケジュールを主導しますが、WidgetKitが、コンテンツを最新の状態に保つためにウィジェットを再読み込みすることがあります。一般的なシナリオには次のようなものがあります。

  • ユーザーがほとんど閲覧しないホーム画面ページにウィジェットがある場合、WidgetKitはそのウィジェットの再読み込みの頻度を減らす場合があります。後で、ユーザーがそのページを表示した場合、WidgetKitはウィジェットが表示された時点でウィジェットを再読み込みすることがあります。

  • 位置情報サービスを利用するウィジェットの場合、WidgetKitは位置の大幅な変更が発生した後にウィジェットを再読み込みします。位置情報サービスを利用するウィジェットの再読み込みについて詳しくは、「ウィジェットでの位置情報へのアクセス」を参照してください。

ウィジェットで再読み込みが必要な時点を予測できる場合、可能な限り多くの将来の日時に対応するタイムラインを生成することを推奨します。表示するコンテンツに応じてタイムラインのエントリの間隔をできるだけ大きく保つようにしてください。WidgetKitはウィジェットを再読み込みするまでの最小時間を指定します。タイムラインプロバイダは、約5分以上の間隔を空けてタイムラインエントリを作成する必要があります。WidgetKitが複数のウィジェット間で再読み込み処理を結合することにより、ウィジェットが再読み込みされる正確な時間に影響を及ぼすことがあります。

予測可能なイベントのタイムラインの生成

多くのウィジェットには、そのコンテンツを更新することが意味を持つ予測可能な時点が存在します。たとえば、気象情報を表示するウィジェットで1日を通して1時間ごとに温度を更新する場合や、株式市場ウィジェットで、市場がオープンしている時間帯はそのコンテンツを頻繁に更新するが、週末はまったく更新しない場合があります。これらの時点を事前に計画することで、WidgetKitは、該当する時間になるとウィジェットを自動的に更新します。

ウィジェットを定義する際に、カスタムのTimelineProviderを実装します。WidgetKitはプロバイダからタイムラインを取得し、そのタイムラインを使って、ウィジェットを更新するタイミングを追跡します。タイムラインはTimelineEntryオブジェクトの配列です。タイムラインの各エントリには、日時と時刻、およびウィジェットでビューの表示に必要な追加情報が含まれます。タイムラインエントリに加えて、タイムラインは、新しいタイムラインをリクエストするタイミングをWidgetKitに知らせる更新ポリシーを指定します。

以下では、キャラクターの体力レベルを表示するゲームウィジェットの例を示します。体力レベルが100パーセント未満のとき、キャラクターは1時間あたり25パーセントの割合で回復します。たとえば、キャラクターの体力レベルが25パーセントの場合、100パーセントまで完全に回復するには3時間かかります。次の図は、どのようにしてWidgetKitがタイムラインをプロバイダにリクエストし、タイムラインエントリに指定された各時点でウィジェットをレンダリングするかを示しています。

タイムラインをリクエストするWidgetKit、タイムラインを生成するプロバイダ、および3時間が経過した後に新しいタイムラインをリクエストするWidgetKitを示す図

WidgetKitが最初にタイムラインをリクエストするときに、プロバイダは4つのエントリを含むタイムラインを作成します。最初のエントリは現在の時刻を表し、その後に時間単位の間隔で3つのエントリが続きます。更新ポリシーがデフォルトのatEndに設定されているので、WidgetKitはタイムラインエントリの最終日時の後に新しいタイムラインをリクエストします。タイムラインのそれぞれの日時に達すると、WidgetKitはウィジェットのコンテンツクロージャを呼び出して結果を表示します。最後のタイムラインエントリを過ぎると、WidgetKitはプロバイダに新しいタイムラインを求めることでこのプロセスを繰り返します。キャラクターの体力が100パーセントに達したため、プロバイダは、現在の時刻を表す単一のエントリと、neverに設定された更新ポリシーで応答します。この設定の場合、WidgetKitは、アプリがWidgetCenterを使って、WidgetKitに新しいタイムラインをリクエストするよう指示するまで別のタイムラインをリクエストしません。

atEndおよびnever更新ポリシーに加えて、プロバイダは、エントリの最後に到達する前後にタイムラインが変更される可能性がある場合、異なる日時を指定できます。たとえば、ドラゴンがキャラクターにバトルを挑むために2時間表示される場合、プロバイダは再読み込みポリシーをafter(_:)に設定して、2時間後の日時を渡します。次の図は、どのようにしてWidgetKitが、2時間のマーク位置でウィジェットをレンダリングした後に、新しいタイムラインをリクエストするかを示しています。

タイムラインをリクエストするWidgetKit、タイムラインを生成するプロバイダ、および2時間後に新しいタイムラインをリクエストするWidgetKitを示す図

ドラゴンとのバトルのため、キャラクターの体力回復が100パーセントに達するまでに、さらに2時間かかります。新しいタイムラインは、現在の時刻を表す1つ目のエントリと、2時間後の2つ目のエントリで構成されます。このタイムラインは更新ポリシーとしてatEndを指定し、タイムラインを変更する可能性がある既知のイベントがこれ以上存在しないことを示します。

2時間が経過して、キャラクターの体力が100パーセントになると、WidgetKitはプロバイダに新しいタイムラインをリクエストします。キャラクターの体力が回復したため、プロバイダは上記の最初の図と同じ最終タイムラインを生成します。ユーザーがゲームをプレイして、キャラクターの体力レベルが変化すると、アプリはWidgetCenterを使って、WidgetKitがタイムラインを更新してウィジェットを更新するよう指示します。

タイムラインの最後よりの日時を指定することに加え、プロバイダはタイムラインの最後よりの日時を指定します。これは、ウィジェットの状態がもっと後の時間になるまで変化しないことをユーザーがわかっている場合に役立ちます。たとえば、株式市場ウィジェットでは、月曜日に市場がオープンする時刻を指定するafterDate()更新ポリシーを含めて、金曜日に市場がクローズする時間にタイムラインを作成できます。株式市場は週末はクローズしているため、市場がオープンするまでウィジェットを更新する必要はありません。

タイムラインが変更される日時をWidgetKitに通知

アプリは、何らかのイベントがウィジェットの現在のタイムラインに影響する場合、新しいタイムラインをリクエストするようにWidgetKitに指示します。上記のゲームウィジェットの例では、チームメンバーがキャラクターに回復ポーションを与えたことを示すプッシュ通知をアプリが受信した場合、アプリはWidgetKitに、タイムラインを再読み込みしてウィジェットのコンテンツを更新するように指示できます。特定のタイプのウィジェットを再読み込みするために、次に示すように、アプリはWidgetCenterを使います。

 WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")
							

kindパラメータには、ウィジェットのWidgetConfigurationを作成するために使用した値と同じ文字列が含まれます。

ウィジェットにユーザーが構成可能なプロパティが存在する場合は、WidgetCenterを使って、適切な設定を含むウィジェットが存在することを確認することで不要な再読み込みを回避します。たとえば、回復ポーションを受け取っているキャラクターに関するプッシュ通知をゲームが受信した場合、ゲームでは、タイムラインを再読み込みする前にウィジェットがそのキャラクターを表示していることを確認します。

次のコードでは、アプリがgetCurrentConfigurations(_:)を呼び出して、ユーザーが構成したウィジェットのリストを取得します。次にアプリは、結果のWidgetInfoオブジェクトを反復処理して、回復ポーションを受け取ったキャラクターが構成されているintentを含むオブジェクトを見つけます。該当するオブジェクトが見つかったら、アプリはreloadTimelines(ofKind:)を呼び出して、そのウィジェットのkindを呼び出します。


WidgetCenter.shared.getCurrentConfigurations { result in
    guard case .success(let widgets) = result else { return }


    // Iterate over the WidgetInfo elements to find one that matches
    // the character from the push notification.
    if let widget = widgets.first(
        where: { widget in
            let intent = widget.configuration as? SelectCharacterIntent
            return intent?.character == characterThatReceivedHealingPotion
        }
    ) {
        WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
    }
}

アプリがWidgetBundleを使って、複数のウィジェットをサポートしている場合は、WidgetCenterを使って、すべてのウィジェットのタイムラインを再読み込みできます。たとえば、ウィジェットでは、ユーザーがアカウントにサインインする必要があるが、ユーザーがサインアウトしている場合は、次のメソッドを呼び出して、すべてのウィジェットを再読み込みします。

 WidgetCenter.shared.reloadAllTimelines()
							

動的な日時の表示

ウィジェットは常時実行されるわけではありませんが、WidgetKitがライブで更新する時間ベースの情報をウィジェットに表示できます。たとえば、Widget Extensionが実行されていない場合でも、継続してカウントダウンするカウントダウン タイマーをウィジェットに表示します。詳しくは、「ウィジェットに動的な日時を表示する」を参照してください。

バックグラウンドネットワークリクエストの完了後の更新

snapshottimelineの提供時のように、Widget Extensionがアクティブな場合、Widget Extensionはバックグラウンドネットワークリクエストを初期化します。例としては、チームメンバーの現在のステータスを取得するゲームウィジェットや、画像のサムネールとともにトップ記事を取得するニュースウィジェットなどがあります。非同期のバックグラウンドネットワークリクエストを作成すると、システムにすぐにコントロールを返すことができるため、応答時間が長すぎて終了するリスクを軽減できます。

このプロセスは、「バックグラウンドでファイルをダウンロードする」に記載されている、アプリがこの種のリクエストを処理する方法に似ています。アプリを再開する代わりに、WidgetKitはウィジェットのExtensionを直接アクティブ化します。ネットワークリクエストの結果を処理するには、onBackgroundURLSessionEvents(matching:_:)修飾子をウィジェットの構成で使って、次の操作を実行します。

  • completionパラメータへの参照を格納します。すべてのネットワークイベントの処理後にcompletionハンドラを呼び出します。

  • identifierパラメータを使って、バックグランドリクエストの初期化時に使ったURLSessionオブジェクトを見つけます。Widget Extensionが終了した場合は、その識別子を使ってURLSessionを再作成します。

onBackgroundURLSessionEvents()を呼び出した後に、システムは、URLSessionに指定したURLSessionDelegateurlSession(_:downloadTask:didFinishDownloadingTo:)メソッドを呼び出します。すべてのイベントが配信されると、システムはそのデリゲートのurlSessionDidFinishEvents(forBackgroundURLSession:)メソッドを呼び出します。

ネットワークリクエストの完了後にウィジェットのタイムラインを更新するには、urlSessionDidFinishEventsのデリゲートの実装からWidgetCenterメソッドを呼び出します。イベントの処理が完了したら、onBackgroundURLSessionEvents()に以前に格納したcompletionハンドラを呼び出します。

関連項目

タイムライン管理