🖼️

【iOS】WidgetKit 入門

2022/10/06に公開

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にタイムラインの更新を依頼するようにしています。

ウィジェットの生成から更新までの動作

  1. TimelineProviderオブジェクト(Provider)や、ウィジェットに表示するSwiftUIビューを返すクロージャ(View Content)を引数にしたConfigurationを作成します。これにより、WidgetKitはTimelineProviderオブジェクトに定義されているメソッドや、SwiftUIビューを返すクロージャを呼び出すことができるようになります。

  1. ウィジェットギャラリーでウィジェットが選択されると、WidgetKitは「1」で引数に指定した、TimelineProviderオブジェクトのgetTimeline メソッドを実行します。

  2. getTimeline メソッドは、タイムラインをWidgetKitに通知します。

  3. タイムラインの通知を受け取ったWidgetKitは、タイムラインに含まれるタイムラインエントリの日時に達するとウィジェットを更新します。

  4. 全てのタイムラインエントリを消化すると、タイムラインに含まれるTimelineReloadPolicyに従って、再び「2」を実行します。

このサイクルを繰り返すことにより、ウィジェットは最新の状態を保つことができるようになっています。

サイズ別にウィジェットのViewを変更する

環境変数を利用することで、ユーザーが選択したウィジェットサイズを取得することができます。ウィジェットサイズを取得して、以下のように分岐させることで、サイズ別にウィジェットのViewを変更することができます。

ウィジェット内でタップされたLinkをApp側で識別する


systemMediumsystemLarge サイズのウィジェットでは、サブリンクを使用してウィジェット内でタップされたLink が動作します。

  • ウィジェット側

App側でLinkurl を受け取ることができます。url の内容を見て、ウィジェット内のどのLink がタップされたのかを判断することができます。

  • App側 (SwiftUI)
  • App側 (Swift)

複数のウィジェットを定義する

@main 属性に指定する構造体をWidgetBundle プロトコルを準拠させます。そして、
@WidgetBundleBuilder 属性を使用して、body プロパティに複数のウィジェットをグループ化します。

WidgetKitでウィジェットを実装する時に必要な項目は以上になります。今回はStaticConfiguration でウィジェットを構築しましたが、IntentConfiguration を使用してウィジェットを実装すると、インタラクティブなウィジェットを構築することができます。
更に学習を深めたいという方は、参考資料に記載しているWWDCのセッション動画を是非視聴してみてください。

参考資料

・Widget Extensionの作成
https://developer.apple.com/jp/documentation/widgetkit/creating-a-widget-extension/
・Meet WidgetKit
https://developer.apple.com/videos/play/wwdc2020/10028/
・Widgets Code-along, part 1: The adventure begins
https://developer.apple.com/videos/play/wwdc2020/10034/
・Widgets Code-along, part 2: Alternate timelines
https://developer.apple.com/videos/play/wwdc2020/10035/
・Widgets Code-along, part 3: Advancing timelines
https://developer.apple.com/videos/play/wwdc2020/10036/ç
・Complications and widgets: Reloaded
https://developer.apple.com/videos/play/wwdc2022/10050/

Discussion