Reputation: 4300
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 to 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), iOS 17.0
Upvotes: 5
Views: 6760
Reputation: 30549
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
, e.g. you are doing something async with lifetime tied to something on screen. It is no longer needed in most cases since we now have async/await via the .task
modifier which runs while something is visible and cancels when it dissapears.
Upvotes: 5
Reputation: 61
Potentially not the way you should do it but it definitely works by registering the ModelContainer as a service using Factory.
Upvotes: 0
Reputation: 910
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)
Upvotes: 8