I have suspected there is something wrong with my personal Health account for a while but while working on a new app that queries HealthKit I'm pretty certain.
I've developed a very simple health app where you can request auth to read steps and then runs a statistic collection query to get a tally of steps you take.
This test app runs on the simulator fine. But when I run it on a device using my Apple ID the query just hangs. I don't get the callback for the initial result or the updated results.
Looking in Xcode, the memory graph just steadily increases until you terminate the app. And it appears there are millions of statistics objects being created.
Can anyone suggest anything to prevent this from happening? Do I need to reset my Health data? I would rather there was another solution.
I've included the code for this dummy app below. You will need to add HealthKit capabilities and the Privacy: Health Sharing info key for it to run.
import SwiftUI
import HealthKit
@main
struct StepCountApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class ViewModel: ObservableObject {
let healthStore = HKHealthStore()
var query: HKStatisticsCollectionQuery?
@Published var isQuerying = false
@Published var authStatus: HKAuthorizationStatus = .notDetermined
@Published var stepCount: Int = 0
init() {
}
func updateAuthStatus() {
authStatus = healthStore.authorizationStatus(for: .quantityType(forIdentifier: .stepCount)!)
}
func requestAuth() {
let sharing = Set<HKSampleType>()
let reading = Set<HKObjectType>([.quantityType(forIdentifier: .stepCount)!])
healthStore.requestAuthorization(toShare: sharing, read: reading) { [weak self] result, error in
if result {
DispatchQueue.main.async {
self?.updateAuthStatus()
}
} else if let error = error {
print("Auth failed: \(error.localizedDescription)")
} else {
fatalError("How did we get here?")
}
}
}
func stopQuery() {
guard let query = query else { return }
healthStore.stop(query)
self.query = nil
isQuerying = false
}
func startQuery() {
let stepsType = HKObjectType.quantityType(forIdentifier: .stepCount)!
let intervalComponents = DateComponents(day: 1)
query = HKStatisticsCollectionQuery(
quantityType: stepsType,
quantitySamplePredicate: nil,
options: [.cumulativeSum],
anchorDate: Date(),
intervalComponents: intervalComponents
)
query!.initialResultsHandler = { [weak self] query, results, error in
print("Initial result")
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
guard let statisticsCollection = results else {
print("Error: No stats collection")
return
}
guard let statistics = statisticsCollection.statistics(for: Date()) else {
print("Error: No stats for date")
return
}
let sum = statistics.sumQuantity()?.doubleValue(for: .count()) ?? 0
DispatchQueue.main.async {
self?.stepCount = Int(sum)
}
}
query!.statisticsUpdateHandler = { [weak self] query, statistics, results, error in
print("Updated result")
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
guard let statisticsCollection = results else {
print("Error: No stats collection")
return
}
guard let statistics = statisticsCollection.statistics(for: Date()) else {
print("Error: No stats for date")
return
}
guard let sum = statistics.sumQuantity()?.doubleValue(for: .count()) else {
print("Error: Statistics have no data?")
return
}
DispatchQueue.main.async {
DispatchQueue.main.async {
self?.stepCount = Int(sum)
}
}
}
healthStore.execute(query!)
isQuerying = true
}
var authStatusString: String {
switch authStatus {
case .notDetermined: return "Not determined"
case .sharingAuthorized: return "Sharing authorised"
case .sharingDenied: return "Sharing denied"
@unknown default: fatalError()
}
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Form {
Section {
HStack {
Text("Auth Status")
Spacer()
Text("\(viewModel.authStatusString)")
}
}
Section {
Button("Request Auth") {
viewModel.requestAuth()
}
}
Section {
if viewModel.isQuerying {
Button("Stop query") {
viewModel.stopQuery()
}
} else {
Button("Start query") {
viewModel.startQuery()
}
}
}
Section {
HStack {
Text("Step count")
Spacer()
Text("\(viewModel.stepCount)")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}