🖼️

【iOS 17】Widgetにインタラクティブな機能とアニメーション効果を追加する

2023/08/22に公開


iOS 17では、Widgetに対してインタラクティブな機能と、アニメーション効果を追加することができるようになりました。
https://developer.apple.com/videos/play/wwdc2023/10028/
本記事ではiOS 17でWidgetに追加されたインタラクティブ機能と、アニメーション効果の実現方法について書きます。

アニメーション効果の追加

SwiftUIを使用して作られる通常のアプリでは、状態を表す変数(@State@Binding がついた変数)の変更が影響するViewに対して、withAnimation(_:_:) を付加することでアニメーションを実現することができます。
しかし、Widgetは状態を持つことができません。その代わり、特定の時間にレンダリングされるViewに対して提供する、エントリのタイムラインを作成します。特定の時間にタイムラインから提供されるエントリを元に作成したViewをWidget上に表示することで、Widgetの表示を更新しています。

Xcode 15では、プロジェクトを再ビルドするだけで、エントリ間で変更があったコンテンツを含むViewを、WidgetKitが自動で特定してくれるようになります。

特定したViewに対して、WidgetKitがアニメーション効果を付与することで、コンテンツの変化がアニメーションで表現されるようになります。
以下の画像はWidget内のText の内容に変更があった時に、デフォルトで付与されるアニメーション効果です。

contentTransition(_:)

contentTransition(_:) を使用することで、コンテンツ変更時のトランジションタイプを変更することができます。

  • .numericText()
  • .opacity
  • .interpolate
  • .symbolEffect

animation(_:value:)

animation(_:value:) を使用することで、トランジションにアニメーション効果を付与することもできます。

transition(_:)

変更が入ったコンテンツを含むViewだけではなく、特定のViewのまとまりに対してトランジション効果を付与することもできます。



トランジション効果を付与したいViewにid(_:) を指定します。id(_:) の引数には変更を監視したい値を指定します。そして、transition(_:) でトランジションタイプを指定することで、複数のViewに対してトランジション効果を付与することができます。サンプルコードでは、entry.count に変更が入ると、VStack で囲んだView全体に対して、transition(_:) で指定したトランジション効果が実行されます。

インタラクティブ性の実現

ButtonToggle を使用することで、Widgetにインタラクティブ性を持たせることができます。

処理の流れ

Widgetは、以下の1 ~ 4を繰り返すことでインタラクティブ性を実現することができます。

  1. Widget上のButton またはToggle をタップ
  2. Button またはToggle 初期化時に指定したIntent を実行
  3. タイムラインのリロードを開始
  4. Widgetに表示しているViewを更新

ButtonとToggleに追加された新しいイニシャライザ

ButtonToggle に新しいイニシャライザが追加されています。

ButtonまたはToggle がタップされた時に実行するIntent を初期化時に指定します。

App Intent

AppIntent プロトコルに準拠した構造体を定義することで、Intent を作成することができます。AppIntent プロトコルに準拠するためにはtitleperform() を定義する必要があります。

perform() メソッド内にButtonまたはToggle がタップされた時に実行したい処理を記述します。

サンプルアプリの実装

今回はカウンター機能を持ったWidgetを作成します。

Counterモデルクラス

数値をインクリメント、デクリメント、現在の数値を取得するメソッドを定義したモデルクラスを準備します。

Intent

IncrementIntent では数値をインクリメントする処理を、DecrementIntent では数値をデクリメントする処理を、各perform() メソッド内に記述しています。

Widget

Button の引数にボタンがタップされた時に実行したいIntent を指定しています。Intent が実行されるたびに、カウンターの数値がインクリメント/デクリメントされます。
そして、タイムラインがリロードされ、タイムラインはカウンターの数値を元に新しいエントリを作成します。
カウンターの数値を含んだエントリを元に、Viewがレンダリングされることで、表示される数値が更新されるような動きになります。

実行結果

補足

「CounterWidget」という名称でWidgetエクステンションをプロジェクトに追加した後に、以下のコードを「CounterWidget.swift」にコピペしていただくと、カウンター機能を持ったWidgetを簡単に作成することができます。
https://gist.github.com/NAOYA-MAEDA-DEV/63eb3255e6f1895d77de5e1905c9bea1

参考資料

・Bring widgets to life
https://developer.apple.com/videos/play/wwdc2023/10028/

Discussion