Hi,
This issue started with iOS 18, in iOS 17 it worked correctly. I think there was a change in SectionedFetchRequest
so maybe I missed it but it did work in iOS 17.
I have a List that uses SectionedFetchRequest
to show entries from CoreData. The setup is like this:
struct ManageBooksView: View {
@SectionedFetchRequest<Int16, MyBooks>(
sectionIdentifier: \.groupType,
sortDescriptors: [SortDescriptor(\.groupType), SortDescriptor(\.name)]
)
private var books: SectionedFetchResults<Int16, MyBooks>
var body: some View {
NavigationStack {
List {
ForEach(books) { section in
Section(header: Text(section.id)) {
ForEach(section) { book in
NavigationLink {
EditView(book: book)
} label: {
Text(book.name)
}
}
}
}
}
.listStyle(.insetGrouped)
}
}
}
struct EditView: View {
private var book: MyBooks
init(book: MyBooks) {
print("Init hit")
self.book = book
}
}
Test 1: So now when I change name
of the Book entity inside the EditView
and do save on the view context and go back, the custom EditView
is correctly hit again.
Test 2: If I do the same changes on a different attribute of the Book entity the custom init of EditView
is not hit and it is stuck with the initial result from SectionedFetchResults
.
I also noticed that if I remove SortDescriptor(\.name)
from the sortDescriptors
and do Test 1, it not longer works even for name
, so it looks like the only "observed" change is on the attributes inside sortDescriptors
.
Any suggestions will be helpful, thank you.
Thanks for your reminding. This post had slipped through the cracks...
Also thanks for your project, which does reproduce the issue. The issue seems to be that SwiftUI believes the change doesn't impact the second ForEach
in the following code snippet, and hence doesn't trigger the update.
List {
let _ = Self._printChanges() // This prints "ContentView: @56 changed."
ForEach(items) { section in
let _ = Self._printChanges() // This prints "ContentView: unchanged."
Section {
ForEach(section) { item in
NavigationLink {
EditView(book: item)
} label: {
VStack {
Text(item.timestamp!, formatter: itemFormatter)
Text("Text - \(item.text!)")
}
}
}
}
}
}
Interesting enough, if I wrap the NavigationLink
with a custom view, SwiftUI will change the way it "optimizes" the graph, and trigger the update as we would expect:
List {
ForEach(items) { section in
Section {
ForEach(section) { item in
ListItemView(item: item)
}
}
}
}
struct ListItemView: View {
@ObservedObject var item: Item
var body: some View {
NavigationLink {
EditView(book: item)
} label: {
VStack {
Text(item.timestamp!, formatter: itemFormatter)
Text("Text - \(item.text!)")
}
}
}
}
I don't have a lot of insight about how SwiftUI determines the "dirty" views, but wrapping the view hierarchy into a custom view so SwiftUI doesn't optimize away the update seems reasonable to me.
If you don't mind, please give it a try and share it that works in your real-world app.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.