struct StaticConfiguration
enum WidgetFamily
ウィジェットには、関連性のあるコンテンツが一目でわかる形で表示されるので、ユーザーはアプリにすばやくアクセスして詳細情報を確認できます。アプリはさまざまなウィジェットを提供できるため、ユーザーは自分にとって最も重要な情報に集中できます。同じウィジェットのコピーを複数追加し、固有のニーズやレイアウトに合わせてそれぞれのウィジェットをカスタマイズできます。カスタムインテントをウィジェットに含めれば、ユーザーはそれぞれのウィジェットを個別にカスタマイズすることもできます。ウィジェットは複数のサイズに対応しているため、お使いのアプリのコンテンツに最も適したサイズを選択できます。空きスペースが限られているため、ウィジェットには必ず、ユーザーにとって最も価値のある情報が表示されるようにしてください。
ウィジェットをアプリに追加するには、最小限の設定が必要で、ユーザーインターフェイスの構成とスタイルについていくつかの決定を行う必要があります。ウィジェットではSwiftUIビューを使ってコンテンツを表示します。詳しくは、SwiftUIを参照してください。
Widget Extensionテンプレートはウィジェットを作成するための出発点です。単一のWidget Extensionに複数のウィジェットを含めることができます。たとえば、スポーツアプリの1つのウィジェットにチーム情報を表示し、もう1つのウィジェットにゲームのスケジュールを表示する場合、両方のウィジェットを単一のWidget Extensionに含めることができます。
Xcodeでアプリプロジェクトを開き、「File(ファイル)」>「New(新規)」>「Target(ターゲット)」の順に選択します。
「Application Extension (App Extension)」グループから、「Widget Extension」を選択して「Next(次へ)」をクリックします。
Extensionの名前を入力します。
ユーザーが構成可能なプロパティをウィジェットが提供する場合は、「Include Configuration Intent(構成Intentを含める)」チェックボックスをオンにします。
「Finish(完了する)」をクリックします。
アプリに複数のExtensionが含まれている場合でも、通常、すべてのウィジェットを単一のWidget Extensionに含めます。たとえば、位置情報を利用するウィジェットと、位置情報を利用しないウィジェットがある場合、位置情報を利用するウィジェットを別個のExtensionに保持します。これにより、位置情報を利用するExtensionのウィジェットに対してのみ、システムは位置情報を利用する許可をユーザーに求めます。
Widget Extensionテンプレートは、Widget
プロトコルに適合する初期ウィジェット実装を提供します。このウィジェットのbody
プロパティは、ユーザーが構成可能なプロパティをウィジェットが含むかどうかを決定します。次の2種類の構成が使用できます。
Static
:ユーザーが構成可能なプロパティをウィジェットが含まない場合です。例として、一般的な市場情報を表示する株式市場ウィジェットや、話題のヘッドラインを表示するニュースウィジェットがあります。
Intent
:ユーザーが構成可能なプロパティをウィジェットが含む場合です。SiriKitのカスタムインテントを使って、プロパティを定義します。例として、都市の郵便番号が必要な天気ウィジェットや、追跡番号が必要な荷物追跡ウィジェットがあります。
「Include Configuration Intent(構成Intentを含める)」チェックボックスは、Xcodeでどちらの構成を使うかを決定します。このチェックボックスをオンにすると、XcodeではIntent構成が使用され、オフにすると、Static構成が使用されます。構成を初期化するには、次の情報を指定します。
kind:ウィジェットを識別する文字列です。これは、ユーザーが選択する識別子であり、ウィジェットが表す内容をわかりやすく説明するものです。
provider:Timeline
に適合して、ウィジェットをレンダリングするタイミングをWidgetKitに知らせるタイムラインを生成するオブジェクトです。タイムラインには、定義したカスタムのTimeline
タイプが含まれます。タイムラインエントリは、WdgetKitがウィジェットのコンテンツを更新する日時を識別します。ウィジェットのビューをカスタムタイプでレンダリングするのに必要なプロパティを含めます。
コンテンツクロージャ: SwiftUIビューを含むクロージャです。WidgetKitは、このクロージャを呼び出してウィジェットのコンテンツをレンダリングし、プロバイダからのTimeline
パラメータを渡します。
カスタムインテント:ユーザーが構成可能なプロパティを定義するカスタムインテントです。カスタマイズの追加について詳しくは、「構成可能なウィジェットの作成」を参照してください。
修飾子を使って、表示名、説明、ウィジェットが対応するファミリーなど、追加の構成の詳細を指定します。次のコードは、ゲームの構成ができない一般的なステータスを提供するウィジェットを示しています。
@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
クロージャを呼び出してウィジェットのコンテンツを表示します。最後に、修飾子が、ウィジェットギャラリーに表示される名前と説明を指定し、ユーザーがウィジェットの小/中/大のバージョンを選択できるようにします。
重要
アプリのウィジェットがウィジェットギャラリーに表示されるようにするには、ユーザーが、アプリのインストール後に、ウィジェットを含むアプリを少なくとも1回起動する必要があります。
このウィジェットでの@main
属性の使い方に注目してください。この属性は、Game
がWidget Extensionのエントリポイントであることを示し、Extensionに1つのウィジェットが含まれていることを意味しています。複数のウィジェットをサポートするには、「App Extensionでの複数のウィジェットの宣言」を参照してください。
タイムラインプロバイダはタイムラインエントリから構成されるタイムラインを生成します。それぞれのエントリはウィジェットのコンテンツを更新する日時を指定します。次に示すように、ゲームステータスのウィジェットでは、ゲームのステータスを表す文字列が含まれるようにタイムラインエントリを定義できます。
struct GameStatusEntry: TimelineEntry {
var date: Date
var gameStatus: String
}
ウィジェットをウィジェットギャラリーに表示するために、WidgetKitはプロバイダにプレビューのスナップショットを求めます。このプレビューリクエストを識別するには、get
メソッドに渡されるcontext
パラメータのis
プロパティをチェックします。is
が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はget
を呼び出して、プロバイダに標準のタイムラインをリクエストします。このタイムラインは、複数のタイムラインエントリと、次のタイムラインをリクエストするタイミングを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は、reason
にplaceholder
を指定したredacted(reason:)
ビュー修飾子を使います。ウィジェットのビュー階層にあるビューが自動的にプレースホルダーとしてレンダリングされないようにするには、unredacted()
ビュー修飾子を使います。
Widget Extensionで「Data Protection(データ保護)」の機能を有効化にすると、データ保護エンタイトルメントに次の値が指定され、関連する条件が満たされた場合に、WidgetKitはウィジェットをプレースホルダーとしてレンダリングします。
NSFile
またはNSFile
、およびデバイスがロックされていること。
NSFile
、およびユーザーがまだ認証されていないこと。
「Data Protection(データ保護)」の構成について詳しくは、Data Protection Entitlement
を参照してください。
ウィジェットでは、SwiftUIビューを使って、一般的に他のSwiftUIビューを作成することでそのコンテンツを定義します。「構成の詳細の追加」セクションで示したとおり、ウィジェットの構成には、WidgetKitがウィジェットのコンテンツをレンダリングするために呼び出すクロージャが含まれます。
ユーザーがウィジェットギャラリーからウィジェットを追加するときは、ウィジェットが対応しているものの中から特定のファミリー(小、中、大)を選択します。ウィジェットのコンテンツクロージャは、ウィジェットが対応しているそれぞれのファミリーのレンダリングをサポートしている必要があります。WidgetKitはSwiftUI環境で、対応するファミリーと色スキーム(濃淡の配色)などの追加のプロパティを設定します。
上記のgame-statusウィジェットの構成では、コンテンツクロージャでGame
を使ってステータスを表示します。このウィジェットは3つすべてのウィジェットファミリーに対応しているため、次に示すとおり、widget
を使って、表示する特定のSwiftUIビューを決定します。
struct GameStatusView : View {
var family: WidgetFamily (\.widgetFamily)
var gameStatus: GameStatus
var body: some View {
switch family {
case .systemSmall: GameTurnSummary(gameStatus)
case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
case .systemLarge: GameStatusWithStatistics(gameStatus)
default: GameDetailsNotAvailable()
}
}
}
「小」ファミリーの場合、ウィジェットでは、ゲームの順番をまとめて表示するビューを使用します。「中」の場合、前回の順番の結果を示すステータスが表示されます。「大」の場合、使用可能なスペースが増えるため、各プレーヤーの実行統計情報が表示されます。ファミリーが不明なタイプの場合は、ゲームステータスが利用不可であることを示すデフォルトビューが表示されます。
注意
このビューでは、使用するビューのタイプが変化するため、@View
を含むbodyが宣言されます。
構成可能なウィジェットでは、プロバイダがIntent
に適合します。このプロバイダはTimeline
と同じ機能を実行しますが、ウィジェットでユーザーがカスタマイズする値を組み込みます。このカスタマイズは、get
とget
の両方に渡されるconfiguration
パラメータのタイムラインプロバイダのIntentで利用できます。通常、ユーザーが構成した値をカスタムのタイムラインエントリタイプのプロパティとして含めるため、詳細をウィジェットのビューで利用できます。
重要
ウィジェットは読み取り専用の情報を提供し、スクロール要素やスイッチなどの対話型要素はサポートしていません。WidgetKitは、ウィジェットのコンテンツのレンダリング時に対話型要素を省略します。
WidgetKitがサポートしているビューのリストについては、「SwiftUIビュー」を参照してください。
ウィジェットの表示はビューのスナップショットに基づいていますが、ウィジェットが表示されている間に継続して更新される各種のSwiftUIビューを使うことができます。ダイナミックコンテンツの提供について詳しくは、「ウィジェットを最新の状態に維持」を参照してください。
ユーザーがウィジェットを操作するとき、システムはアプリを起動してリクエストを処理します。システムがアプリをアクティブ化すると、ウィジェットのコンテンツに対応する詳細に移動します。ウィジェットでは、URLを指定して、表示するコンテンツをアプリに通知することができます。ウィジェットでカスタムURLを構成するには、次の手順を実行します。
すべてのウィジェットで、ウィジェットのビュー階層にあるビューにwidget
ビュー修飾子を追加します。ウィジェットのビュー階層に複数のwidget
修飾子が含まれている場合、動作は定義されていません。
Widget
またはWidget
を使うウィジェットでは、1つ以上のLink
コントロールをウィジェットのビュー階層に追加します。widget
コントロールとLink
コントロールの両方を使うことができます。操作がLink
コントロールを対象とする場合、システムはそのコントロールのURLを使います。ウィジェットの他の場所で操作が行われた場合、システムはwidget
ビュー修飾子に指定されたURLを使用します。
たとえば、ゲームの1つのキャラクターの詳細を表示するウィジェットで、widget
を使って、そのキャラクターの詳細をアプリで開くことができます。
var body: some View {
ZStack {
AvatarView(entry.character)
.widgetURL(entry.character.url)
.foregroundColor(.white)
}
.background(Color.gameBackground)
}
ウィジェットにキャラクターのリストが表示される場合、リスト内のそれぞれのアイテムをLink
コントロールに指定できます。各Link
コントロールは、表示する特定のキャラクターのURLを指定します。
ウィジェットが操作を受け取ると、システムは含まれるアプリをアクティブ化し、アプリで使われるライフサイクルに応じて、URLをon
、application(_:
、またはapplication(_:
に渡します。
ウィジェットでwidget
コントロールとLink
コントロールをどちらも使わない場合、システムは含まれるアプリをアクティブ化し、NSUser
をon
、application(_:
、またはapplication(_:
に渡します。ユーザーアクティビティのuser
ディクショナリには、ユーザーが操作したウィジェットに関する詳細が含まれています。これらの値にSwiftコードからアクセスするには、Widget
のキーを使用します。Objective-Cからuser
の値にアクセスするには、代わりにWGWidget
キーとWGWidget
キーを使用します。
Intent
を使用するウィジェットの場合、ユーザーアクティビティのinteraction
プロパティにウィジェットのINIntent
が含まれます。
上記のGame
の例では、@main
属性を使用してWidget Extensionの単一のエントリポイントを指定しています。複数のウィジェットをサポートするには、Widget
に適合して、複数のウィジェットをbody
プロパティにまとめる構造体を宣言します。このwidget-bundle構造体で@main
属性を追加して、Extensionが複数のウィジェットをサポートすることをWidgetKitに知らせます。
たとえば、ゲームアプリに、キャラクターの体力を表示する2つ目のウィジェットと、リーダーボードを表示する3つ目のウィジェットが存在する場合、次に示すように、それらのウィジェットを1つにまとめます。
@main
struct GameWidgets: WidgetBundle {
var body: some Widget {
GameStatusWidget()
CharacterDetailWidget()
LeaderboardWidget()
}
}
struct StaticConfiguration
enum WidgetFamily