I’m building a document‑based Swift UI app using DocumentGroup with SwiftData. I want:
Gate) that is persisted inside a fileUser) that is stored in the app’s container (outside the document), like a typical .modelContainer(for:) setup.When I attach .modelContainer(for: User.self) to my ContentView, my Gate model (the document data) stops working. If I remove the User container, Gate works again but User is unavailable. How can I use both stores at the same time without the environments stepping on each other?
import SwiftData
import Foundation
// This should be stored inside the document
@Model
final class Gate {
var number: Int
var manufacturer: String
var year: Int
var broken: Bool
init(number: Int, manufacturer: String, year: Int, broken: Bool) {
self.number = number
self.manufacturer = manufacturer
self.year = year
self.broken = broken
}
}
// This should be stored inside the app's container
@Model
final class User {
var name: String
var locationCity: String
var birthYear: Int
init(name: String, locationCity: String, birthYear: Int) {
self.name = name
self.locationCity = locationCity
self.birthYear = birthYear
}
}
import UniformTypeIdentifiers
extension UTType {
static var gateDocument = UTType(exportedAs: "com.example.gatedoc")
}
import SwiftUI
import SwiftData
@main
struct MyApp: App {
var body: some Scene {
DocumentGroup(editing: Gate.self, contentType: .gateDocument) {
// If I add the user container here, Gate stops working
ContentView()
.modelContainer(for: User.self)
}
}
}
My understanding is that View.modelContainer(for:) sets a new ModelContainer (and thus a new modelContext) in the environment for that view subtree, which overrides the ModelContainer provided by DocumentGroup. Since @Query uses the environment modelContext, there’s effectively only one active context per view subtree. I need to somehow get both models with their different configurations into one container.
I am aware that I can create a custom ModelContainer to get multiple objects into a single container (for storing only part of the Data in CloudKit for example). However since I am using DocumentGroup for Gate, I don’t know to control the behavior of its ModelContainer.
You can do something similar to my answer here, where you inject your own app-wide model container for User, into the environment.
@main
struct FooApp: App {
var body: some Scene {
DocumentGroup(editing: Gate.self, contentType: .gateDocument) {
ContentView()
.environment(\.userContainer, Self.userContainer)
}
}
static let userContainer = try! ModelContainer(for: User.self)
}
extension EnvironmentValues {
@Entry var userContainer: ModelContainer? = nil
@MainActor
var userContext: ModelContext? {
userContainer?.mainContext
}
}
You can then switch between the two containers using the .modelContainer modifier. You can write a helper view like this that allows you to switch to a given ModelContainer within a @ViewBuilder closure:
struct QueryView<Content: View, Model: PersistentModel>: View {
@Query var models: [Model]
let content: ([Model]) -> Content
init(query: () -> Query<Model, [Model]>, content: @escaping ([Model]) -> Content) {
self._models = query()
self.content = content
}
var body: some View {
content(models)
}
}
struct QueryContainer<Content: View, Model: PersistentModel>: View {
let content: ([Model]) -> Content
let query: () -> Query<Model, [Model]>
let container: ModelContainer
init(_ container: ModelContainer, for type: Model.Type, query: @autoclosure @escaping () -> Query<Model, [Model]> = .init(), @ViewBuilder content: @escaping ([Model]) -> Content) {
self.query = query
self.content = content
self.container = container
}
var body: some View {
QueryView<Content, Model>(query: query, content: content)
.modelContainer(container)
}
}
Example usage: suppose you are in ContentView where the Gate model container is active:
@Query
var gates: [Gate] // You can directly query Gates
@Environment(\.modelContext) var gateContext
// you can get access to the container for Users like this
@Environment(\.userContainer) var userContainer
@Environment(\.userContext) var userContext
var body: some View {
GatesView(gates)
// to also query users, wrap your view with QueryContainer
QueryContainer(userContainer, for: User.self) { users in
UsersView(users)
// inside this closure, the Gates container is not active
// if some view in here needs to query Gates, remember to set the model container back
SomeViewThatQueriesGates()
.modelContainer(gateContext.container) // access the container for Gates through the context for Gates
}
}
Here is a complete ContentView where I have added forms to add gates and users, to demonstrate everything working
struct ContentView: View {
@Query
var gates: [Gate]
@Environment(\.modelContext) var gateContext
@Environment(\.userContext) var userContext
@Environment(\.userContainer) var userContainer
@State private var number = 0
@State private var manufacturer = ""
@State private var year = 2000
@State private var broken = false
@State private var name = ""
@State private var locationCity = ""
@State private var birthYear = 1990
var body: some View {
VStack {
HStack {
Form {
TextField("Number", value: $number, format: .number)
TextField("Manufacturer", text: $manufacturer)
TextField("Year", value: $year, format: .number)
Toggle("Broken", isOn: $broken)
Button("Add") {
gateContext.insert(Gate(number: number, manufacturer: manufacturer, year: year, broken: broken))
try! gateContext.save()
}
}
List(gates) {
Text($0.description)
}
}
HStack {
Form {
TextField("Name", text: $name)
TextField("Location City", text: $locationCity)
TextField("Birth Year", value: $birthYear, format: .number)
Button("Add") {
userContext?.insert(User(name: name, locationCity: locationCity, birthYear: birthYear))
try! userContext?.save()
}
}
QueryContainer(userContainer!, for: User.self) { users in
VStack {
List(users) {
Text($0.description)
}
SomeViewThatQueriesGates()
.modelContainer(gateContext.container)
}
}
}
}
}
}
struct SomeViewThatQueriesGates: View {
@Query
var gates: [Gate]
var body: some View {
Text("\(gates.count) Gates")
}
}
extension Gate: CustomStringConvertible {
var description: String {
"\(number): \(manufacturer) \(year), broken: \(broken)"
}
}
extension User: CustomStringConvertible {
var description: String {
"\(name) in \(locationCity), born \(birthYear)"
}
}