I am working on the health kit. Before iOS15 below code is working correctly I am getting vitals on an hourly basis but in this latest version, I am not getting any vitals event per hour.
I am facing an issue in iOS15 not getting any event in an hourly event. Can anyone help me out? As per apple documentation https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery I have enabled the permission in XCode13 still I am facing this issue.
Here is my code. (Working code before iOS15)
private func startObservingStepCountChanges() {
let sampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)
let query: HKObserverQuery = HKObserverQuery(sampleType: sampleType!, predicate: nil, updateHandler: self.stepChangeHandler)
healthKitStore.execute(query)
healthKitStore.enableBackgroundDelivery(for: sampleType!, frequency: .hourly, withCompletion: {(succeeded: Bool, error: Error!) in
if succeeded{
print("Enabled background delivery of stepcount changes")
} else {
if let theError = error{
print("Failed to enable background delivery of stepcount changes. ")
print("Error = \(theError)")
}
}
} as (Bool, Error?) -> Void)
}
private func stepChangeHandler(query: HKObserverQuery!, completionHandler: HKObserverQueryCompletionHandler!, error: Error!) {
// Flag to check the background handler is working or not
print("Backgound Mode activated")
fireTestPush()
completionHandler()
}
Check if background app refresh is enabled for your app. If it is and you're still running into this issue, file a feedback assistant report to Apple with sample code and sysdiagnose.
Related
I'm using a CKQueryOperation that apparently works fine for most of my users. However, for some of them it is not working. The issue is that queryCompletionBlock is not being called.
Analysing user logs I can see that it works fine for most of the users, but it doesn't work for a few of them. It fails in all kind of iPhone models. But iOS is always iOS 14.2 on the failing devices. Unfortunately, I can not reproduce the issue on my device and this makes impossible to debug it.
I've already checked that the issue is not related with the internet connection type (wifi or data)
Any idea?
This is the code
func fetchTeams(_ success: #escaping (_ result: [CKRecord]?) -> Void,
failure: #escaping (_ error: NSError) -> Void) {
bfprint("fetchTeams starts")
let type = RecordType.Team
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: type.rawValue, predicate: predicate)
let operation = CKQueryOperation(query: query)
allTeamsRecords = []
executeQueryOperation(operation,
success: success,
failure: failure)
}
private func executeQueryOperation(_ queryOperation: CKQueryOperation,
success: #escaping (_ result: [CKRecord]?) -> Void,
failure: #escaping (_ error: NSError) -> Void) {
bfprint("executeQueryOperation starts")
let configuration = CKOperation.Configuration()
configuration.qualityOfService = .userInitiated
queryOperation.configuration = configuration
queryOperation.queuePriority = .veryHigh
queryOperation.recordFetchedBlock = { [weak self] (record) in
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we got all team records")
return
}
strongSelf.allTeamsRecords.append(record)
}
queryOperation.queryCompletionBlock = { [weak self] (cursor, error) in
bfprint("fetch teams operation completion block called")
if let cursor = cursor {
bfprint("We got a new cursor fetching teams")
let newOperation = CKQueryOperation(cursor: cursor)
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we got all team records")
return
}
strongSelf.executeQueryOperation(newOperation,
success: success,
failure: failure)
}
else if let error = error {
DispatchQueue.main.async(execute: {
failure(error as NSError)
bfprint("Cloud Query Error - Fetching Teams): \(error)")
})
}
else {
DispatchQueue.main.async(execute: {
bfprint("Get teams finished successfully")
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we execute success closure")
return
}
success(strongSelf.allTeamsRecords)
})
}
}
Self.publicDB.add(queryOperation)
bfprint("query added to database")
}
I don't know what's specifically wrong in your situation, but I might offer some general guidance with CloudKit as I've worked with it over the years. CloudKit is really reliable, but it's also a little unreliable. :)
Here are some tips:
Build in mechanisms to repeatedly check that you have the latest data.
Background notifications don't always come. Have a way to get data that may have been missed.
Development and production behave a little differently in that dev seems to be a bit less reliable overall.
The CloudKit Dashboard needs to be refreshed (like the whole page in your web browser) from time-to-time because the state shown can get stale even when using the reload and query buttons in the interface.
So in your case, you might have a way to repeatedly try the CKQueryOperation so that it gets reattempted if something is haywire on CloudKit. Maintaining a local cache that syncs with CloudKit is the best way I've found to make sure your data is accurate.
I'm 99% sure it was an iOS issue. After users updated to iOS 14.3 the problem disappeared
I have an iOS app that communicates with the paired watch using WatchConnectivity. In most cases, it works without problems, on the simulators and on the devices.
The problem:
During development on the simulators, I get now and then the following communication error when I try to send a direct message from iOS to watchOS using WCSession.default.sendMessage(_:replyHandler:errorHandler:):
Error Domain=WCErrorDomain Code=7007
"WatchConnectivity session on paired device is not reachable."
I have read this related post, but it does not apply to my case, because my app does work normally.
My questions:
How can it be that the watch simulator becomes not reachable while the app is running on the iOS simulator?
Does it make sense just to retry sendMessage after a while?
Is there any workaround?
As a workaround, I modified the sendMessage function so that in case of this error the transfer is retried a number of times. Since then, all sendMessage transfers are executed successfully.
func sendMessage(_ message: [String: AnyObject],
replyHandler: (([String: Any]) -> Void)?,
errorHandler: ((Error) -> Void)?) {
guard let communicationReadySession = communicationReadySession else {
// watchOS: A session is always valid, so it will never come here.
print("Cannot send direct message: No reachable session")
let error = NSError.init(domain: kErrorDomainWatch,
code: kErrorCodeNoValidAndReachableSession,
userInfo: nil)
errorHandler?(error)
return
}
/* The following trySendingMessageToWatch sometimews fails with
Error Domain=WCErrorDomain Code=7007 "WatchConnectivity session on paired device is not reachable."
In this case, the transfer is retried a number of times.
*/
let maxNrRetries = 5
var availableRetries = maxNrRetries
func trySendingMessageToWatch(_ message: [String: AnyObject]) {
communicationReadySession.sendMessage(message,
replyHandler: replyHandler,
errorHandler: { error in
print("sending message to watch failed: error: \(error)")
let nsError = error as NSError
if nsError.domain == "WCErrorDomain" && nsError.code == 7007 && availableRetries > 0 {
availableRetries = availableRetries - 1
let randomDelay = Double.random(min: 0.3, max: 1.0)
DispatchQueue.main.asyncAfter(deadline: .now() + randomDelay, execute: {
trySendingMessageToWatch(message)
})
} else {
errorHandler?(error)
}
})
} // trySendingMessageToWatch
trySendingMessageToWatch(message)
} // sendMessage
EDIT to add my updated code which I based on WWDC 2016's Getting the Most Out of Healthkit talk, but I still am not getting my print statement with the new workout to fire unless I open the app?
I'm trying to observe for new workouts on the iPhone after they've been saved on the Apple Watch. Below is the code I'm running in didFinishLaunching. To test it, I'm running Xcode on my iPhone App...building and running, then navigating back to the home screen. Then starting and saving a workout on my watch, however my print statements aren't printing in the console. What am I missing?
func startObservingNewWorkouts() {
let sampleType = HKObjectType.workoutType()
//1. Enable background delivery for workouts
self.healthStore.enableBackgroundDelivery(for: sampleType, frequency: .immediate) { (success, error) in
if let unwrappedError = error {
print("could not enable background delivery: \(unwrappedError)")
}
if success {
print("background delivery enabled")
}
}
//2. open observer query
let query = HKObserverQuery(sampleType: sampleType, predicate: nil) { (query, completionHandler, error) in
self.updateWorkouts() {
completionHandler()
}
}
healthStore.execute(query)
}
func updateWorkouts(completionHandler: #escaping () -> Void) {
var anchor: HKQueryAnchor?
let sampleType = HKObjectType.workoutType()
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: anchor, limit: HKObjectQueryNoLimit) { [unowned self] query, newSamples, deletedSamples, newAnchor, error in
self.handleNewWorkouts(new: newSamples!, deleted: deletedSamples!)
anchor = newAnchor
completionHandler()
}
healthStore.execute(anchoredQuery)
}
func handleNewWorkouts(new: [HKSample], deleted: [HKDeletedObject]) {
print("new sample added = \(new.last.startTime!)")
}
Turns out this code 👆 works, its just that I was testing in the simulator and apparently the Observer Query does NOT fire when running in the simulator but it DOES fire when running on device
So I've been following the instructions in this answer...
Healthkit background delivery when app is not running
The code runs fine and works whilst the application is open and says that background delivery is successful, however when I test the application by walking around and changing the clock on the device to an hour forward I do not receive any logs to let me know it has run. However, if I open the application again the observer query runs.
private func checkAuthorization(){
let healthDataToRead = Set(arrayLiteral: self.distanceQuantityType!)
healthKitStore.requestAuthorization(toShare: nil, read: healthDataToRead) { (success, error) in
if error != nil {
print(error?.localizedDescription)
print("There was an error requesting Authorization to use Health App")
}
if success {
print("success")
}
}
}
public func enableBackgroundDelivery() {
self.checkAuthorization()
self.healthKitStore.enableBackgroundDelivery(for: self.distanceQuantityType!, frequency: .hourly) { (success, error) in
if success{
print("Background delivery of steps. Success = \(success)")
}
if let error = error {
print("Background delivery of steps failed = \(error.localizedDescription)")
}
}
}
func observeDistance(_ handler:#escaping (_ distance: Double) -> Void) {
let updateHandler: (HKObserverQuery?, HKObserverQueryCompletionHandler?, Error?) -> Void = { query, completion, error in
if !(error != nil) {
print("got an update")
completion!()
} else {
print("observer query returned error: \(error)")
}
}
let query = HKObserverQuery(sampleType: self.distanceQuantityType!, predicate: nil, updateHandler: updateHandler)
self.healthKitStore.execute(query)
}
The query is initialised in the appDelegate method didFinishLaunching
This particular HealthKitQuery is asynchronous. You should wait until it finishes processing.
However, this case is not possible in didFinishLaunching. The application just ended execution and there is not enough time to process the query.
I would seriously suggest to rethink the logic behind the operation of your code. A good way to solve this would be to put the request elsewhere, preferrably after the needed operations were completed.
I have followed the answers of this question -
Healthkit background delivery when app is not running
on stackoverflow but not achieved my goal.
I want to notify user using local notification when he/she walked 1 km exact, when app is in background and not running state.
I am still not able to read the distance from Healthkit in background as well.
But when app is in foreground then only my code works and the code is as blow
let healthKitStore = HKHealthStore()
let distanceType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)!
let query:HKObserverQuery = HKObserverQuery(sampleType: distanceType, predicate: nil) { (query, HKObserverQueryCompletionHandler, error) in
//here I schedule my local notification
// I want to read the distance here and also want to notify user.
self.callLocalNotification(1)
HKObserverQueryCompletionHandler()
}
healthKitStore?.execute(query)
healthKitStore?.enableBackgroundDelivery(for: distanceType, frequency: .immediate, withCompletion: { (succeeded, error) in
if succeeded{
print("Enabled background delivery of Distance changes")
} else {
if let theError = error{
print("Failed to enable background delivery of Distance changes.")
print("Error = \(theError)")
}
}
})
func callLocalNotification(_ distance:Int)
{
// Request Notification Settings
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined:
self.requestAuthorization(completionHandler: { (success) in
guard success else { return }
// Schedule Local Notification
self.scheduleLocalNotification(distance)
})
case .authorized:
// Schedule Local Notification
self.scheduleLocalNotification(distance)
case .denied:
print("Application Not Allowed to Display Notifications")
}
}