HealthKitでのヘルスデータ連携が一段落し、次は以前作った表示専用のウィジェットをタップ操作できるようにしたくなり、iOS 17以降のインタラクティブウィジェットに挑戦しました。
1. 通常のボタンと同じ感覚でアクションを書けると思っていた
最初、SwiftUIのButtonにいつも通りのクロージャを書けば動くものだと思い込んでいました。
// 通常はここに処理を書く
Button(action: { }) {
Text("完了")
}
ウィジェットの中では、通常のクロージャベースのアクションが実行されません。ウィジェットはアプリ本体とは別プロセスで動いているため、代わりにAppIntentという仕組みを使って、実行したい処理を独立した構造体として定義する必要がありました。
2. AppIntentをどこに定義すればいいか迷った
AppIntentを実装すること自体は分かったものの、最初はアプリ本体のターゲットにファイルを置いてしまい、ウィジェットExtensionから参照できずビルドエラーになりました。
AppIntentを定義する場所:
ウィジェットExtensionのターゲット
(アプリ本体とデータを共有する場合はApp Groupsも設定)
ウィジェットのExtensionターゲット側にAppIntentを定義し、アプリ本体とデータを共有したい場合は以前扱ったApp Groupsの仕組みを組み合わせる必要がありました。過去に整えた仕組みがここでも役立ちました。
3. ボタンを押してもウィジェットの見た目がすぐ変わらなかった
AppIntentのperform()メソッド内でデータを更新する処理は書けたのですが、ボタンを押した直後、ウィジェットの表示がすぐには変わらず不具合かと思いました。
perform()メソッドの中身は次のような流れです。
// データ更新処理のあとに呼ぶ
WidgetCenter.shared.reloadAllTimelines()
これを呼び忘れると、ウィジェット側の再描画がリクエストされず、表示は自動的には更新されません。ボタンを押した後の再描画は自分で指示する必要があると学びました。
4. 実機での動作確認がなかなか反映されなかった
コードを修正してビルドし直しても、ホーム画面のウィジェットに変更が反映されず、何度も首をかしげました。
ウィジェットはOSにキャッシュされる性質があり、アプリを削除して入れ直す、あるいはホーム画面からウィジェット自体を一度削除して追加し直すことで、最新の実装が反映されることが多いと分かりました。地味ですが、動作確認時の基本動作として覚えておくとよいポイントでした。
まとめ
- ウィジェット内のボタンは通常のクロージャでは動かない。
AppIntentとして処理を独立させる必要がある AppIntentはウィジェットExtension側に定義し、必要に応じてApp Groupsでアプリ本体とデータを共有するperform()内でWidgetCenter.shared.reloadAllTimelines()を呼ばないと、表示は自動更新されない- 変更が反映されない場合は、アプリの再インストールやウィジェットの再追加を試す
「タップできるウィジェット」という見た目以上に、プロセスをまたいだ設計を意識する必要のある実装でした。
次は、Xcode Instrumentsでパフォーマンス改善に挑戦してみた話をまとめます。
本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。


コメント