Skip to content

Instantly share code, notes, and snippets.

@juanarzola
Last active June 5, 2024 18:21
Show Gist options
  • Save juanarzola/e868a278ebc7dbabb6a2482e5be575cc to your computer and use it in GitHub Desktop.
Save juanarzola/e868a278ebc7dbabb6a2482e5be575cc to your computer and use it in GitHub Desktop.
An attempt to reduce unnecessary computations for section building in the body of a View. Ultimately I had to modify it (See comments)
// slow version
struct MyFancyList: View {
@Query(FetchDescriptors.fancyListItems) var items
var sections: [ListSection] = []
var body: some View {
// it's a bad idea to build sections here, as that can execute for all sorts of updates to the view (there's more going on in this view in the original code)
SomethingRenderingSections(ListSection.sections(for: items, editMode: editMode, searchText: searchText)
.fullScreenCover(isPresented: $someFullScreenThingVisible) { SomethingThatCovers() }
}
}
// Faster version
struct MyFancyList: View {
@Query(FetchDescriptors.fancyListItems) var items
@State private var sections: [ListSection] = []
@State private var someFullScreenThingVisible = false
var body: some View {
SomethingRenderingSections(sections)
.fullScreenCover(isPresented: $someFullScreenThingVisible) { SomethingThatCovers() }
.sectionsBuilder(
for: $sections,
enabled: !someFullScreenThingVisible,
items: items,
editMode: editMode,
searchText: searchText)
}
}
...
...
...
// modifier that builds sections for a query
private struct SectionsBuilder: ViewModifier {
@Binding var sections: [ListSection]
let items: [Item] // items from the query
// variables that affect sections
let enabled: Bool
let editMode: EditMode
let searchText: String
// a subject that doesn't publish duplicates, used to signal list updates
@State private var listUpdated = DedupedSubject(Date.now)
func body(content: Content) -> some View {
let date = Date.now
content
.onChange(of: enabled) { oldEnabled, enabled in
guard oldEnabled != enabled else {
return
}
listUpdated.enabled = enabled
}
.onChange(of: items) {
listUpdated.send(date)
}
.onChange(of: editMode) {
listUpdated.send(date)
}
.onChange(of: searchText) {
listUpdated.send(date)
}
.onReceive(listUpdated.publisher) { _ in
rebuildSections()
}
}
@MainActor
private func rebuildSections() {
sections = ListSection.sections(
for: items,
editMode: editMode,
searchText: searchText
)
}
}
@juanarzola
Copy link
Author

juanarzola commented Jun 5, 2024

one flaw of this approach is that the sections are not a function of the list alone. (e.g., [1,2] may be sectioned as {a: [1], b: [2] } or {b: [1, 2]}, depending on the contents of the objects that 1, 2 point to).

Implementing equatable to consider the contents of the objects to solve this problem would be prohibitively expensive because it trips all the model object faults on each comparison.

So ultimately, instead of onChange of the list for all the inputs the list depends on, I had to observe private SwiftData notifications to know if rebuildSections had to be called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment