Fetch running data with HealthKit in Swift

136 Views Asked by At

I tried to use HealthKit to fetch my own running data in Swift.

For this I created a HealthManager class for requesting authorization to reading "Workouts" data from HealthKit.

For requesting the authorization I added "Privacy - Health Share Usage Description" key in Info.plist

enter image description here

enter image description here

After this I tried to fetch my running data with this function :

func fetchRunningData() {
        
        // Closure for convert meters to kilometers
        let convertToKilometers = { $0 / 1000.0 }
        
        let calendar = Calendar.current
        let endDate = Date() // End date is today
        let startDate = calendar.date(byAdding: .day, value: -30, to: endDate)!
        
        // Weekly stats
        var weeklyDistance = 0.0
        var weeklyElevation = 0.0
        var weeklyDuration = 0.0
    
        // Filters
        let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
            HKQuery.predicateForSamples(withStart: startDate, end: endDate),
            HKQuery.predicateForWorkouts(with: .running)
        ])
                
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
        
        let query = HKSampleQuery(sampleType: .workoutType(),
                                  predicate: predicate,
                                  limit: HKObjectQueryNoLimit,
                                  sortDescriptors: [sortDescriptor]) { (query, results, error) in
            if let workouts = results as? [HKWorkout], error == nil {
                // Process the retrieved workouts
                for workout in workouts {
                    let distance = workout.totalDistance?.doubleValue(for: .meter()) ?? 0.0
                    let duration = workout.duration
                    
                    // fetch elevation
                    guard let workoutMetadata = workout.metadata,
                          let workoutElevation = workoutMetadata["HKElevationAscended"] as? HKQuantity else {
                        return
                    }
                    
                    let elevation = workoutElevation.doubleValue(for: .meter())
                    
                    weeklyDistance += convertToKilometers(distance)
                    weeklyElevation += convertToKilometers(elevation)
                    weeklyDuration += duration
                }
                
                print("Weekly statistics -> Distance: \(weeklyDistance) - Elevation: \(weeklyElevation) - Duration: \(weeklyDuration)")
                
            } else {
                // Handle the case where the query fails or there's an error
                if let error = error {
                    print("Error retrieving running data: \(error.localizedDescription)")
                }
            }
        }
        healthStore.execute(query)
    }

When I run this code (on a real device) app requesting access for workouts data, I allow this and the app start, but my running data are never fetched. The print into my function is never executed.

enter image description here

The query is correct and access is granted because the print into the 'else' is not executed.

That weird because my app works as if there is no running data into my HealthKit data but on my iPhone I have many running workouts for the last 30 days.

enter image description here

Here my entire code.

import Foundation
import HealthKit


class HealthManager: ObservableObject {
    
    let healthStore = HKHealthStore()
    
    init() {
        let readTypes = HKObjectType.workoutType()
        let healthTypes: Set = [readTypes]
        
        // Request user permission for access to his health data
        // We need to add a key in Info.plist for request permission
        Task {
            do {
                try await healthStore.requestAuthorization(toShare: [], read: healthTypes)
            } catch {
                print("error fetching health data")
            }
        }
    }
    
    
    
    func fetchRunningData() {
        
        // Closure for convert meters to kilometers
        let convertToKilometers = { $0 / 1000.0 }
        
        let calendar = Calendar.current
        let endDate = Date() // End date is today
        let startDate = calendar.date(byAdding: .day, value: -30, to: endDate)!
        
        // Weekly stats
        var weeklyDistance = 0.0
        var weeklyElevation = 0.0
        var weeklyDuration = 0.0
    
        // Filters
        let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
            HKQuery.predicateForSamples(withStart: startDate, end: endDate),
            HKQuery.predicateForWorkouts(with: .cycling)
        ])
                
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
        
        let query = HKSampleQuery(sampleType: .workoutType(),
                                  predicate: predicate,
                                  limit: HKObjectQueryNoLimit,
                                  sortDescriptors: [sortDescriptor]) { (query, results, error) in
            if let workouts = results as? [HKWorkout], error == nil {
                // Process the retrieved workouts
                for workout in workouts {
                    let distance = workout.totalDistance?.doubleValue(for: .meter()) ?? 0.0
                    let duration = workout.duration
                    
                    // fetch elevation
                    guard let workoutMetadata = workout.metadata,
                          let workoutElevation = workoutMetadata["HKElevationAscended"] as? HKQuantity else {
                        return
                    }
                    
                    let elevation = workoutElevation.doubleValue(for: .meter())
                    
                    weeklyDistance += convertToKilometers(distance)
                    weeklyElevation += convertToKilometers(elevation)
                    weeklyDuration += duration
                }
                
                print("Weekly statistics -> Distance: \(weeklyDistance) - Elevation: \(weeklyElevation) - Duration: \(weeklyDuration)")
                
            } else {
                // Handle the case where the query fails or there's an error
                if let error = error {
                    print("Error retrieving running data: \(error.localizedDescription)")
                }
            }
        }
        healthStore.execute(query)
    }
}

I tried to change the workout type in my predicate.

I replace .running by .cycling and when I run the app my cycling data are fetched and printed in the log.

enter image description here

Does anyone understand why the running data are no fetched ?

UPDATE

When I try to print my stats in the for loop the data are fetched but I can't use them after the loop. But that's so confusing because my variables are declared in the beginning of the function and if I replace running by cycling data are available in and out the loop.

let query = HKSampleQuery(sampleType: .workoutType(),
                              predicate: predicate,
                              limit: HKObjectQueryNoLimit,
                              sortDescriptors: [sortDescriptor]) { (query, results, error) in
        if let workouts = results as? [HKWorkout], error == nil {
            // Process the retrieved workouts
            for workout in workouts {
                let distance = workout.totalDistance?.doubleValue(for: .meter()) ?? 0.0
                let duration = workout.duration
                
                // fetch elevation
                guard let workoutMetadata = workout.metadata,
                      let workoutElevation = workoutMetadata["HKElevationAscended"] as? HKQuantity else {
                    return
                }
                
                let elevation = workoutElevation.doubleValue(for: .meter())
                
                weeklyDistance += convertToKilometers(distance)
                weeklyElevation += convertToKilometers(elevation)
                weeklyDuration += duration
                
               print(weeklyDistance) // Displayed if workout is running or cycling
            }
            print(weeklyDistance) // displayed on if workout is cycling
            //print("Weekly statistics -> Distance: \(weeklyDistance) - Elevation: \(weeklyElevation) - Duration: \(weeklyDuration)")
            
        } else {
            // Handle the case where the query fails or there's an error
            if let error = error {
                print("Error retrieving running data: \(error.localizedDescription)")
            }
        }
0

There are 0 best solutions below