概要
iOS の Widget 拡張は別プロセスで動作するため、本体アプリと直接データ共有できない。
AppGroup UserDefaults を経由してデータを共有する。本体起動時に最新データを書き込み、Widget が読み込む構造。
関連画面
同期フロー
flowchart LR
App[本体アプリ起動] --> AVM[AppViewModel]
AVM --> Sync[syncWidgetData]
Sync --> WTip[WidgetTipData
Codable]
Sync --> WCal[WidgetCalendarData
Codable]
WTip --> AG[(AppGroup
UserDefaults)]
WCal --> AG
AG -.読込.-> SP[SmallTipWidgetProvider]
AG -.読込.-> MP[MediumCalendarProvider]
SP --> SW[SmallTipWidget]
MP --> MW[MediumCalendarWidget]
AppGroup 設定
enum WidgetAppGroup {
static let suiteName = "group.com.happyboy1002.DailyTips"
static let tipDataKey = "widgetTipData"
static let calendarDataKey = "widgetCalendarData"
}
// UserDefaults へのアクセス(共通パターン)
let store = UserDefaults(suiteName: WidgetAppGroup.suiteName) ?? .standard
同期タイミング
| イベント | 処理 |
| 本体アプリ起動時 | AppViewModel が syncWidgetData() を呼出 |
| 豆知識切り替え時(日付変更) | HistoryService.markAsLogin() 経由で再同期 |
| WidgetCenter.shared.reloadAllTimelines() | Widget の即時更新トリガー |
| Widget 自身の Timeline | 1時間ごと + 翌日0時に自動再取得 |
共有データ
WidgetTipData(Small用)
struct WidgetTipData: Codable, Sendable {
let title: String
let category: String
let dateKey: String
let updatedAt: Date
}
WidgetCalendarData(Medium用)
struct WidgetCalendarData: Codable, Sendable {
let loginDaysThisWeek: [Bool] // 7要素
let loginStreak: Int
let updatedAt: Date
}
ビジネスルール
- 本体・Widget の整合性は AppGroup UserDefaults を Single Source of Truth とする
- Widget は本体に依存せず、AppGroup nil でも空表示で動作(リカバリは本体起動時)
- 本体起動時に必ず syncWidgetData() を実行
- 更新後は WidgetCenter.shared.reloadAllTimelines() で即時反映
外部連携
| 連携先 | 用途 |
| WidgetKit | Widget拡張の宣言的UI実装 |
| App Group | 本体・Widget間のサンドボックス共有 |
エラー処理
| 発生条件 | 対応 |
| AppGroup UserDefaults nil | standardへフォールバック、Logger.warning |
| JSON decode 失敗 | Widget 空表示(plain placeholder) |
実装メモ
- project.yml で App Group capability 設定済み(本体・Widget両方)
- TimelineProvider で
policy: .after(startOfTomorrow)
- HistoryService.syncWidgetData() で AppGroup 書込
- Widget の Smart Stack 対応(v1.x)
変更履歴
| バージョン | 日付 | 変更内容 |
| 1.0 | 2026-05-09 | 初版作成 |