I've been trying to wrap my head around a sorting and 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 (some items removed for brevity):
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, I was trying to do this to perform the rendering:
enum GroupOption { // New grouping for Section
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]] = [] // New multidimensional array
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 it is empty. So switching from the // 1
to // 2
makes the array of data return empty.
// 1
List(quakes, selection: $selectedId) { quake in
QuakeRow(quake: quake)
}
// 2
List {
ForEach(groupedQuakes, id: \.self) { group in
Section {
ForEach(group) { quake in
QuakeRow(quake: quake)
}
} header: {
groupHeader(for: group)
}
}
}
// ...
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?
Answers
It seems like you're trying to implement sorting and grouping functionality in your SwiftUI app using CoreData and SwiftUI's @Query
property wrapper. The approach you're taking looks reasonable, but there might be a few things causing the grouped data to return empty.
Here are a couple of things to check and adjust in your implementation:
-
Ensure Data is Loaded: Make sure that your CoreData fetch request is returning data before you try to group it. You can do this by printing the
quakes
array in yourinit
method to see if it contains any data. -
Check Grouping Logic: Verify that the grouping logic is correct. In your
init
method, you are sorting thequakes
array based onsortParameter
andsortOrder
, and then grouping it based onsortGrouping
. Ensure that thesortGrouping
value is set correctly and that the grouping logic is functioning as expected. -
Debug Grouped Data: Print the
groupedQuakes
array to see if it contains any data after grouping. This will help you determine if the grouping logic is working as intended. -
Inspect Data Structure: Ensure that the structure of your CoreData entity (
Quake
) matches the grouping logic you're using. For example, if you're grouping bytime
, make sure that thetime
attribute of theQuake
entity is correctly populated and formatted. -
Verify View Rendering: Check that your view is rendering correctly by using
Text
elements or other UI components to display information from thegroupedQuakes
array. This will help you identify if the issue lies in the rendering logic or the data itself.
By carefully checking each of these points and debugging your code, you should be able to identify the cause of the empty grouped data and resolve the issue. Additionally, consider using breakpoints or print statements throughout your code to inspect variable values and execution flow during runtime, which can help pinpoint the problem more accurately.