【iOS】WidgetKit 入門
WidgetKitはiOS / iPadOS / macOSのウィジェット、watchOSのコンプリケーションを作成するためのフレームワークです。iOS 14でホーム画面にウィジェットを表示することができるようになり、iOS 16でロック画面にウィジェットを表示することができるようになりました。
Appにウィジェットを搭載することで、ウィジェット上に重要な情報を表示することができます。さらに、ウィジェットをタップして、App内の特定の機能やコンテンツにアクセスすることができるようになり、Appのユーザビリティを向上させることができます。
本記事ではウィジェットがどのように動作しているかを理解し、簡単なホーム画面 / ロック画面ウィジェットを構築することができるようになることをゴールとします。最初にホーム画面 / ロック画面ウィジェットを構築します。その後、サンプルソース内で定義されているプロトコルやメソッドの解説を進めながら、ウィジェットがどのように動作しているかを理解していきます。
ホーム画面ウィジェットの構築
Appにウィジェットを追加する為に、Widget Extensionターゲットをプロジェクトに追加します。File -> New -> Targetの順に選択すると、以下のようなターゲット一覧ダイアログが表示されます。
この中からWidget Extensionを選択すると、Widget Extensionに関する情報を入力するダイアログが表示されます。Product Nameに任意の名前を指定、Include Configuration Intentのチェックを外します(チェックをつけると、Intent
を使用してユーザがカスタマイズできるようなウィジェットを構築することができますが、今回はカスタマイズ不可能なウィジェットを構築します)。
今回はProduct Nameを"SampleWidget"としました。
そしてFinishを選択すると、Widget Extension Schemeをアクティブにするかを確認するダイアログが表示されますのでActivateを選択します。
プロジェクトにWidget Extensionターゲットが追加されました。
この時点でプロジェクトをビルドすると、ホーム画面ウィジェットを表示することができるようになります。
ロック画面ウィジェットの構築
次にロック画面ウィジェットを構築します。前項までの手順に沿って、Widget Extensionをプロジェクトに追加すると、以下のようなファイル群が生成されます。
この中のswiftファイルのコードに数行の修正を加えます。SampleWidget
の中身を以下のように修正してください。
詳細は後程説明しますが、上記の修正によって、iOS 16以上の時はウィジェットがサポートするサイズに、ロック画面で表示するウィジェットサイズを追加しています。この時点でプロジェクトをビルドすると、ロック画面にウィジェットを表示することができるようになります。
わずかこれだけの手順で、ホーム画面 / ロック画面にウィジェットを表示できるようになりました。次章からサンプルソースをベースに、WidgetKitでウィジェットを構築する際に必要となるプロトコルやメソッドの解説を行います。
ウィジェット構築の概要
ウィジェットを構築する上で重要な項目は以下の三つです。
1. ウィジェットの設定
2. ウィジェットのSwiftUIビュー
3. タイムラインとリロード
ウィジェットの設定では、ウィジェットのConfiguration
やサポートするサイズ、ウィジェット名といったウィジェット全体の設定を行います。ウィジェットのSwiftUIビューは、ウィジェットに表示するView
を定義します。タイムラインとリロードは、ウィジェットを更新するタイミングを定義します。
ウィジェットの設定
ウィジェットの設定では、後述するウィジェットのConfiguration
タイプや、サポートするウィジェットのサイズといった、ウィジェット全体の設定を定義します。
Widgetプロトコル
Widget
プロトコルには、WidgetConfiguration
型のbody
プロパティが定義されています。
WidgetConfiguration
body
プロパティには、WidgetConfiguration
プロトコルに準拠した以下の2種類のいずれかを使用します。
StaticConfiguration
ユーザがウィジェットをカスタマイズする必要がない時に使用します。
例えば、写真Appのウィジェットは、ユーザが写真Appに保存している写真を表示するだけで、ユーザがウィジェットをカスタマイズすることができないようになっています。
IntentConfiguration
ユーザがウィジェットをカスタマイズできるようにする時に使用します。
例えば、天気Appのウィジェットは天気を表示する地域を、ユーザがカスタマイズすることができるようになっています。
kind 引数
ウィジェットを識別するためのString
型のオブジェクトを渡します。WidgetKitでは一つのWidget Extensionに対して、複数のウィジェットを作成することができます。その際、kind
はそれぞれのウィジェットを区別する識別子の役割を果たします。例えば、ウィジェットをタップしてAppを起動した時、App側でどのタイプのウィジェット経由で起動したかを、kind
で識別することができます。
provider 引数
ウィジェットを更新するタイミングをWidgetKitに知らせるメソッドや、各タイミングでWidgetKitにタイムラインエントリと呼ばれるオブジェクトを、ウィジェットに渡すメソッドを定義したTimelineProvider
型のオブジェクトを渡します。TimelineProvider
プロトコルについては後程説明します。
content 引数
サンプルソースでは、トレイリングクロージャによって外部引数名が省略されていますが、一つのTimelineEntry
型のオブジェクトを引数にとって、SwiftUIビューを返すクロージャを渡します。この返されるSwiftUIビューが、ウィジェットに表示されるView
になります。
その他のmodifier(モディファイア)
.configurationDisplayName
ユーザーがウィジェットを設定する時に表示するタイトル文を指定します。
.description
ユーザーがウィジェットを設定する時に表示する説明文を指定します。
.supportedFamilies
ウィジェットがサポートするサイズを指定します。
サイズ | iOS | iPadOS | watchOS | 表示箇所 |
---|---|---|---|---|
systemSmall | ○ | ○ | × | iOS / iPadOSのホーム画面 |
systemMedium | ○ | ○ | × | iOS / iPadOSのホーム画面 |
systemLarge | ○ | ○ | × | iOS / iPadOSのホーム画面 |
systemExtraLarge | × | ○ | × | iPadOSのホーム画面 |
accessoryCircular | ○ | × | ○ | iOSのロック画面 / watchOSのコンプリケーション |
accessoryCorner | × | × | ○ | watchOSのコンプリケーション |
accessoryRectangular | ○ | × | ○ | iOSのロック画面 / watchOSのコンプリケーション |
accessoryInline | ○ | × | ○ | iOSのロック画面 / watchOSのコンプリケーション |
-
iOS / iPadOSのホーム画面ウィジェット
-
iOSのロック画面ウィジェット / watchOSのコンプリケーション
Widget Extensionをプロジェクトに追加した時に生成されるコードでは、.supportedFamilies
モディファイアの指定がありませんでした。.supportedFamilies
モディファイア指定がない時は、全てのホーム画面ウィジェットサイズがサポートされます。
「ロック画面ウィジェットの作成方法」の章で行ったSampleWidget
の修正は、iOS 16以上の時は、ホーム画面ウィジェットサイズに加えて、ロック画面ウィジェットサイズのaccessoryInline
, accessoryCircular
, accessoryRectangular
をサポートするようにしていました。
ウィジェットのSwiftUIビュー
ウィジェットのSwiftUIビューでは、ウィジェットに表示するView
を定義します。
View
View
プロトコルには、View
型のbody
プロパティが定義されています。body
プロパティにウィジェットで表示するView
を定義します。また、View
プロトコルに準拠した構造体にTimelineEntry
型のプロパティを定義することで、ウィジェットが更新されるタイミングでTimelineEntry
に準拠したオブジェクトをTimelineProvider
から受け取ることができます。サンプルソースでは現在の日付を表示するだけのView
が定義されています。
タイムラインとリロード
タイムラインとリロードでは、ウィジェットを更新するタイミングであるタイムラインエントリの配列と、次のタイムラインエントリの配列をリクエストするタイミングをWidgetKitに通知するメソッドや、各タイミングでウィジェットのSwiftUIビューに渡すTimelineEntry
オブジェクトを返す関数の定義を行います。
TimelineEntry プロトコル
TimelineEntry
プロトコルには、ウィジェットを更新する日付を表すプロパティが定義されています。また、ウィジェットを表示する日付に加えて、ウィジェットに表示したい情報を定義することもできます。定義したTimelineEntry
型のオブジェクトは、ウィジェットを更新する時にSwiftUI Viewに渡されます。
TimelineProvider プロトコル
WidgetKitは各タイミングでTimelineEntry
オブジェクトを要求してきます。TimelineProvider
プロトコルには、その各タイミングで要求されたTimelineEntry
オブジェクトを返すメソッドや、ウィジェットを更新するタイミングを決定するメソッドが定義されています。TimeLineProvider
プロトコルには三つのメソッドが定義されています。
placeholder メソッド
プレースホルダViewを表示する時に、WidgetKitが呼び出すメソッドです。プレースホルダViewにTimelineEntry
オブジェクトを渡します。プレースホルダViewは、端末のDynamic Type設定が変わる時といった、端末の環境が変わる時に表示されます。プレースホルダViewは、以下の画像のようにウィジェットの外観を表すためのものであり、実際に表示するコンテンツやデータを持っていません。
getSnapshot メソッド
ウィジェットを初めてウィジェットギャラリーに表示する時に、WidgetKitが呼び出すメソッドです。completionの引数に、TimelineEntry
オブジェクトを渡して、WidgetKitにウィジェットのSwiftUIビューを供給します。
getTimeline メソッド
ウィジェットギャラリーからウィジェットを選択した時に、WidgetKitが呼び出すメソッドです。TimelineEntry
オブジェクトの配列と、次のタイムラインを更新するタイミングを表すTimelineReloadPolicy
オブジェクトをセットにしたTimeline
オブジェクトを生成します。そしてcompletion
の引数に生成したTimeline
オブジェクトを渡して、WidgetKitにウィジェットの更新タイミングを通知します。
指定可能なTimelineReloadPolicy
は以下の3種類があります。
種類 | 動作 |
---|---|
atEnd | 最後のEntryが表示された時にWidgetKitにタイムラインの更新を依頼 |
after(date: Date) | 指定した日時にWidgetKitにタイムラインの更新を依頼 |
never | タイムラインの更新をしないようにWidgetKitに依頼 |
サンプルソースでは1時間ごとにウィジェットを更新し、最後のEntry
が表示された後に、WidgetKitにタイムラインの更新を依頼するようにしています。
ウィジェットの生成から更新までの動作
-
TimelineProvider
オブジェクト(Provider)や、ウィジェットに表示するSwiftUIビューを返すクロージャ(View Content)を引数にしたConfigurationを作成します。これにより、WidgetKitはTimelineProvider
オブジェクトに定義されているメソッドや、SwiftUIビューを返すクロージャを呼び出すことができるようになります。
-
ウィジェットギャラリーでウィジェットが選択されると、WidgetKitは「1」で引数に指定した、
TimelineProvider
オブジェクトのgetTimeline
メソッドを実行します。 -
getTimeline
メソッドは、タイムラインをWidgetKitに通知します。 -
タイムラインの通知を受け取ったWidgetKitは、タイムラインに含まれるタイムラインエントリの日時に達するとウィジェットを更新します。
-
全てのタイムラインエントリを消化すると、タイムラインに含まれる
TimelineReloadPolicy
に従って、再び「2」を実行します。
このサイクルを繰り返すことにより、ウィジェットは最新の状態を保つことができるようになっています。
サイズ別にウィジェットのViewを変更する
環境変数を利用することで、ユーザーが選択したウィジェットサイズを取得することができます。ウィジェットサイズを取得して、以下のように分岐させることで、サイズ別にウィジェットのViewを変更することができます。
ウィジェット内でタップされたLinkをApp側で識別する
systemMedium
、systemLarge
サイズのウィジェットでは、サブリンクを使用してウィジェット内でタップされたLink
が動作します。
- ウィジェット側
App側でLink
のurl
を受け取ることができます。url
の内容を見て、ウィジェット内のどのLink
がタップされたのかを判断することができます。
- App側 (SwiftUI)
- App側 (Swift)
複数のウィジェットを定義する
@main
属性に指定する構造体をWidgetBundle
プロトコルを準拠させます。そして、
@WidgetBundleBuilder
属性を使用して、body
プロパティに複数のウィジェットをグループ化します。
WidgetKitでウィジェットを実装する時に必要な項目は以上になります。今回はStaticConfiguration
でウィジェットを構築しましたが、IntentConfiguration
を使用してウィジェットを実装すると、インタラクティブなウィジェットを構築することができます。
更に学習を深めたいという方は、参考資料に記載しているWWDCのセッション動画を是非視聴してみてください。
参考資料
・Widget Extensionの作成
・Meet WidgetKit ・Widgets Code-along, part 1: The adventure begins ・Widgets Code-along, part 2: Alternate timelines ・Widgets Code-along, part 3: Advancing timelines ・Complications and widgets: Reloaded
Discussion