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
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.
I’m struggling through the same stuff – one thing I noticed:
Instead of:
try: