I've been trying to figure out what the bare minimum is required for HKWorkoutBuilder to create a workout that adds time the appleExerciseTime. I couldn't find the documentation for this. This is my code so far.
func createWorkoutSample(
expectedActiveEnergyData: [Double],
expectedExerciseMinutesData: [Double],
calendar: Calendar,
startDate: Date
) async throws -> [HKSample] {
var testData: [HKSample] = []
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .running
workoutConfiguration.locationType = .outdoor
let results = try await withThrowingTaskGroup(of: HKSample?.self) { group in
for (index) in 0..<expectedActiveEnergyData.count {
guard let date = calendar.date(byAdding: .day, value: index, to: startDate) else {
continue
}
group.addTask {
let builder = HKWorkoutBuilder(
healthStore: self.manager.healthStore,
configuration: workoutConfiguration,
device: .local()
)
let endDate = date.addingTimeInterval(expectedExerciseMinutesData[index] * 60)
try await builder.beginCollection(at: date)
let energyType = HKQuantityType.quantityType(
forIdentifier: .activeEnergyBurned
)!
let energyQuantity = HKQuantity(
unit: HKUnit.kilocalorie(),
doubleValue: expectedActiveEnergyData[index]
)
let energySample = HKQuantitySample(
type: energyType,
quantity: energyQuantity,
start: date,
end: endDate
)
return try await withCheckedThrowingContinuation { continuation in
builder.add([energySample]) { (success, error) in
if let error = error {
continuation.resume(throwing: error)
return
}
builder.endCollection(withEnd: endDate) { (success, error) in
if let error = error {
continuation.resume(throwing: error)
return
}
builder.finishWorkout { (workout, error) in
if let error = error {
continuation.resume(throwing: error)
return
}
continuation.resume(returning: workout)
}
}
}
}
}
}
for try await workout in group {
if let workout = workout {
testData.append(workout)
} else {
print("Skipping nil workout result.")
}
}
return testData
}
print("Total samples created: \(results.count)")
return results
}
When I query appleExerciseTime, there are no results. I've looked at the HKWorkoutBuilder documentation, and most of the information expands on adding samples related to the deprecated HKWorkout.
I’m able to see the exercises in the Fitness app along with their times, but the green ring doesn’t fill up.
The green ring not changing is probably because your workout doesn't have the .distanceWalkingRunning
sample. Here is the code that works for me:
let endDate: Date = .now
let startDate = endDate.addingTimeInterval(-30 * 60) // 30 minutes before.
try await workoutBuilder.beginCollection(at: startDate)
let energySample = HKQuantitySample(type: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
quantity: HKQuantity(unit: HKUnit.kilocalorie(), doubleValue: 300),
start: startDate, end: endDate)
let distanceSample = HKQuantitySample(type: HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!,
quantity: HKQuantity(unit: HKUnit.mile(), doubleValue: 3),
start: startDate, end: endDate)
try await workoutBuilder.addSamples([energySample, distanceSample])
try await workoutBuilder.endCollection(at: endDate)
if let workout = try await workoutBuilder.finishWorkout() {
print("\(workout)")
} else {
print("Failed to finish workout!")
}
To retrieve the .appleExerciseTime
samples, note that they are not associated with a workout, and so you should use the workout duration as the predicate:
let predicate = HKQuery.predicateForSamples(withStart: workout.startDate, end: workout.endDate)
let query = HKSampleQuery(sampleType: HKQuantityType(.appleExerciseTime),
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: []) { (_, results, error) in
results?.forEach { sample in
if let quantity = sample as? HKQuantitySample {
print(quantity)
}
}
}
healthStore.execute(query)
Best,
——
Ziqiao Chen
Worldwide Developer Relations.