Search code examples
swiftswiftuicore-datapopup

How do I instantly load core data in a view after closing my popup?


The point of this app is to use core data to permanently add types of fruit to a list. I have two views: ContentView and SecondScreen. SecondScreen is a pop-up sheet. When I input a fruit and press 'save' in SecondScreen, I want to immediately update the list in ContentView to reflect the type of fruit that has just been added to core data as well as the other fruits which have previously been added to core data. My problem is that when I hit the 'save' button in SecondScreen, the new fruit is not immediately added to the list in ContentView. Instead, I have to restart the app to see the new fruit in the list.

Here is the class for my core data:

class CoreDataViewModel: ObservableObject {
    
    let container: NSPersistentContainer
    @Published var savedEntities: [FruitEntity] = []
    
    init() {
        container = NSPersistentContainer(name: "FruitsContainer")
        container.loadPersistentStores { (description, error) in
            if let error = error {
                print("Error with coreData. \(error)")
            }
        }
        fetchFruits()
    }
    
    func fetchFruits() {
        let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")
        
        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch let error {
            print("Error fetching. \(error)")
        }
        
    }
    
    func addFruit(text: String) {
        let newFruit = FruitEntity(context: container.viewContext)
        newFruit.name = text
        saveData()
    }
    
    func saveData() {
        do {
            try container.viewContext.save()
            fetchFruits()
        } catch let error {
            print("Error saving. \(error)")
        }
    }
}

Here is my ContentView struct:

struct ContentView: View {
    
    //sheet variable
    @State var showSheet: Bool = false
    
    @StateObject var vm = CoreDataViewModel()
    
    @State var refresh: Bool
    
    
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
               
                Button(action: {
                    showSheet.toggle()
                }, label: {
                    Text("Add Fruit")
                })
                
                List {
                    ForEach(vm.savedEntities) { entity in
                        Text(entity.name ?? "NO NAME")
                    }
                }
            }
            .navigationTitle("Fruits")
            .sheet(isPresented: $showSheet, content: {
                SecondScreen(refresh: $refresh)
            })
        }
    }
}

Here is my SecondScreen struct:

struct SecondScreen: View {
    
    @Binding var refresh: Bool
    
    @Environment(\.presentationMode) var presentationMode
    
    @StateObject var vm = CoreDataViewModel()
    
    @State var textFieldText: String = ""
    
    var body: some View {
        TextField("Add fruit here...", text: $textFieldText)
            .font(.headline)
            .padding(.horizontal)
        
        Button(action: {
            guard !textFieldText.isEmpty else { return }
            vm.addFruit(text: textFieldText)
            textFieldText = ""
            presentationMode.wrappedValue.dismiss()
            refresh.toggle()
        }, label: {
            Text("Save")
        })
    }
}

To try and solve this issue, I've created a @State boolean variable called 'refresh' in ContentView and bound it with the 'refresh' variable in SecondScreen. This variable is toggled when the user hits the 'save' button on SecondScreen, and I was thinking that maybe this would change the @State variable in ContentView and trigger ContentView to reload, but it doesn't work.


Solution

  • In your second screen , change

    @StateObject var vm = CoreDataViewModel()
    

    to

    @ObservedObject var vm: CoreDataViewModel
    

    then provide for the instances that compiler will ask for

    hope it helps