c reind
c reind

Reputation: 11

SwiftData Model Not Saving Changes

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:

@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

Answers (1)

Disco
Disco

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

Related Questions