Trying and failing to get route/location data from a workout due to denied authorization. I'm only asking for 'read' values of HKObjectType.workoutType
. I have all plist keys added, I have capability setup. Running on a physical device with data.
Error:
Error Domain=com.apple.healthkit Code=5 "Authorization not determined" UserInfo={NSLocalizedDescription=Authorization not determined}
Not sure why. I even tried adding 'sharing' request but still hit the error. The 'permissions UI' appears and I can view a list of workouts, it just fails unexpectedly (for me) when loading a detail view.
Based on: https://developer.apple.com/documentation/healthkit/workouts_and_activity_rings/reading_route_data
ContentView
struct ContentView: View {
@State private var isPresented = false
private let healthStore = HKHealthStore()
var body: some View {
NavigationStack {
Button("Request") {
requestAuthorization()
}
.navigationDestination(isPresented: $isPresented) {
WorkoutListView()
}
}
}
private func requestAuthorization() {
let typesToRead: Set<HKObjectType> = [HKObjectType.workoutType()]
healthStore.requestAuthorization(toShare: nil, read: typesToRead) { success, error in
if let error = error {
print("Authorization request failed: \(error.localizedDescription)")
return
}
if success {
print("Authorization granted")
isPresented = true
}
} else {
print("Authorization denied")
}
}
}
}
WorkoutListView
struct WorkoutListView: View {
@StateObject var viewModel = WorkoutListViewModel()
var body: some View {
List {
Section("Walk") {
ForEach(viewModel.workouts, id: \.uuid) { workout in
if case .walking = workout.workoutActivityType {
NavigationLink(destination: WorkoutDetailView(workout: workout)) {
HStack {
Image(systemName: "figure.walk")
Text("Outdoor Walk")
}
}
}
}
}
}
}
.onAppear {
viewModel.fetchWorkouts()
}
.navigationTitle("Workouts")
}
}
WorkoutDetailView
struct WorkoutDetailView: View {
var workout: HKWorkout
@StateObject var viewModel = WorkoutListViewModel()
@State private var location: CLLocation?
var body: some View {
VStack {
// Display location data if available
if let location = location {
Text("Latitude: \(location.coordinate.latitude)")
Text("Longitude: \(location.coordinate.longitude)")
Text("Time: \(location.timestamp)")
} else {
Text("Location data not available.")
}
}
.onAppear {
viewModel.getLocationData(for: workout) { extractedLocation in
location = extractedLocation
}
}
}
}
WorkoutListViewModel
final class WorkoutListViewModel: ObservableObject {
@Published private(set) var workouts: [HKWorkout] = []
private let healthStore = HKHealthStore()
// Fetch today's walking exercises
func fetchWorkouts() {
let workoutType = HKObjectType.workoutType()
// Define the start and end dates for today
let calendar = Calendar.current
let startDate = calendar.startOfDay(for: Date())
let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)
// Create a predicate to fetch workouts that fall within the specified date range
let datePredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
// Create a predicate to filter workouts by activity type (e.g., walking)
let activityPredicate = HKQuery.predicateForWorkouts(with: .walking)
// Combine the date and activity predicates
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [datePredicate, activityPredicate])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(
sampleType: workoutType,
predicate: compoundPredicate,
limit: 0,
sortDescriptors: [sortDescriptor]
) { query, samples, error in
guard let samples = samples as? [HKWorkout], error == nil else {
print("Failed to fetch workouts: \(error?.localizedDescription ?? "Unknown error")")
return
}
DispatchQueue.main.async {
self.workouts = samples
}
}
healthStore.execute(query)
}
// Get location data for a workout
func getLocationData(for workout: HKWorkout, completion: @escaping (CLLocation?) -> Void) {
// 1. Get Route
let runningObjectQuery = HKQuery.predicateForObjects(from: workout)
let routeQuery = HKAnchoredObjectQuery(
type: HKSeriesType.workoutRoute(),
predicate: runningObjectQuery,
anchor: nil,
limit: HKObjectQueryNoLimit
) { (query, samples, deletedObjects, anchor, error) in
guard error == nil else {
// Handle any errors here.
print(error!)
fatalError("The initial query failed.")
}
// Process the initial route data here.
}
// 2. Location Data from Route
// Execute
healthStore.execute(routeQuery)
}
}
To get the location details of the workout that you're requesting when the WorkoutDetailView
appears, you'll also need to request access to HKSeriesType.workoutRoute()
.
So your request will be
let typesToRead: Set<HKObjectType> = [HKObjectType.workoutType(), HKSeriesType.workoutRoute()]