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
}
}
2
Answers
I missed the code also for
TaxCodeList
(without the "s"), but I think I found the problem: in your view model you are reading from CoreData and passing the values of yourTaxCodes
entity to astruct
calledTaxCodesList
(with the "s"). Then, your view model is publishing the changes from an array of structs.Your views are reading data that comes from the array of the
TaxCodesList
struct, not from the Core Data object. Structs cannot publish changes, so your view cannot update when you change Core Data – the changes are not reaching the view.Your code is a bit long for me to give you the solution, but the first step is to have your views reading form an
ObservableObject
, which can be either a class or the Core Data object directly. Depending on what you want to achieve, you could turn yourTaxCodesList
into a class, or – my personal preference – turnTaxCodesList
into a Core Data entity and create a relationship between this new entity andTaxCodes
.Call
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
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
Object that does all the heavy lifting for any object.