Reputation: 11
In the following example, changes to my UserGroup
aren't saved and don't update the UI.
Upon tapping the user's id, the function to change the id is called, but the user's id is never updated and the UI never changes.
In the following code:
User
: A simple user objectUserGroup
: an @Model
object that stores a list of usersMyViewModel
: A view model that stores a UserGroup
and has a function to update the ID of a user in the group. The modelContext
property is there because I get errors if I remove itMain
: A view that display's a user's id
and changes the id
when tapped@Observable class User: Codable {
var id: String
init() {
self.id = UUID().uuidString
}
}
@Model
class UserGroup {
var users: [User]
init() {
self.users = [User()]
}
}
@Observable class MyViewModel {
var modelContext: ModelContext
var userGroup: UserGroup()
init(modelContext: ModelContext) {
self.modelContext = modelContext
self.userGroup = UserGroup()
}
func generateNewId() {
userGroup.users[0].id = UUID().uuidString
}
}
struct Main: View {
@Environment(\.modelContext) var modelContext
@State var viewModel: MyViewModel?
var body: some View {
ZStack {
if viewModel != nil {
Text(viewModel!.dataModel.users[0].id)
.onTapGesture {
viewModel!.generateNewId()
}
}
}
.onAppear { viewModel = MyViewModel(modelContext: modelContext) }
}
}
Instead of changing the id property of a user in userGroup.users
through userGroup.users[0].id = <new id>
, I tried reassigning it using userGroup.users[0] = User()
. This got the user's id to successfully change, but didn't update the UI until I restarted the app.
I also tried making UserGroup a vanilla, non-@Model
class (not stored in SwiftData), which solved the issue, making me believe it's a SwiftData related issue.
I've tried to minimize the example and recreate it in a fresh project so I don't believe this is due to any other files or messed up SwiftData configurations. Why are my changes not saving or updating the UI, and how can I fix it?
Upvotes: 1
Views: 731
Reputation: 363
As others have said, the User class also needs to be stored in SwiftData. Conforming to identifiable helps the views iterate over the data, and to store it back correctly. I would encourage you therefore to keep the id's separate from the UI since they should not change. I think what you are looking for is something like the code below, which as you will note, does not have a view model since code such as generatateNewID() is stored in the model.
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var userGroups: [UserGroup]
var body: some View {
NavigationSplitView {
List {
ForEach(userGroups) { userGroup in
NavigationLink {
UserView(users: userGroup.users)
} label: {
Text("\(userGroup.group)")
}
}
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
.toolbar {
ToolbarItem {
Button { print("add") }
label: { Label("Add Item", systemImage: "plus") }
}
}
} detail: {
Text("Select an item")
}
.onAppear {
/// Ensure the modelContext is created inMemory only, so no data is permanently saved
modelContext.insert(UserGroup(group: "Test", users: [User(),User()]))
}
}
}
struct UserView: View {
var users: [User]
var body: some View {
List {
ForEach(users) { user in
Text("\(user.modifiableId)")
.onTapGesture {
user.generateNewId()
}
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
@Model final class User: Identifiable {
var id: String
var modifiableId: String
init() {
let newID = UUID().uuidString
self.id = newID
self.modifiableId = newID
}
func generateNewId() {
modifiableId = UUID().uuidString
}
}
@Model final class UserGroup: Identifiable {
var id: String { group }
var group: String
var users: [User]
init(group: String, users: [User]) {
self.group = group
self.users = users
}
}
Upvotes: 1