Search code examples
iosswiftcore-dataswiftui

Updated core data values not Reflecting in SwiftUI List


I have a tax Create/Edit View, and List View,View Model Class as Observable Object.

I update values to core data in viewModel class from an edit view, that updated values are not reflecting in list view which uses the same view Model class. The list view only updated when I go back to other screen and come to this list screen screen or if restart the app

Please kindly suggest

List View Code

struct TaxListView: View {
    
    @State var searchTerm:String = ""
    @State var isTaxSelected = false
    @State var seletedTax:TaxCodesList = TaxCodesList(businessId: "", taxID: "", taxName: "", taxRate: 0.0, taxtype: "", taxCodes: [TaxCodeList]())

    @ObservedObject var taxViewModel = TaxViewModel()
    @Environment(\.presentationMode) var mode
    
var body : some View {
    
    VStack{
        List{
            ForEach(taxViewModel.arrTaxCodes, id: \.taxID){ tax in
                TaxlistRow(tax: tax, selectedTax: $seletedTax, taxSelected: $isTaxSelected)
            }
        }
    }
    
}

View Model Class

class TaxViewModel:ObservableObject {
    
    @Published var arrTaxCodes:[TaxCodesList] = []
    @Published var arrTempTaxCodes:[TaxCodesList] = []
    
    let context = PersistenceController.shared.container.viewContext
    
    func getTaxCodes(){
        let fetchRequest : NSFetchRequest<TaxCodes> = TaxCodes.fetchRequest()
            fetchRequest.predicate = NSPredicate(format: "businessId = %@", Constants.businessID ?? "")
        
        do {
            let result = try context.fetch(fetchRequest)
            arrTaxCodes.removeAll()
            for data in result {
                arrTaxCodes.append(TaxCodesList(responseObj: data))
            }
            arrTempTaxCodes = arrTaxCodes
            
        } catch  {
            print(error.localizedDescription)
        }
    }
    
    //MARK: Update Tax
    func updateTax(tax:TaxCodesList, completion:@escaping (Bool,String) -> Void) {
        
        let fetchRequest : NSFetchRequest<TaxCodes> = TaxCodes.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "taxID = %@", tax.taxID)
        
        do {
            let result = try context.fetch(fetchRequest)
            
            guard let objectToUpdate = result.first else {
                return
            }
            
            var taxCodesObj = [[String:Any]()]
            ///// assigning values to taxCodesObj 
 
            objectToUpdate.setValue(tax.taxName ,forKey: "taxName")
            objectToUpdate.setValue(tax.taxRate, forKey: "taxRate")
            objectToUpdate.setValue(tax.taxType, forKey: "taxType")
            objectToUpdate.setValue(["taxCodes":taxCodesObj], forKey: "taxCodes")
            
            do {
                try context.save()
                //Context.refreshAllObjects()
                completion(true,"Tax Updated Successfully")
            } catch {
                completion(true,error.localizedDescription)
                print(error.localizedDescription)
            }

        } catch  {
            completion(true,"TaxId not Found")
            print(error.localizedDescription)
        } 
    }   
}

Edit View

struct CreateTaxView: View {

    ///// Some Variables
    
    @ObservedObject var taxViewModel:TaxViewModel
    
    @Environment(\.presentationMode) var mode
    
    func saveTax(){
        
        //// Codes to create tax Object
            taxViewModel.updateTax(tax: tax) { flag, msg in
                print(msg)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
                    self.mode.wrappedValue.dismiss()
                })
            }   
        } 
    
    var body: some View {
        Form {
            Section {
                ///// Some Views
            }
            .onAppear{
                taxViewModel.getTaxCodes(Context: viewContext)
                if isFromUpdate && isInitialLoad {
                 /////Assign values to variables
                }
            }
        }
        .navigationBarTitle(Text(navTitle))
        .navigationBarItems(trailing: Button{
            saveTax()
        } label:{
            Text("Save")
        } )
            
    }
    
    func delete(at offsets:IndexSet){
        taxCodes.remove(atOffsets: offsets)
    }

TaxCodesList Struct

struct TaxCodesList{
    
    var businessId:String
    var taxID:String
    var taxName:String
    var taxRate: Double
    var taxType:String
    var taxCodes:[TaxCodeList]
    
    ////Init code
    

    //Initialize from core data object
    init(responseObj:TaxCodes){
        self.businessId = responseObj.businessId ?? ""
        self.taxID = responseObj.taxID ?? ""
        self.taxName = responseObj.taxName ?? ""
        self.taxRate = responseObj.taxRate
        self.taxType = responseObj.taxType ?? ""
        
        let payload = responseObj.taxCodes?["taxCodes"] as? [[String:Any]] ?? [[String:Any]()]
        var tempArr = [TaxCodeList]()
        tempArr.removeAll()
        for data in payload {
            if !(data["taxId"] as? String ?? "").isEmpty {
                tempArr.append(TaxCodeList(responseObj: data))
            }
        }
        self.taxCodes = tempArr
    }
    
}

Solution

  • Call

    getTaxCodes()
    

    When you want to see an update.

    No part of your code is observing the persistent store. Your list doesn’t know when to get the new values

    You completely disconnect from CoreData with the View Model and struct. If you are doing this for abstraction and not unintentionally it’s fine but you have to somehow listen to the store so you can recreate everything.

    Using something in the View like onChange may or may not provide updates when needed. The store can change from many different directions that your abstraction layer will be unaware of.

    This may offer you an alternative for listening.

    If this is an unintentional pattern use the CoreData object directly by wrapping it with an

    @ObservedObject
    

    That way you can make changes and see them right away.

    The link above also has a more direct setup for CoreData and has a simple

    CoreDataPersistence
    

    Object that does all the heavy lifting for any object.