Search code examples
iosswifthealthkitwatchos

Can't get weight samples using anchored query


I'm working on a watchOS App as my first Swift/iOS project ever. I want to fetch the latest body weight sample and use it for some calculation. The result is presented to the user. As soon as a new sample is added, I want to update my UI as well. It works in a completely fresh simulator installation. As soon as I add a sample in the iOS simulator, the app updates its UI in the watchOS simulator. However, it doesn't work on my real device or after resetting the watchOS simulator. And I just don't know why. The HKAnchoredObjectQuery just returns 0 samples but I definitely have some samples stored in health. I can even see them under Settings > Health on my watch. I can't imagine this is related to my code, but here it is:

class WeightProvider: ObservableObject {
    private static let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
    private static let healthStore: HKHealthStore = .init()

    private var previousAnchor: HKQueryAnchor?
    private var runningQuery: HKAnchoredObjectQuery?

    @Published var bodyWeight: Measurement<UnitMass>?

    func getBodyWeight(longRunning: Bool = false) {
        let query = HKAnchoredObjectQuery(type: Self.weightSampleType, predicate: nil, anchor: previousAnchor, limit: longRunning ? HKObjectQueryNoLimit : 1, resultsHandler: processQueryResult)

        if longRunning {
            query.updateHandler = processQueryResult
            runningQuery = query
        }

        Self.healthStore.execute(query)
    }

    func stopLongRunningQuery() {
        if let runningQuery = runningQuery {
            Self.healthStore.stop(runningQuery)
            self.runningQuery = nil
        }
    }

    private func processQueryResult(_: HKAnchoredObjectQuery, samples: [HKSample]?, _: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
        guard let samples = samples as? [HKQuantitySample], error == nil else {
            fatalError(error?.localizedDescription ?? "Failed to cast [HKSample] to [HKQuantitySample]")
        }

        previousAnchor = newAnchor

        guard let sample = samples.last else {
            return
        }

        DispatchQueue.main.async {
            if Locale.current.usesMetricSystem {
                let weight = sample.quantity.doubleValue(for: .gramUnit(with: .kilo))
                self.bodyWeight = .init(value: weight, unit: UnitMass.kilograms)
            } else {
                let weight = sample.quantity.doubleValue(for: .pound())
                self.bodyWeight = .init(value: weight, unit: UnitMass.pounds)
            }
        }
    }
}

// MARK: - HealthKit Authorization

extension WeightProvider {
    private static let typesToRead: Set<HKObjectType> = [
        weightSampleType,
    ]

    func authorize(completion: @escaping (Bool, Error?) -> Swift.Void) {
        Self.healthStore.requestAuthorization(toShare: nil, read: Self.typesToRead) { success, error in
            completion(success, error)
        }
    }
}

In my Views onAppear I call this function:

private func authorizeHealthKit() {
    guard firstRun else {
        return
    }
    firstRun = false

    weightProvider.authorize { success, error in
        guard success, error == nil else {
            return
        }

        weightProvider.getBodyWeight(longRunning: true)
    }
}

HealthKit is properly authorized as I can see in the Settings of my Watch. Any ideas? Any tips for my code in general?


Solution

  • Wow, after all this time I found the issue: The line previousAnchor = newAnchor needs to be after the guard statement. That's it.