Hello,
In my application, I need to obtain precise workout segment data from HKWorkout in order to calculate per-kilometer metrics such as heart rate and pace.
My current approach is:
1.Use HKWorkout to fetch the associated HKWorkoutEvents.
2.Take the end time of one event as the start time of the next event to derive per-kilometer segment ranges.
The issue I’m facing:
•If a user sets Apple Watch to notify every 5 kilometers, then at 5 km, 10 km, 15 km, etc., I see overlapping event times.
•From the HKWorkoutEvents data alone, I cannot distinguish between events that represent “per-kilometer splits” and those that represent “5-kilometer notifications.”
•As a result, my per-kilometer heart rate and pace calculations can be inaccurate.
My question is:
Is there a recommended way to reliably differentiate per-kilometer splits from custom distance notifications and ensure accurate segment data retrieval?
For example, should I instead reconstruct segments using HKWorkoutRoute and distance samples, rather than relying on HKWorkoutEvents?
STEPS TO REPRODUCE
1.On Apple Watch, start an Outdoor Run using the Workout app.
2.In workout notifications, set distance alerts to every 5 kilometers.
3.During the run, when reaching 5 km, 10 km, 15 km, etc., the watch triggers notifications.
4.Query the corresponding HKWorkout from HealthKit and inspect its HKWorkoutEvents.
5.Notice that some event start times are duplicated, and it is unclear which events represent “per-kilometer splits” and which represent “5-kilometer notifications.”
Expected Result:
Be able to differentiate between per-kilometer splits and custom distance alerts, so that heart rate and pace per kilometer can be calculated accurately.
Actual Result:
The HKWorkoutEvents data contains duplicated event times without a way to distinguish event types, leading to inaccurate per-kilometer statistics.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
I am a develop beginner. Recently, my App used SwiftData's MigraitonPlan, which caused it to crash when I opened it for the first time after updating in TestFlight or the store. After clicking it a second time, I could enter the App normally, and I could confirm that all the models were the latest versions.However, when I tested it in Xcode, everything was normal without any errors.
Here is my MigrationPlan code:
import Foundation
import SwiftData
enum MeMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[MeSchemaV1.self, MeSchemaV2.self, MeSchemaV3.self, MeSchemaV4.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3, migrateV3toV4]
}
//migrateV1toV2, because the type of a data field in MeSchemaV1.TodayRingData.self was modified, the historical data was deleted during the migration, and the migration work was successfully completed.
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: MeSchemaV1.self,
toVersion: MeSchemaV2.self,
willMigrate: { context in
try context.delete(model: MeSchemaV1.TodayRingData.self)
},
didMigrate: nil
)
//migrateV2toV3, because a new Model was added, it would crash at startup when TF and the official version were updated, so I tried to delete the historical data during migration, but the problem still exists.
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: MeSchemaV2.self,
toVersion: MeSchemaV3.self,
willMigrate: { context in
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.HealthDataStatistics.self)
try context.delete(model: MeSchemaV2.SportsDataStatistics.self)
try context.delete(model: MeSchemaV2.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.TodayHealthData.self)
try context.delete(model: MeSchemaV2.SleepDataSource.self)
try context.delete(model: MeSchemaV2.WorkoutTargetData.self)
try context.delete(model: MeSchemaV2.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV2.HealthDataList.self)
},
didMigrate: nil
)
//migrateV3toV4, adds some fields in MeSchemaV3.WorkoutList.self, and adds several new Models. When TF and the official version are updated, it will crash at startup. Continue to try to delete historical data during migration, but the problem still exists.
static let migrateV3toV4 = MigrationStage.custom(
fromVersion: MeSchemaV3.self,
toVersion: MeSchemaV4.self,
willMigrate: { context in
do {
try context.delete(model: MeSchemaV3.WorkoutList.self)
try context.delete(model: MeSchemaV3.HealthDataStatistics.self)
try context.delete(model: MeSchemaV3.SportsDataStatistics.self)
try context.delete(model: MeSchemaV3.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV3.TodayRingData.self)
try context.delete(model: MeSchemaV3.TodayHealthData.self)
try context.delete(model: MeSchemaV3.SleepDataSource.self)
try context.delete(model: MeSchemaV3.WorkoutTargetData.self)
try context.delete(model: MeSchemaV3.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV3.HealthDataList.self)
try context.delete(model: MeSchemaV3.SleepStagesData.self)
try context.save()
} catch {
print("Migration from V3 to V4 failed with error: \(error)")
throw error
}
},
didMigrate: nil
)
}
There are multiple versions of VersionedSchema in my App. I used MigrationPlan to migrate data. It works well in Xcode, but in TestFlight and App Store, it always crashes when opening the App for the first time.
MeMigrationPlan Code:
import Foundation
import SwiftData
enum MeMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[MeSchemaV1.self, MeSchemaV2.self, MeSchemaV3.self, MeSchemaV4.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3, migrateV3toV4]
}
//migrateV1toV2, because the type of a data field in MeSchemaV1.TodayRingData.self is modified, the historical data is deleted during migration, and the migration work is successfully completed.
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: MeSchemaV1.self,
toVersion: MeSchemaV2.self,
willMigrate: { context in
try context.delete(model: MeSchemaV1.TodayRingData.self)
},
didMigrate: nil
)
//migrateV2toV3, because a new Model was added, it would crash when starting up when TF and the official version were updated, so I tried to delete the historical data during migration, but the problem still exists.
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: MeSchemaV2.self,
toVersion: MeSchemaV3.self,
willMigrate: { context in
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.HealthDataStatistics.self)
try context.delete(model: MeSchemaV2.SportsDataStatistics.self)
try context.delete(model: MeSchemaV2.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.TodayHealthData.self)
try context.delete(model: MeSchemaV2.SleepDataSource.self)
try context.delete(model: MeSchemaV2.WorkoutTargetData.self)
try context.delete(model: MeSchemaV2.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV2.HealthDataList.self)
},
didMigrate: nil
)
//migrateV3toV4, adds some fields in MeSchemaV3.WorkoutList.self, and adds several new Models. When TF and the official version are updated, it will crash at startup. Continue to try to delete historical data during migration, but the problem still exists.
static let migrateV3toV4 = MigrationStage.custom(
fromVersion: MeSchemaV3.self,
toVersion: MeSchemaV4.self,
willMigrate: { context in
do {
try context.delete(model: MeSchemaV3.WorkoutList.self)
try context.delete(model: MeSchemaV3.HealthDataStatistics.self)
try context.delete(model: MeSchemaV3.SportsDataStatistics.self)
try context.delete(model: MeSchemaV3.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV3.TodayRingData.self)
try context.delete(model: MeSchemaV3.TodayHealthData.self)
try context.delete(model: MeSchemaV3.SleepDataSource.self)
try context.delete(model: MeSchemaV3.WorkoutTargetData.self)
try context.delete(model: MeSchemaV3.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV3.HealthDataList.self)
try context.delete(model: MeSchemaV3.SleepStagesData.self)
try context.save()
} catch {
print("Migration from V3 to V4 failed with error: \(error)")
throw error
}
},
didMigrate: nil
)
}