classswiftuibindingswiftdata

Proper way to pass a class around SwiftUI views


I have a SwiftData class that I get with @Query private var items: [Item] in a SwiftUI view. Then I pass it to a sub view that passes it to other sub views. What is the best way to pass the class around. I need to be able to modify it, and its properties (structs and classes) in the sub views. Should I use @Binding, @Bindable, @ObservableObject or something else?


Solution

  • Here is an example of how to pass the items around to various subviews. In essence, use a let items: [Item] in the subviews that just display the info. Use a @Bindable var item: Item when you want to modify an item property. Use @Environment(\.modelContext) private var modelContext to add (or delete) an item to SwiftData, that will update your array items: [Item]. You cannot add or delete an item from the array items: [Item] directly (it is read only), you must use the context.

    
     @Model
     final class Item {
         var name: String
         
         init(name: String) {
             self.name = name
         }
     }
     
     @main
     struct TestApp: App {
         var body: some Scene {
             WindowGroup {
                 ContentView()
             }
             .modelContainer(for: Item.self)
         }
     }
    
    struct ContentView: View {
        @Query private var items: [Item]
    
        var body: some View {
            Layer1View(items: items)
        }
    }
    
    struct Layer1View: View {
        let items: [Item]
        
        var body: some View {
            Text("Layer1View items count: \(items.count)")
            Layer2View(items: items)
        }
    }
    
    struct Layer2View: View {
        @Environment(\.modelContext) private var modelContext
        let items: [Item]
        
        var body: some View {
            List(items) { item in
                EditorView(item: item)
            }
            Button("Add item") {
                let newItem = Item(name: "Mickey Mouse")
                modelContext.insert(newItem)
            }
        }
    }
    
    struct EditorView: View {
        @Bindable var item: Item
        
        var body: some View {
            TextField("", text: $item.name).border(.red)
        }
    }