UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: updating Core Data with function, list view not updating after the change

Forums > SwiftUI

I've seen several "list not updating" posts on this site. It feels like I've read through all of them and tried to implement the solutions to my code without success. So I figured I'd just ask for myself. Warning: I am very new to this, so I may just be doing something silly.

I'm using Core Data with a list of tools Here is my MainViewModel:

class CoreDataViewModel: ObservableObject {

    let container: NSPersistentContainer
    @Published var savedEntities: [ToolEntity] = []

    @Published var added2List: [ToolEntity] = []

    @Published var savedLoans: [LoanEntity] = []

    @Published var searchText: String = ""

    init() {
        container = NSPersistentContainer(name: "CoreDataContainer")
        container.loadPersistentStores { (description, error) in
            if let error = error {
                print("ERROR LOADING CORE DATA. \(error)")
            }
        }
        fetchTools()
        updatelist()
        fetchLoans()

    }

    func fetchTools() {
        let request = NSFetchRequest<ToolEntity>(entityName: "ToolEntity")

        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }

    }

    func fetchLoans() {
        let request = NSFetchRequest<LoanEntity>(entityName: "LoanEntity")

        do {
            savedLoans = try container.viewContext.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }
    }

    func addTool(nametext: String, sntext: String, notestext: String) {
        let newTool = ToolEntity(context: container.viewContext)
        newTool.name = nametext
        newTool.sn = sntext
        newTool.notes = notestext
        newTool.status = "In the toolbox"
        newTool.incart = false
        saveData()
    }

    func deleteTool(indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        let entity = savedEntities[index]
        container.viewContext.delete(entity)
        saveData()
    }

    func updateTool(entity: ToolEntity) {
        let currentName = entity.name ?? ""
        let newName = currentName + "!"
        entity.name = newName
        saveData()
    }

    func saveData() {
        do {
            try container.viewContext.save()
            fetchTools()
        } catch let error {
            print("Error saving. \(error)")
        }

    }

    func add2list(entity: ToolEntity) {
        entity.incart = true
        saveData()
    }

    func updatelist() {
        added2List = savedEntities.filter({ $0.incart })
    }

    func removeFromList(entity: ToolEntity) {
        entity.incart = false
        saveData()
    }

}

In my loan tool view, Ive got a foreach setup to only display tools that have the incart bool as true:

struct loanTool: View {

    @ObservedObject var vm = CoreDataViewModel()

    @State var showSheet: Bool = false
    @State var personName = ""

    var body: some View {

        NavigationView {
            VStack {

                HStack {
                    Text("tool list")
                    Button {
                        showSheet.toggle()
                    } label: {
                        Text("Add Tools")
                    }

                }

                List {
                    ForEach(vm.added2List) { entity in

                            HStack {

                                Text(entity.name!)
                                    .font(.headline)
                                    .foregroundColor(.red)
                                Spacer()
                                Button {
                                    vm.removeFromList(entity: entity)
                                } label: {
                                    Image(systemName: "xmark")
                                }
                            }

                        }
                            .listStyle(PlainListStyle())
                            .onAppear {

                            }

                        Spacer()
                }
                }
            .navigationTitle("Loan a tool")
            .sheet(isPresented: $showSheet, onDismiss: {
                vm.updatelist()
            }) {
                add2loan()
            }
        }

    }
}

I have a sheet view that I use to select the tool that I want to add to the cart, which takes place in a function add2List in the MainView Model. When the sheet dismisses I want the view to update and show the tool I've selected in the list. It doesn't. If I reload the sceeen it will show, so I know the function is happening, but it's not reloading the view. same problem removing from the list.

here is the add2loan sheet view:

struct add2loan: View {

    @Environment(\.presentationMode) var presenationMode

    @StateObject var vm = CoreDataViewModel()

    var body: some View {

        VStack {

            HStack {

                Spacer()

                Button(action: {
                    presenationMode.wrappedValue.dismiss()
                }, label: {
                    Image(systemName: "xmark")
                        .font(.title)
                        .padding(20)
                })

            }

            NavigationView {
                VStack {

                    SearchBar(search4Tool: $vm.searchText)

                    List {
                        ForEach(vm.savedEntities.filter({
                            vm.searchText.isEmpty ? true :
                            $0.name!.localizedCaseInsensitiveContains(vm.searchText)
                        })) { entity in

                            HStack {

                                Button {
                                    vm.add2list(entity: entity)
                                    presenationMode.wrappedValue.dismiss()
                                } label: {
                                    Text(entity.name ?? "NO NAME")
                                }
                            }
                        }

                    }
                    .listStyle(PlainListStyle())

                    Spacer()
                }

            }
        }
    }
}

The strange thing is I'm doing something very similar in a different set of views, when tools are added to Core Data without issue. Like I said in the beginning, I'm sure it's something simple I have yet to learn in my journey.

Any help or guidance would be much appreciated.

I've seen some really cool code reading through some forums, but none of those solutions worked for me.

Thanks again, Luke

2      

2      

i took a glance before i left the house this morning, but didnt have a lot of time. I'll take another look later. Is there a specific part that you think I should pay attention to?

Sorry if thats a silly question and its obvious, I'm still learning.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

@lukepilk - change @ObservedObject var vm = CoreDataViewModel() , to @ObservedObejct var vm: CoreDataViewModel in struct loanTool , provide for preview values ,

also move @StateObject var vm = CoreDataViewModel() to the file with @main method in your app, use @ObservedObejct var vm: CoreDataViewModel in add2Loan also

2      

ok, did that stuff, also needed to pass vm: CoreDataViewModel() around a bit within the views.

still no dice. takes a screen reload to see the tool added to or removed from the list view on loanTool

2      

if you can post the project , trying it out with the coredata model will be better, if your files are some thing you are comfortable sharing with ...

2      

It sounds like the problem that you are experiencing is that the data model is not being updated on the @MainActor thread.

So, when you add a new item to your data model, your View senses that a change has happened, and reloads it's body. But the View finishes reloading its body before the updates to the data model have finished.

Then, by the time the data finishes making its changes, the View thinks that it has already reloaded its body since the last change, and doesn't need to do it again.

That would explain why the data is there when you reload the view, but it doesn't automatically show if you don't reload the view.

I'm not sure exactly what the fix would be, because I am not very confident in my threading/concurrency skills just yet. But, I would suggest looking into that type of thing.

It may be as simple as making your ViewModel an @MainActor class, or you may have to make your functions that add items run on the mainActor using tasks, or you may just simply need to add an objectWillChange() somewhere before the changes occur. I'm really not sure.

2      

@AmitShrivastava,

I'm doing this to learn so it's ok to share my project.

it's here: https://github.com/lukepilk/ToolKeeper

I've done something now trying to fix the issue that I can no longer update the list with a screen reload. ugh!

@Fly0strich,

I don't know anything about threading, I'll dig into that. thanks for the hint.

2      

@lukepilk you did a great job , some changes now

1) File ToolKeeperApp - remove @StateObject var vm = CoreDataViewModel()

2) ContentView - add @StateObject var vm = CoreDataViewModel() , then 2 error will pop up, make Toolbox Toolbox(vm: vm) and this part just add vm like so loanTool(vm: vm)

3) go to Toolbox file remove the @StateObject line and instead add - @ObservedObject var vm: CoreDataViewModel , then in its preview make Toolbox(vm: CoreDataViewModel())

4) addTool - again remove @StateObject and add @ObservedObject var vm: CoreDataViewModel , then in its preview addTool(vm: CoreDataViewModel())

5) loanTool - @ObservedObject var vm: CoreDataViewModel, remove @ObservedObject from preview , add loanTool(vm: CoreDataViewModel())

6) add2load - change to @ObservedObject var vm: CoreDataViewModel , in preview add2loan(vm: CoreDataViewModel())

Now your code will work, however when you restart , the data is not being saved, that is to do with how you are saving the data, you can try that on your own i guess

Hope it helps , good luck

2      

@AmitShrivastava,

Thank you so much for taking the time to help me learn through this experience.

I did the things you suggested and still cannot get the loan tool screen to update with the tool that I have selected when add2loan sheet closes.

There is a very real possibility that I did not do it correctly, but I pushed it up to the github.

I'm going to keep working at it. it's helping me learn regardless. TY

2      

@lukepilk - you just have to make sure you use @ObservedObject var vm: CoreDataViewModel , remove any other usage of @ObservedObject var vm = CoreDataViewModel() , this actually resets the value to new instance, we do not want this, also make sure to remove all instances of @StateObject var vm = CoreDataViewModel() , it must be used only once, in contentView() , rest of your code is good ... just make sure you save data properly

2      

@Bnerd  

I downloaded your project and I made the next two changes and now it works as I guess you intented. Change 01 - In your loanTool View

.sheet(isPresented: $showSheet, onDismiss: {
                vm.saveData()
            }) {
                add2loan(vm: vm) //Change 01 ****
            }

Change 02 - In your loanTool View . Note: Your added2List Array was not saved..before. As you can see I am not using the added2List so you can review your code and remove as required.

ForEach(vm.savedEntities) { entity in //Change 02
                        if entity.incart {
                            HStack {

                                Text(entity.name!)
                                    .font(.headline)
                                    .foregroundColor(.red)

                                Spacer()

                                Button {
                                    vm.removeFromList(entity: entity)
                                } label: {
                                    Image(systemName: "xmark")
                                }
                            }
                        }
                        }

2      

that did the trick. Thank you to all who have been working with me and helping me figure this out.

I'm going to keep building on this thing as a learning experiment. I'll keep the code public so if anyone wants to check it out in the future.

woot woot! coding is so fun!

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.