ゴール
「@Environment(\.modelContext) には .modelContainer(...) の情報が書かれていないのに、どうして値が入るの?」という疑問を解決するために、Environment仕組みの流れとSwiftDataでの使われ方を丁寧に解説します。
1. 大前提:Environment とは?
SwiftUI には Environment(環境値) という仕組みがあります。これは:
- 親ビューで設定した値を
- 子ビューに自動的に受け渡す
ための「辞書」のようなものです。キーと値のペアで構成され、ビュー階層を下へ下へと流れていきます。
例:
@Environment(\.colorScheme) var scheme // ダーク/ライトモードを受け取る
@Environment(\.dismiss) var dismiss // 画面を閉じるアクションを受け取る
これと同じ仕組みで modelContext も環境値として用意されています。
2. .modelContainer(...) がやっていること
WindowGroup {
ContentView()
}
.modelContainer(for: Item.self)
このモディファイアは、実際には次の処理をまとめて行っています:
- ModelContainer を生成
- 指定したモデル(
Itemなど)からスキーマを作成。 - それに基づいて 永続化の土台(ModelContainer) を構築。
- 指定したモデル(
- ModelContext を生成
- そのコンテナに紐づく ModelContext(編集用の机) を作成。
- Environment にセット
EnvironmentValues.modelContextにこのModelContextを格納。
3. @Environment(\.modelContext) がやっていること
@Environment(\.modelContext) private var context
これは単に:
- Environment の「辞書」から
modelContextというキーを使って- 値(ModelContext)を取り出す
という宣言です。
重要ポイント:ここには「保存先はどこ」などの情報は一切書かれていません。
値を「注入」するのはあくまで 親ビュー側の .modelContainer(...) の役目です。
4. 流れを図にすると
App/Scene
.modelContainer(for: Item.self)
│
└─ EnvironmentValues[.modelContext] = 生成された ModelContext
子ビュー
@Environment(\.modelContext)
│
└─ EnvironmentValues[.modelContext] から値を注入
つまり:
- 上(親)がセット
- 下(子)が読む
という流れ。
5. 動かして確認
もし .modelContainer(...) を外すと…
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView() // ← .modelContainer を付け忘れ
}
}
}
この場合、@Environment(\.modelContext) は 何もセットされていない状態で取り出そうとするので、実行時エラーになります。
“No ModelContext in environment”
これで「セットしてないと値が入らない」ことが確認できます。
6. まとめ
.modelContainer(...)- ModelContainer(データベースの土台)を作成して
- ModelContext(編集机)を生成し
- Environment に登録する
@Environment(\.modelContext)- Environment 辞書から
modelContextをキーに値を取り出すだけ
- Environment 辞書から
- 両者は直接リンクしているわけではなく、EnvironmentValues を介して間接的につながっている。
7. 実務での使い分け
- 読む係 →
@Query(自動フェッチ、自動更新) - 書く係 →
@Environment(\.modelContext)(insert/delete/save)
例:
// 読む
@Query private var items: [Item]
// 書く
@Environment(\.modelContext) private var context
Button("追加") {
let newItem = Item(timestamp: .now)
context.insert(newItem)
try? context.save()
}

コメント