Sam Uyemura
Sam Uyemura

Reputation: 37

How do I the amount of time spent sleeping in the previous day with HealthKit and Swift?

I'm trying to use HealthKit to determine the amount of time the user spent sleeping in the 24 hours previous to the query. I've requested permission from the user to read step and sleep data and, while I can read step data without any trouble, my sleep query returns no data.

Here's my function:

    func fetchSleepData(completion: @escaping (TimeInterval?) -> Void) {
        let healthStore = HKHealthStore()
        let calendar = Calendar.current

        let endDate = Date()
        let startDate = calendar.date(byAdding: .day, value: -1, to: endDate)!
        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)

        let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)

        let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: [sortDescriptor]) { _, results, error in
            if let error = error {
                print("Error: \(error.localizedDescription)")
                return
            }

            guard let results = results as? [HKCategorySample] else {
                print("No data to display")
                return
            }

            print("Start Date: \(startDate), End Date: \(endDate)")
            print("Fetched \(results.count) sleep analysis samples.")

            var totalSleepTime: TimeInterval = 0

            for result in results {
                if result.value == HKCategoryValueSleepAnalysis.asleepUnspecified.rawValue {
                    let sleepDuration = result.endDate.timeIntervalSince(result.startDate)
                    print("Sample start: \(result.startDate), end: \(result.endDate), value: \(result.value), duration: \(sleepDuration) seconds")
                    totalSleepTime += sleepDuration
                }
            }

            completion(totalSleepTime)
        }

        healthStore.execute(query)
    }

and here's my authentication and function call:

let healthStore = HKHealthStore()
        
let steps = HKQuantityType(.stepCount)
let sleepAnalysis = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!
        
let healthTypes: Set = [steps, sleepAnalysis]
        
Task {
    do {
        try await healthStore.requestAuthorization(toShare: [], read: healthTypes)
        fetchStepData()
        fetchSleepData { totalSleepTime in
            if let totalSleepTime = totalSleepTime {
                print("User slept for \(totalSleepTime) seconds last night.")
            } else {
                print("No sleep data available for last night.")
            }
        }
    } catch {
        print("Error fetching health data!")
    }
}

When the app runs, the console shows:

Health Manager Init
Fetching step data...
...
Start Date: 2023-07-07 06:00:00 +0000, End Date: 2023-07-08 06:00:00 +0000
Fetched 0 sleep analysis samples.
User slept for 0.0 seconds last night.
...

I've double checked that there is sleep data in the testing device's Health app and that the user has granted permission to the app. I'm wondering if it may be a problem with my environment because every resource I've found online seems to show similar solutions. Thank you in advance for any advice/solutions!

Upvotes: 0

Views: 581

Answers (1)

matt
matt

Reputation: 535159

The problem is that .asleepUnspecified does not embrace the actual sleep types, such as .asleepDeep and so forth. What does embrace them all is .allAsleepValues. Change

if result.value == HKCategoryValueSleepAnalysis.asleepUnspecified.rawValue {

To

if HKCategoryValueSleepAnalysis.allAsleepValues.map({$0.rawValue}).contains(result.value) {

Actually I would rewrite your loop (more neatly, I think) like this:

for result in results {
    if let type = HKCategoryValueSleepAnalysis(rawValue: result.value) {
        if HKCategoryValueSleepAnalysis.allAsleepValues.contains(type) {
            let sleepDuration = result.endDate.timeIntervalSince(result.startDate)
            print("""
            Sample start: (result.startDate), \
            end: (result.endDate), \
            value: (result.value), \
            duration: (sleepDuration) seconds
            """)
            totalSleepTime += sleepDuration
        }
    }
}

Upvotes: 1

Related Questions