Last active
June 5, 2024 18:21
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.