Home > Net >  NSManagedObject relationship changes handling
NSManagedObject relationship changes handling

Time:09-29

I got problem in the view updates , the Core Data context is not aware about the relationships changes , so I'm not that expert in swift still beginner , I would like to know how to observe those changes or something to do , to see the particular changes in the models , ( I tried ENTITY.objectWillChange.send() not working for relationships) my current solution was to refresh the Head entity in context which is not a solution , cause the whole UI will change with ( context.refresh() ).

so here the example am working with , let say we have "Profile" with OneToMany "Wallet" and Wallet with OneToMany Balance , so the core data diagram will be :

   ,----------------.
   |  Profile       |
   |----------------|
   `----------------'
          | 1     
          | *      
     ,---------.  
     |Wallet   |  
     |---------|  
     `---------'  
          | 1     
          | *      
  ,-------------.  
  |Balance.     |  
  |-------------|  
  `-------------'  

swift sample , I tried to put evrything in one view , you can try it ,you need just to add the Models and PersistentContainer

import SwiftUI
import CoreData

struct TestView : View {
    @Environment(\.managedObjectContext)
    var context : NSManagedObjectContext
    
    
    @FetchRequest(sortDescriptors: [])
    var profiles : FetchedResults<Profile>
    
    var body: some View {
        NavigationView {
            List{
                ForEach(profiles){ profile in
                    NavigationLink( destination: {
                        profileView(profile : profile)
                    }){
                        if(profile.isDefault){
                            Text(profile.profileName).foregroundColor(Color.red).lineLimit(1)
                        }else{
                            Text(profile.profileName).foregroundColor(Color.blue).lineLimit(1)
                        }
                    }
                }
                .onDelete(perform: deleteProfile)
                .navigationTitle("Profiles")
                .toolbar{
                    Button(action: addProfile, label: {
                        Text("Add").foregroundColor(Color.black)
                    })
                }
            }
        }
    }
    
    private func deleteProfile(offset : IndexSet) {
        offset.map{
            profiles[$0]
        }.forEach { profile in
            context.delete(profile)
            saveContext()
        }
    }
    private func deleteWallet(profile : Profile , offset : IndexSet) {
        offset.map{
            profile.profileWalletsArray()[$0]
        }.forEach { wallet in
            context.delete(wallet)
            saveContext()
        }
    }
    private func deleteBalance(wallet : Wallet , offset : IndexSet) {
        offset.map{
            wallet.walletBalanceArray()[$0]
        }.forEach { balance in
            context.delete(balance)
            saveContext()
        }
    }
    
    @ViewBuilder
    func profileView(profile : Profile) -> some View {
        List{
            if profile.profileWalletsArray().isEmpty{
                Text("Empty")
            }else{
                ForEach(profile.profileWalletsArray()){ wallet in
                    NavigationLink(destination: {
                        walletView(wallet: wallet)
                    }){
                        Text("\(wallet.walletName)").lineLimit(1)
                    }
                }
                .onDelete(perform: { index in
                    return deleteWallet(profile: profile, offset: index)
                })
            }
        }
        .navigationTitle(Text(profile.profileName))
        .toolbar{
            Button(action: {
                addWallet(profile : profile)
            }, label: {
                Text("Add").foregroundColor(Color.black)
            })
        }
    }
    
    @ViewBuilder
    func walletView(wallet : Wallet) -> some View {
        if wallet.walletBalanceArray().isEmpty {
            Text("Empty Wallet").lineLimit(1)
        }else {
            VStack(alignment: .leading, spacing: 0){
                List{
                    ForEach(wallet.walletBalanceArray()) { balance in
                        VStack{
                            HStack{
                                Text("Asset : \(balance.balanceAsset)").lineLimit(1)
                                Text("Balance : \(balance.balanceAmount) ")
                            }
                        }
                    }
                    .onDelete(perform: { index in
                        return deleteBalance(wallet: wallet, offset: index)
                    })
                }
            }
            .navigationTitle(Text(wallet.walletName))
            .toolbar{
                Button(action: {
                    addBalance(wallet: wallet)
                }, label: {
                    Text("Add").foregroundColor(Color.black)
                })
            }
        }
    }
    
    func addWallet(profile : Profile) {
        withAnimation{
            debugPrint("Add Wallet")
            let wallet = Wallet(context: context)
            let balance1 = Balance(context: context)
            let balance2 = Balance(context: context)
            
            balance1.asset = "EUR"
            balance1.amount = Int64.random(in: 1..<100)
            balance2.asset = "USD"
            balance2.amount = Int64.random(in: 1..<100)
            //Wallet
            wallet.addToBalances(balance1)
            wallet.addToBalances(balance2)
            wallet.name = UUID().uuidString
            wallet.createdAt = Date()
            wallet.updatedAt = Date()
            profile.addToWallets(wallet)
            
            context.refresh(profile, mergeChanges: true)
            saveContext()
        }
    }
    
    func addBalance(wallet : Wallet) {
        
        withAnimation{
            wallet.profile?.objectWillChange.send()
            wallet.objectWillChange.send()
            debugPrint("Add Balance")
            let balance1 = Balance(context: context)
            let balance2 = Balance(context: context)
            
            balance1.asset = "EUR"
            balance1.amount = Int64.random(in: 1..<100)
            balance2.asset = "USD"
            balance2.amount = Int64.random(in: 1..<100)
            //Wallet
            wallet.addToBalances(balance1)
            wallet.addToBalances(balance2)
            wallet.name = UUID().uuidString
            wallet.createdAt = Date()
            wallet.updatedAt = Date()
            
            
            saveContext()
        }
    }
    
    func addProfile() {
        withAnimation{
            do {
                let oldProfile = try context.fetch(Profile.fetchRequest()).filter{
                    $0.isDefault
                }.first
                oldProfile?.isDefault = false
                debugPrint("old profile \(String(describing: oldProfile?.profileName))")
            }catch {
                fatalError(error.localizedDescription)
            }
            
            let profile = Profile(context: context)
            let wallet = Wallet(context: context)
            let balance1 = Balance(context: context)
            let balance2 = Balance(context: context)
            
            balance1.asset = "EUR"
            balance1.amount = Int64.random(in: 1..<100)
            balance2.asset = "USD"
            balance2.amount = Int64.random(in: 1..<100)
            
            //Wallet
            wallet.addToBalances(balance1)
            wallet.addToBalances(balance2)
            wallet.name = UUID().uuidString
            wallet.createdAt = Date()
            wallet.updatedAt = Date()
            
            //Profile
            profile.name = UUID().uuidString
            profile.isDefault = true
            profile.addToWallets(wallet)
            profile.createdAt = Date()
            profile.updatedAt = Date()
            
            saveContext()
        }
    }
    
    private func saveContext() {
        do {
            try context.save()
        }catch {
            let error = error as NSError
            fatalError(error.debugDescription)
        }
    }
}
extension Profile {
    public func profileWalletsArray() -> [Wallet] {
        return wallets?.allObjects as? [Wallet] ?? []
    }
    
    var profileName : String {
        return name ?? "unknown"
        
    }
}

extension Wallet {
    var walletName : String {
        return name ?? "unknown"
        
    }
    
    public func walletBalanceArray() -> [Balance] {
        return balances?.allObjects as? [Balance] ?? []
    }
}

extension Balance {
    var balanceAmount : Int {
        return Int(amount)
        
    }
    
    var balanceAsset : String {
        return asset ?? "unknown"
        
    }
}

============= ANSWER FIX =========

here the solution by "@lorem ipsum" that fixed the problem by creating separate view and adding @ObservedObject

TestView Update =>

var body: some View {
        NavigationView {
            List{
                ForEach(profiles){ profile in
                    NavigationLink( destination: {
                        ProfileView(profile : profile)
                    }){
                        if(profile.isDefault){
                            Text(profile.profileName).foregroundColor(Color.red).lineLimit(1)
                        }else{
                            Text(profile.profileName).foregroundColor(Color.blue).lineLimit(1)
                        }
                    }
                }
                .onDelete(perform: deleteProfile)
                .navigationTitle("Profiles")
                .toolbar{
                    Button(action: addProfile, label: {
                        Text("Add").foregroundColor(Color.black)
                    })
                }
            }
        }
    }

ProfileView.swift

import SwiftUI
import CoreData

struct ProfileView: View {
    
    @Environment(\.managedObjectContext)
    var context : NSManagedObjectContext
    
    @ObservedObject
    var profile : Profile
    
    var body: some View {
        List{
            if profile.profileWalletsArray().isEmpty{
                Text("Empty")
            }else{
                ForEach(profile.profileWalletsArray()){ wallet in
                    NavigationLink(destination: {
                        WalletView(wallet: wallet)
                    }){
                        Text("\(wallet.walletName)").lineLimit(1)
                    }
                }
                .onDelete(perform: { index in
                    return deleteWallet(profile: profile, offset: index)
                })
            }
        }
        .navigationTitle(Text(profile.profileName))
        .toolbar{
            Button(action: {
                addWallet(profile : profile)
            }, label: {
                Text("Add").foregroundColor(Color.black)
            })
        }
    }
    private func deleteWallet(profile : Profile , offset : IndexSet) {
        offset.map{
            profile.profileWalletsArray()[$0]
        }.forEach { wallet in
            context.delete(wallet)
            saveContext()
        }
    }
    
   
    func addWallet(profile : Profile) {
        withAnimation{
            debugPrint("Add Wallet")
            let wallet = Wallet(context: context)
            let balance1 = Balance(context: context)
            let balance2 = Balance(context: context)
            
            balance1.asset = "EUR"
            balance1.amount = Int64.random(in: 1..<100)
            balance2.asset = "USD"
            balance2.amount = Int64.random(in: 1..<100)
            //Wallet
            wallet.addToBalances(balance1)
            wallet.addToBalances(balance2)
            wallet.name = UUID().uuidString
            wallet.createdAt = Date()
            wallet.updatedAt = Date()
            profile.addToWallets(wallet)
            
            context.refresh(profile, mergeChanges: true)
            saveContext()
        }
    }
    
    private func saveContext() {
        do {
            try context.save()
        }catch {
            let error = error as NSError
            fatalError(error.debugDescription)
        }
    }
}

WalletView.swift

import SwiftUI
import CoreData

struct WalletView: View {
    @Environment(\.managedObjectContext)
    var context : NSManagedObjectContext
    
    @ObservedObject
    var wallet : Wallet
    
    var body: some View {
        if wallet.walletBalanceArray().isEmpty {
            Text("Empty Wallet").lineLimit(1)
        }else {
            VStack(alignment: .leading, spacing: 0){
                List{
                    ForEach(wallet.walletBalanceArray()) { balance in
                        BalanceView(balance: balance)
                    }
                    .onDelete(perform: { index in
                        return deleteBalance(wallet: wallet, offset: index)
                    })
                }
            }
            .navigationTitle(Text(wallet.profile?.profileName ?? "unknown"))
            .toolbar{
                Button(action: {
                    addBalance(wallet: wallet)
                }, label: {
                    Text("Add").foregroundColor(Color.black)
                })
            }
        }
    }
    private func deleteBalance(wallet : Wallet , offset : IndexSet) {
        offset.map{
            wallet.walletBalanceArray()[$0]
        }.forEach { balance in
            context.delete(balance)
            saveContext()
        }
    }
    
    func addBalance(wallet : Wallet) {
        
        withAnimation{
            wallet.profile?.objectWillChange.send()
            wallet.objectWillChange.send()
            debugPrint("Add Balance")
            let balance1 = Balance(context: context)
            let balance2 = Balance(context: context)
            
            balance1.asset = "EUR"
            balance1.amount = Int64.random(in: 1..<100)
            balance2.asset = "USD"
            balance2.amount = Int64.random(in: 1..<100)
            //Wallet
            wallet.addToBalances(balance1)
            wallet.addToBalances(balance2)
            wallet.name = UUID().uuidString
            wallet.createdAt = Date()
            wallet.updatedAt = Date()
            
            
            saveContext()
        }
    }
    
    private func saveContext() {
        do {
            try context.save()
        }catch {
            let error = error as NSError
            fatalError(error.debugDescription)
        }
    }
}

BalanceView.swift

import SwiftUI
import CoreData

struct BalanceView: View {
    
    @Environment(\.managedObjectContext)
    var context : NSManagedObjectContext
    
    @ObservedObject
    var balance : Balance
    
    var body: some View {
        VStack{
            HStack{
                Text("Asset : \(balance.balanceAsset)").lineLimit(1)
                Text("Balance : \(balance.balanceAmount) ")
            }
        }
    }
    
    private func deleteBalance(wallet : Wallet , offset : IndexSet) {
        offset.map{
            wallet.walletBalanceArray()[$0]
        }.forEach { balance in
            context.delete(balance)
            saveContext()
        }
    }
    
    func addBalance(wallet : Wallet) {
        
        withAnimation{
            wallet.profile?.objectWillChange.send()
            wallet.objectWillChange.send()
            debugPrint("Add Balance")
            let balance1 = Balance(context: context)
            let balance2 = Balance(context: context)
            
            balance1.asset = "EUR"
            balance1.amount = Int64.random(in: 1..<100)
            balance2.asset = "USD"
            balance2.amount = Int64.random(in: 1..<100)
            //Wallet
            wallet.addToBalances(balance1)
            wallet.addToBalances(balance2)
            wallet.name = UUID().uuidString
            wallet.createdAt = Date()
            wallet.updatedAt = Date()
            
            
            saveContext()
        }
    }
    
    private func saveContext() {
        do {
            try context.save()
        }catch {
            let error = error as NSError
            fatalError(error.debugDescription)
        }
    }
}

hope this example could help others :P

CodePudding user response:

All CoreData objects are ObservableObjects if you want to see changes you have to wrap them in a @ObservedObject.

To do this make subviews that take profile, wallet, and balance as a parameter vs having all those variables with your subviews.

  • Related