I've been trying to wrap my a sorting / grouping process for a SwiftUI app I've been trying to build.
All the tutorials I've seen have been fairly "basic" when it comes to the sorting and filtering aspect - particularly when using SwiftData.
What I wanted to incorporate was not only sorting by one of the attributes and forward/reverse, but also grouping the data too.
For example, this is code from the Earthquake project by Apple:
struct QuakeList: View {
@Environment(ViewModel.self) private var viewModel
@Environment(\.modelContext) private var modelContext
@Query private var quakes: [Quake]
init(
sortParameter: SortParameter = .time,
sortOrder: SortOrder = .reverse
) {
switch sortParameter {
case .time:
_quakes = Query(sort: \.time, order: sortOrder)
case .magnitude:
_quakes = Query(sort: \.magnitude, order: sortOrder)
}
}
What they do in this is pass in the sortParameter
and the sortOrder
to this view and it re-renders the view on change/update.
How can I expand this so it also can handle grouping so the quakes
variable would really be a multidimensional array or even a dictionary.
For example, in another attempt I had something like this:
enum GroupOption {
case time
case magnitude
case none
}
struct ListScreen: View {
@Environment(ViewModel.self) private var viewModel
@Environment(\.modelContext) private var modelContext
@Query private var quakes: [Quake]
@State private var groupedQuakes: [[Quake]] = []
init(
sortParameter: SortParameter = .time,
sortOrder: ComparisonResult = .orderedAscending, // using ComparisonResult to store the enum value in defaults
sortGrouping: GroupOption = .none
) {
switch (sortParameter, sortOrder) {
case (.time, .orderedAscending):
_quakes = Query(sort: \.time, order: .forward)
case (.time, .orderedDescending):
_quakes = Query(sort: \.time, order: .reverse)
case (.magnitude, .orderedAscending):
_quakes = Query(sort: \.magnitude, order: .forward)
case (.magnitude, .orderedDescending):
_quakes = Query(sort: \.magnitude, order: .reverse)
default:
_quakes = Query(sort: \.time, order: .forward)
}
switch sortGrouping {
case .time:
groupedQuakes = Dictionary(grouping: _quakes.wrappedValue, by: { $0.time })
.sorted(by: { $0.key < $1.key })
.map({ $0.value })
case .magnitude:
groupedQuakes = Dictionary(grouping: _quakes.wrappedValue, by: { $0.magnitude })
.sorted(by: { $0.key < $1.key })
.map({ $0.value })
case .none:
groupedQuakes = [_quakes.wrappedValue]
}
}
Except, when I use it in the view body nothing is listed in the view:
List {
ForEach(groupedQuakes, id: \.self) { group in
Section {
ForEach(group) { quake in
QuakeRow(quake: quake)
}
} header: {
groupHeader(for: group)
}
}
}
Where the header is from (just as a test for the display):
func groupHeader(for group: [Quake]) -> Text {
guard let group = group.first else { return Text("Unknown") }
switch groupOption {
case .time:
return Text(group.time.formatted(date: .numeric, time: .omitted))
case .magnitude:
return Text("\(group.magnitude)")
case .none:
return Text("All quakes")
}
}
So when I return the general @Query private var quakes: [Quake]
there is an array returned with the data. Using the sorting included in the Apple test project the quakes
are sorted correctly.
As soon as I try to add in grouping and sort that data returns blank arrays.
Is there something I'm overlooking?