Reputation: 415
In this SwiftUI project, I had a profile tab that displayed the username of the user. When I tapped on this tab again (whilst already in the profile view), the name of the user disappeared.
I fixed this issue by changing this line of code:
@ObservedObject var ProfileModel = ProfileViewModel()
To this line of code:
@StateObject var ProfileModel = ProfileViewModel()
I found that changing @ObservedObject to @StateObject fixed the bug.
Therefore I wanted to apply the same fix to my home view (which was experiencing the same bug as the profile view), where I am displaying the User's step data using the HealthKit framework.
However this caused problems in the init().
Changing:
@ObservedObject var healthStore = HealthStore()
To:
@StateObject var healthStore = HealthStore()
Caused this error in my init(): Cannot assign to property: 'healthStore' is a get-only property.
Here is my init():
init() {
healthStore = HealthStore()
}
I need the healthStore to use @ObservedObject in order to listen for changes in the healthStore and update the view accordingly.
Here is HealthStore:
import Foundation
import HealthKit
import SwiftUI
import Combine
class HealthStore: ObservableObject {
@Published var healthStore: HKHealthStore?
@Published var query: HKStatisticsCollectionQuery?
@Published var steps = [Step]()
init() {
if HKHealthStore.isHealthDataAvailable() {
healthStore = HKHealthStore()
}
}
func calculateSteps(completion: @escaping (HKStatisticsCollection?) -> Void) {
let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())
let anchorDate = Date.mondayAt12AM()
let daily = DateComponents(day: 1)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictStartDate)
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates:
[.init(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered), predicate]
)
DispatchQueue.main.async {
self.query = HKStatisticsCollectionQuery(
quantityType: stepType,
quantitySamplePredicate: compoundPredicate,
options: .cumulativeSum,
anchorDate: anchorDate,
intervalComponents: daily)
self.query!.initialResultsHandler = { [weak self] query, statisticsCollection, error in
guard let self = self else { return }
DispatchQueue.main.async {
completion(statisticsCollection)
}
}
self.query!.statisticsUpdateHandler = { [weak self] query, statistics, collection, error in
guard let self = self else { return }
DispatchQueue.main.async {
if let stepCount = statistics?.sumQuantity()?.doubleValue(for: HKUnit.count()) {
self.steps.append(Step(count: Int(stepCount), date: statistics!.startDate))
}
}
}
if let healthStore = self.healthStore, let query = self.query {
healthStore.execute(query)
}
}
}
func requestAuthorization(completion: @escaping (Bool) -> Void) {
let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
guard let healthStore = self.healthStore else { return completion(false) }
healthStore.requestAuthorization(toShare: [], read: [stepType]) { (success, error) in
completion(success)
}
}
}
How can I fix the issue of the view that contains step data from healthStore disappearing when the Home view is reinitialised (tapping the home tab when already in the home tab)?
Upvotes: 1
Views: 224
Reputation: 114846
SwiftUI views are structs and therefore immutable. What actually happens when a SwiftUI view "updates", is that a new instance of the view is created and shown.
This means that when you declare your view model via @ObservableObject
, you get a new instance of ProfileViewModel
along with your new ProfileView
.
@StateObject
tells SwiftUI that you want a single instance of this object to be kept when this view is updated, so SwiftUI takes care of this for you. An implication of this is that you cannot assign new values to the property; It is "get only".
In your HomeView
you need to remove the healthStore = HealthStore()
from your init
- You have already provided an instance of HealthStore
and assigned it to healthStore
when you declared @StateObject var healthStore = HealthStore()
@StateObject
properties still refer to ObservableObject
s, so your HealthStore
doesn't need to change.
Upvotes: 3