NSManagedObject 关系变化处理
NSManagedObject relationship changes handling
我在视图更新中遇到问题,核心数据上下文不知道关系变化,所以我不是专家 swift 还是初学者,我想知道如何观察那些更改或要做的事情,以查看模型中的特定更改,(我尝试 ENTITY.objectWillChange.send() 不适用于关系)
我当前的解决方案是在上下文中刷新 Head 实体,这不是解决方案,因为整个 UI 将随着 ( context.refresh()).
而改变
所以这里的例子正在使用,假设我们有“个人资料”和OneToMany“钱包" 和带有 OneToMany Balance 的钱包,所以核心数据图将是:
,----------------.
| Profile |
|----------------|
`----------------'
| 1
| *
,---------.
|Wallet |
|---------|
`---------'
| 1
| *
,-------------.
|Balance. |
|-------------|
`-------------'
swift 示例,我尝试将所有内容放在一个视图中,你可以试试,你只需要添加模型和 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[[=11=]]
}.forEach { profile in
context.delete(profile)
saveContext()
}
}
private func deleteWallet(profile : Profile , offset : IndexSet) {
offset.map{
profile.profileWalletsArray()[[=11=]]
}.forEach { wallet in
context.delete(wallet)
saveContext()
}
}
private func deleteBalance(wallet : Wallet , offset : IndexSet) {
offset.map{
wallet.walletBalanceArray()[[=11=]]
}.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{
[=11=].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"
}
}
============= 答案 + 修复 =========
这里是“@lorem ipsum”的解决方案,它通过创建单独的视图并添加 @ObservedObject
解决了问题
测试视图更新=>
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()[[=13=]]
}.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()[[=14=]]
}.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()[[=15=]]
}.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)
}
}
}
希望这个例子对其他人有帮助:P
所有 CoreData 对象都是 ObservableObject
,如果您想查看更改,您必须将它们包装在 @ObservedObject
中。
为此,创建将 profile
、wallet
和 balance
作为参数的子视图,而不是将所有这些变量与您的子视图一起使用。
我在视图更新中遇到问题,核心数据上下文不知道关系变化,所以我不是专家 swift 还是初学者,我想知道如何观察那些更改或要做的事情,以查看模型中的特定更改,(我尝试 ENTITY.objectWillChange.send() 不适用于关系) 我当前的解决方案是在上下文中刷新 Head 实体,这不是解决方案,因为整个 UI 将随着 ( context.refresh()).
而改变所以这里的例子正在使用,假设我们有“个人资料”和OneToMany“钱包" 和带有 OneToMany Balance 的钱包,所以核心数据图将是:
,----------------.
| Profile |
|----------------|
`----------------'
| 1
| *
,---------.
|Wallet |
|---------|
`---------'
| 1
| *
,-------------.
|Balance. |
|-------------|
`-------------'
swift 示例,我尝试将所有内容放在一个视图中,你可以试试,你只需要添加模型和 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[[=11=]]
}.forEach { profile in
context.delete(profile)
saveContext()
}
}
private func deleteWallet(profile : Profile , offset : IndexSet) {
offset.map{
profile.profileWalletsArray()[[=11=]]
}.forEach { wallet in
context.delete(wallet)
saveContext()
}
}
private func deleteBalance(wallet : Wallet , offset : IndexSet) {
offset.map{
wallet.walletBalanceArray()[[=11=]]
}.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{
[=11=].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"
}
}
============= 答案 + 修复 =========
这里是“@lorem ipsum”的解决方案,它通过创建单独的视图并添加 @ObservedObject
测试视图更新=>
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()[[=13=]]
}.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()[[=14=]]
}.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()[[=15=]]
}.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)
}
}
}
希望这个例子对其他人有帮助:P
所有 CoreData 对象都是 ObservableObject
,如果您想查看更改,您必须将它们包装在 @ObservedObject
中。
为此,创建将 profile
、wallet
和 balance
作为参数的子视图,而不是将所有这些变量与您的子视图一起使用。