iosswiftuiswift-data

Understanding SwiftData and bindings/updates, a bare bones example


Doing this code-along, I'm trying to verify what little understanding I have of SwiftData by making a bare bones example.

This works:

import SwiftUI

@main
struct WWPDApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                
        }
        // this makes modelContext available in the app
        .modelContainer(for: Memo.self)
    }
}

struct ContentView: View {
    // for getting swiftdata
    @Query private var memos: [Memo]
    // for setting swiftdata
    @Environment(\.modelContext) var modelContext
    
    var body: some View {
        
        VStack {
            Button("ADD MEMO") {
                modelContext.insert(Memo(name: "new memo"))
            }
            List(memos) {memo in
                Text(memo.name)
            }
        }
        .padding()
    }
}

// Putting "@Model" here makes this class work with swift data
@Model
final class Memo {
    
    var id: UUID
    var name: String
    
    init(id: UUID = UUID(), name: String) {
        self.id = id
        self.name = name
    }
    
}

That works and verifies that we can add new objects and they are saved.

However, I would like to verify and understand the editting/binding aspect.

I understand that the modelContext is only used for inserting a new object. For editting an existing object, we are to rely on two way bindings with an object contained in the queried array(?)...

So I tried this:

struct ContentView: View {
    // for getting swiftdata
    @Query private var memos: [Memo]
    // for setting swiftdata
    @Environment(\.modelContext) var modelContext
    
    var body: some View {
        
        // need to put some kind of @Bindable thingy here
        
        VStack {
            Button("ADD MEMO") {
                modelContext.insert(Memo(name: "new memo"))
            }
            List(memos) {memo in
                TextField("this doesn't work", text: memo.$name)
            }
        }
        .padding()
    }
}

Solution

  • The @Model final class Memo ... enables change tracking for the class by adding conformance to the Observable protocol. So when you want a binding eg for memo.name you could use a @Bindable as shown in the example code.

    struct ContentView: View {
        // for getting swiftdata
        @Query private var memos: [Memo]
        // for setting swiftdata
        @Environment(\.modelContext) var modelContext
        
        var body: some View {
            VStack {
                Button("ADD MEMO") {
                    modelContext.insert(Memo(name: "new memo"))
                }
                List(memos) { memo in
                    @Bindable var memo = memo  // <--- here
                    TextField("this works", text: $memo.name)  // <--- here
                }
            }
            .padding()
        }
    }