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 ObservableObject
s 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.