I am creating a workout tracking app and trying to plot the largest weight value on a day over time for a given exercise. The entity "ExerciseSet" contains attributes dateCompleted and weight. There are multiple ExerciseSets saved on the same date, so I want to sort the ExerciseSets descending by weight for each dateCompleted and return the first element (being the max) for each date so that there is one entry (the max) for each date. How can I modify the fetch request to do this? Any suggestions would be greatly appreciated. As of now, there are multiple points for each date when I want just the max weight. My code is here:
import SwiftUI
import Charts
struct ExerciseDetailView: View {
@FetchRequest var exercisesets: FetchedResults<ExerciseSet>
var exercise: String
init(exercise: String) {
self.exercise = exercise
self._exercisesets = FetchRequest(
entity: ExerciseSet.entity(),
sortDescriptors: [NSSortDescriptor(key: "weight", ascending: false)],
predicate: NSPredicate(format: "exercisename == %@", exercise as any CVarArg)
)}
var body: some View {
NavigationView {
ScrollView {
if #available(iOS 16.0, *) {
Chart(exercisesets) { e in
LineMark(x: .value("Date", e.dateCompleted.startOfDay),
y: .value("Reps", e.reps)
)}
}
else {
Text("Oops")
// Fallback on earlier versions
}
}
}
}
}
AFAIK you can't solve this with the @FetchRequest
alone, but you can use a computed var that transforms the fetched exercisesets
into a new array that only holds the max weight entries:
The technique is commonly used for uniqueValues functions on arrays or other sequences. The seenDates
set keeps track of dates we already have seen. It's a set
and not an array, because a set has a nice insert
behavior that we can use: if we insert into the set, we get back (inserted: Bool, memberAfterInsert: Set.Element)
. So inserted
is true if the element did not exist before, and false if it did.
So the .filter
line in the computed var does the magic:
it tries to insert the current date into seenDates
and as the inserted` result is only true for the first time this date was added, those are the elements we are looking for.
var exerciseMaxWeightArray: [ExerciseSet] {
var seenDates: Set<Date> = []
return exercisesets
.sorted { $0.weight > $1.weight } // you can skip this line if already sorted by weight in the @fetchrequest
.filter { seenDates.insert($0.dateCompleted.startOfDay).inserted }
.sorted { $0.dateCompleted < $1.dateCompleted }
}