Replies: 2 comments
-
Beta Was this translation helpful? Give feedback.
0 replies
-
|
By adding an extension on // MARK: - QueryCursor + sectioned
extension QueryCursor {
/// Groups pre-sorted cursor rows into sections in a single O(n) pass.
func sectioned<SectionID: Hashable & Sendable>(
by sectionedBy: (Element) -> SectionID
) throws -> [FetchSection<SectionID, Element>] where Element: Sendable {
var sections: [FetchSection<SectionID, Element>] = []
var currentID: SectionID?
while let row = try next() {
let id = sectionedBy(row)
if id == currentID {
sections[sections.count - 1].items.append(row)
} else {
sections.append(FetchSection(id: id, items: [row]))
currentID = id
}
}
return sections
}
}@Selection
nonisolated struct BookWithAuthor: Identifiable {
var id: Book.ID
var title: String
var authorName: String
}
struct SectionedByAuthorRequest: FetchKeyRequest {
func fetch(_ db: Database) throws -> [FetchSection<String, BookWithAuthor>] {
try Book
.join(Author.all) { $0.authorID.eq($1.id) }
.order { _, authors in authors.name.asc() }
.order { books, _ in books.title.asc() }
.select { books, authors in
BookWithAuthor.Columns(
id: books.id,
title: books.title,
authorName: authors.name
)
}
.fetchCursor(db)
.sectioned(by: \.authorName)
}
}
struct SectionedByAuthorView: View {
@Fetch(SectionedByAuthorRequest(), animation: .default)
var sections: [FetchSection<String, BookWithAuthor>] = []
// MARK: - Body
var body: some View {
List {
ForEach(sections) { section in
Section(section.id) {
ForEach(section.items) { row in
Text(row.title)
}
}
}
}
.navigationTitle("By Author")
}
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

Uh oh!
There was an error while loading. Please reload this page.
-
Why this is needed
Today, sectioned list UIs usually require:
@FetchAllfor flat rowsDictionary(grouping:))For common section dimensions like
isPinned, you can also do this with two separate fetches(e.g. pinned + unpinned) or a custom
FetchKeyRequestthat manually assembles sections.Both approaches works, but it's repetitive for a very common UI pattern.
Core Data baseline
Core Data provides sectioning with
@SectionedFetchRequest:What this could look like in SQLiteData
Keep the familiar
sortDescriptorsAPI and use descriptor order to drive both section and item ordering.Option A: New wrapper (
@FetchSectioned)Option B: Extend
@FetchAllProposed section type
Small example: pinned section first
Small example: earthquakes sectioned by date
Section transforms
The optional
sectionTransformclosure converts a raw column value into a coarser grouping key before sections are formed. In the earthquake example above, each row has a precisetimetimestamp, but we want to group rows by day, not by individual timestamp. The transformCalendar.current.startOfDay(for:)buckets every timestamp into its calendar day so all quakes from the same day land in one section.This is useful whenever the natural column value is too granular for sectioning — dates into days/months, prices into ranges, scores into letter grades, etc. Core Data's
@SectionedFetchRequestdoes not offer this; it requires adding a computed property on the model instead.Alternative: SQL-level bucketing via
.TableColumnsextensions. Instead of transforming in Swift, the same bucketing could be expressed as a computed column expression using the library's existing extension pattern. For example, the codebase already extends.TableColumnswith reusable query expressions likeisActiveandisArchived:This would let the database handle bucketing before rows reach Swift, and the computed column could be used directly as the
sectionIdentifier:Both approaches could coexist —
sectionTransformfor simple client-side cases where a Swift closure is convenient, and.TableColumnsextensions for performance-sensitive or reusable bucketing that pushes the work to SQLite.Behavior expectations
load(...)with a new query/filter/sort.Beta Was this translation helpful? Give feedback.
All reactions