To my knowledge, I pass a Core Data Entity object to an @ObservedObject in a child-struct to force the UI to update when I change the Core Data object.
Example:
struct ParentView: View {
@FetchRequest(entity: CDUser.entity(), sortDescriptors: [])
var users: FetchedResults<CDUser>
var body: some View {
List {
ForEach(users, id: \.self) { item in
ChildView(item: item)
}
}
}
}
struct ChildView: View {
@Environment(\.managedObjectContext) private var moc
@ObservedObject var item: CDUser
var body: some View {
HStack {
Button(action: {
item.count = item.count + 1
do {
try moc.save()
} catch {
print(error)
}
}, label: {
Text(String(item.count))
}
)
}
}
}
The @ObservedObject will force the UI to show the increased item.count when the button is pressed.
While this works great, is there a way to not use the parent-child hierarchy to force the UI update by having both the @FetchRequest and @ObservedObject in the same struct?
Ideally, I'd only have one struct, which contains the button.
Below example will update the Code Data object, but the change will only be reflected in the UI when the View reappears.
Example:
struct MainView: View {
@Environment(\.managedObjectContext) private var moc
@FetchRequest(entity: CDUser.entity(), sortDescriptors: [])
var users: FetchedResults<CDUser>
// HOW DO I INTEGRATE THIS OBSERVED OBJECT TO REFLECT THE BUTTON ACTION IMMEDIATELY IN THE UI?
// @ObservedObject var users: [CDUser]
//
var body: some View {
List {
ForEach(users, id: \.self) { item in
Button(action: {
item.count = item.count + 1
do {
try moc.save()
} catch {
print(error)
}
}, label: {
Text(String(item.count))
}
)
}
}
}
}
Edit: Adding a more complex example, in which changing the Buttons to Child-Views does not cause the sections to update:
struct MainView: View {
@Environment(\.managedObjectContext) private var moc
@FetchRequest(entity: CDUser.entity(), sortDescriptors: [])
var users: FetchedResults<CDUser>
var body: some View {
List {
//Section of logged-in users
Section(header: Text("Logged in")) {
ForEach(users.filter({ $0.logged == true }), id: \.self) { user in
Button(action: {
user.logged = false
do {
try moc.save()
} catch {
print(error)
}
}, label: {
Text(String(user.firstName))
})
}
//Section of logged-out users
Section(header: Text("Logged out")) {
ForEach(users.filter({ $0.logged == false }), id: \.self) { user in
Button(action: {
user.logged = true
do {
try moc.save()
} catch {
print(error)
}
}, label: {
Text(String(user.firstName))
})
}
}
}
}
}
}
You can’t do that. Because an array is not an ObservableObject
. Make a CDUserButtonView
so you can observe the item in your loop
Your added sections don't update because you should be using a NSPredicate
vs a filter
that doesn’t have any built in observers, that is expected behavior, the FetchResults
only cause a redraw when the array as a whole changes. An array can’t be an observed object you can only observe the elements of the array individually.
Also, if you are targeting iOS 15+ you should look into SectionedFetchRequest
it will create the sections for you.
https://developer.apple.com/wwdc21/10017
it is at about minute 23:26