プッシュ通知の実装で外部との連携に慣れてきたところで、次はホーム画面に表示できるウィジェットをWidgetKitで実装してみることにしました。SwiftUIの延長線上だろうと軽く考えていたのですが、独特の制約がいくつもありました。
1. 普通の画面と同じ感覚でコードを書いたら更新されなかった
最初はいつも通りのSwiftUIビューと同じ感覚で、状態が変わったら自動的に再描画されるだろうと考えて実装していました。
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in
MyWidgetView(entry: entry)
}
}
}
しかし、アプリ本体でデータを更新しても、ウィジェットの表示が全く変わらないことに戸惑いました。ウィジェットは通常の画面のように「常に動いていて状態を監視している」わけではなく、決められたタイミングでしか更新されない仕組みだと後から理解しました。
2. TimelineProviderの役割を理解していなかった
調べていくと、ウィジェットの表示内容はTimelineProviderというものが「いつ・何を表示するか」のスケジュールを事前に組み立てて渡す仕組みになっていると分かりました。
struct Provider: TimelineProvider {
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
let entry = Entry(date: Date(), text: "最新のデータ")
let timeline = Timeline(entries: [entry], policy: .after(Date().addingTimeInterval(3600)))
completion(timeline)
}
}
「リアルタイムに反映される画面」ではなく「あらかじめ用意したスナップショットを、決めたタイミングで切り替えて表示する仕組み」なのだと理解してから、なぜ即座に更新されなかったのかに納得がいきました。この発想の転換に一番時間がかかりました。
3. アプリ本体とウィジェットでデータが共有されず空欄になった
ウィジェットの表示自体はできるようになったものの、肝心のデータがいつまでも空欄のままでした。原因は、アプリ本体が保存しているデータの場所に、ウィジェット側からアクセスできていなかったことでした。
App Groups(アプリグループ)を設定し、
アプリ本体とウィジェットの両方から同じ保存領域を参照できるようにする
ウィジェットはアプリ本体とは別のプロセスとして動くため、通常のUserDefaultsをそのまま共有できるわけではなく、「App Groups」という仕組みで明示的に共有領域を設定する必要があると知りました。「同じアプリなのだから当然データも見えるはず」という思い込みが原因でした。
4. Xcodeプレビューでは実際の見た目と差が出ることがあった
実装を進める中で、Xcodeのプレビュー上では綺麗に表示されていたのに、実機のホーム画面に追加すると余白やフォントサイズの見え方が微妙に異なることがありました。
#Preview(as: .systemSmall) {
MyWidget()
} timeline: {
Entry(date: .now, text: "サンプル")
}
プレビューはあくまで目安であり、最終確認は必ず実機(またはシミュレーターのホーム画面)に実際に追加して見た目を確かめる、という一手間を省かないようにしています。
まとめ
- ウィジェットは常時監視型ではなく、
TimelineProviderがあらかじめ用意したスナップショットを決めたタイミングで切り替える仕組み - アプリ本体とウィジェットは別プロセスのため、データ共有には「App Groups」の明示的な設定が必要
- Xcodeプレビューと実機での見た目には差が出ることがあるため、最終確認は実機で行う
- 通常のSwiftUI画面とは異なる制約が多く、「延長線上」という思い込みで進めると詰まりやすい
見た目はSwiftUIそのものでも、裏側の仕組みは別物として学び直す必要があると実感した実装でした。
次は、ASO(アプリストア最適化)でやってみたことをまとめます。
本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。


コメント