skip to Main Content

I’m trying to understand the new SwiftData framework. It’s pretty easy to get it to work as long as
I do everything in a SwiftUI view. I’m trying to be a good coder and separate the data
from the UI but have been unable to get connections to the ModelContainer and the ModelContext
is a class file.

Here is an example that can be run as is:

Model:

@Model
final public class Thing: Identifiable {

    let myID = UUID()
    var name: String
    var comment: String

    init(name: String, comment: String) {
        self.name = name
        self.comment = comment
    }

}//struct

ContentView: Change the code in the Button for Create 10 Test Records to use the VM version.

struct ContentView: View {

    @StateObject var contentVM = ContentViewModel()
    @Environment(.modelContext) private var context

    @State private var name: String = ""
    @State private var comment: String = ""

    @State private var selection: Thing?

    @Query(sort: .name) var things: [Thing]

    var body: some View {
        NavigationStack {
            VStack(alignment: .leading) {
                Text("Name:")
                    .padding(.leading, 12)
                TextField("name", text: $name)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding(EdgeInsets(top: 5, leading: 10, bottom: 0, trailing: 10))
            
                Text("Comment:")
                    .padding(.leading, 12)
                TextField("comment", text: $comment)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding(EdgeInsets(top: 5, leading: 10, bottom: 0, trailing: 10))
            }//v
            .padding()
            VStack(spacing: 20) {
                    Button(action: {
                        let thing = Thing(name: name, comment: comment)
                        context.insert(object: thing)
                    }, label: {
                        Text("Save")
                    })
                
                    Button(action: {
                        //contentVM.createThingsForTestVM(count: 10)
                        createThingsForTest(count: 10)
                    }, label: {
                        Text("Create 10 Test Records")
                    })
                }//v buttons

            Divider()
        
            List {
                ForEach(things) { thing in
                    Text(thing.name)
                }
                .onDelete(perform: deleteThings(at:))
            }//list
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    Button {
                        deleteAllThings()
                    } label: {
                        Image(systemName: "trash")
                    }

                }//group
            }//toolbar
        }//nav stack
    }//body

    private func deleteThings(at offsets: IndexSet) {
        withAnimation {
            offsets.map { things[$0] }.forEach(deleteThing)
        }
    }//delete at offsets

    private func deleteThing(_ thing: Thing) {

        //Unselect the item before deleting it.
        if thing.objectID == selection?.objectID {
            selection = nil
        }
        context.delete(thing)
    }//delete things

    private func deleteAllThings() {
    
        for t in things {
            if t.objectID == selection?.objectID {
                selection = nil
            }
            context.delete(t)
        }
    }//delete all things

    private func createThingsForTest(count: Int) {
    
        for i in 0..<count {
            let t = Thing(name: "Name " + String(i), comment: "Comment " + String(i))
            context.insert(object: t)
        }
    
        do {
            try context.save()
        } catch {
            print(error)
        }
    
    }//create things

}//struct content view

ContentViewModel: This does create records but it does not update the UI and it seems
like the wrong approach to me. I tried to setup the ModelContainer and the ModelContext
in the initializer but I was not able to make that work at all.

class ContentViewModel: ObservableObject {

    init() {}

    @MainActor
    func createThingsForTestVM(count: Int) {
    
        do {
            let container = try ModelContainer(for: Thing.self)
            let context = container.mainContext
        
            for i in 0..<count {
                let t = Thing(name: "Name " + String(i), comment: "Comment " + String(i))
                context.insert(object: t)
            }
        
            try context.save()
        
        } catch {
            print("Could not create a container (error.localizedDescription)")
        }
    }//create things
}//class

Any guidance would be appreciated. Xcode 15.0 Beta (15A5160n), iOS17.0

2

Answers


  1. In SwiftUI the View struct is already separate from the UI and the View struct hierarchy should be your primary encapsulation mechanism for view data.

    SwiftUI diffs these structs and creates/updates/removes the UI objects automatically for you, ie it manages the actual view layer or the V in MVC.

    So you can just remove the custom view model object and use the View struct and property wrappers as designed.

    StateObject is for when you want a reference type in a State, eg you are doing something async with lifetime tied to something on screen. Now we have async await and the task modifier it is no longer needed in most cases.

    Login or Signup to reply.
  2. I’m struggling through the same stuff – one thing I noticed:

    Instead of:

    let container = try ModelContainer(for: Thing.self)
    let context = container.mainContext
    

    try:

    let container = try ModelContainer(for: Thing.self)
    let context = ModelContext(container)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search