Observe CKRecord deletion via CKSubscription does not work - ios

CKSubscription doc says: When a record modification causes a subscription to fire, the server sends push notifications to all devices with that subscription except for the one that made the original change to the record.
Let assume I have two devices: device 1 and device 2 logged in from different iCloud accounts. Let assume both devices subscribed for record deletion for a certain record type.
If device 1 creates a record and then device 1 deletes it then device 2 get notified - THAT IS ACCORDING TO THE DOC, BUT ..
If device 1 creates a record and then device 2 deletes it then device 2 get notified - I do NOT think it is ACCORDING TO THE DOC, and IT DOES NOT MAKE ANY SENSE, device 2 deleted it so device 1 should be notified
SET UP SUBSCRIPTION ON DEVICE 1 AND DEVICE 2
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert, categories: nil))
application.registerForRemoteNotifications()
let defaultContainer = CKContainer.defaultContainer()
let publicDatabase = defaultContainer.publicCloudDatabase
publicDatabase.fetchAllSubscriptionsWithCompletionHandler({subscriptions, error in
if error == nil {
if subscriptions.count == 0 {
let subscription = CKSubscription(recordType: "OU", predicate: NSPredicate(value: true), options: .FiresOnRecordDeletion)
subscription.notificationInfo = CKNotificationInfo()
subscription.notificationInfo.shouldBadge = false
subscription.notificationInfo.alertBody = "OU removed or upated"
publicDatabase.saveSubscription(subscription, completionHandler: {subscription, error in
if error == nil {
} else {
println("\(error.localizedDescription)")
}
})
}
} else {
println("\(error.localizedDescription)")
}
})
return true
}
CREATE RECORD on DEVICE 1
#IBAction func addOU(sender: AnyObject) {
var defaultContainer = CKContainer.defaultContainer()
var publicDatabase = defaultContainer.publicCloudDatabase
let r = CKRecord(recordType: "OU", recordID: CKRecordID(recordName: "aaaa"))
publicDatabase.saveRecord(r, completionHandler: { r2, error in
if error == nil {
} else {
println("\(error.localizedDescription)")
}
})
}
DELETE RECORD ON DEVICE 2
#IBAction func removeOU(sender: AnyObject) {
var defaultContainer = CKContainer.defaultContainer()
var publicDatabase = defaultContainer.publicCloudDatabase
publicDatabase.deleteRecordWithID(CKRecordID(recordName: "aaaa"), completionHandler: {recordID, error in
if error == nil {
} else {
println("\(error.localizedDescription)")
}
})
}

I still think that IT MAKE NO SENSE how CKSubscription works, but as a temporary fix I recommend to changed first CKRecord's lastModifiedUserRecordID to the user who want to delete the record, and only afterwards to delete record.
To change lastModifiedUserRecordID you have to fetch it and without do anything on it save it back, and then deletion can come:
#IBAction func removeOU(sender: AnyObject) {
var defaultContainer = CKContainer.defaultContainer()
var publicDatabase = defaultContainer.publicCloudDatabase
publicDatabase.fetchRecordWithID(CKRecordID(recordName: "aaaa"), completionHandler: {record, error in
if error == nil {
publicDatabase.saveRecord(record, completionHandler: {record2, error in
if error == nil {
publicDatabase.deleteRecordWithID(CKRecordID(recordName: "aaaa"), completionHandler: {recordID, error in
if error == nil {
} else {
println("\(error.localizedDescription)")
}
})
} else {
println("\(error.localizedDescription)")
}
})
} else {
println("\(error.localizedDescription)")
}
})
}

Related

How to update state from Phone view on WatchOS view?

I'm using a companion app to authorize a user with a 3rd party service. Once authorized, I update a UserDefaults variable to true. On the companion app side, the view updates correctly and shows that the user has been authenticated. However, on the watch OS side the view does not update. Would I need to use the Watch Connectivity API and send a message to the watch to update the state? Or is there a simple way?
Phone App
struct AuthenticationView: View {
#State private var startingWebAuthenticationSession = false
#AppStorage("authorized") private var authorized = false
var body: some View {
Group {
if !authorized {
VStack {
Button("Connect", action: { self.startingWebAuthenticationSession = true })
.webAuthenticationSession(isPresented: $startingWebAuthenticationSession) {
WebAuthenticationSession(
url: URL(string: "https://service.com/oauth/authorize?scope=email%2Cread_stats&response_type=code&redirect_uri=watch%3A%2F%2Foauth-callback&client_id=\(clientId)")!,
callbackURLScheme: callbackURLScheme
) { callbackURL, error in
guard error == nil, let successURL = callbackURL else {
return
}
let oAuthCode = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "code"}).first
guard let authorizationCode = oAuthCode?.value else { return }
let url = URL(string: "https://service.com/oauth/token")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
let params = "client_id=\(clientId)&client_secret=\(clientSecret)&grant_type=authorization_code&code=\(authorizationCode)&redirect_uri=\(callbackURLScheme)://oauth-callback";
request.httpBody = params.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Error took place \(error)")
return
}
if let data = data, let response = String(data: data, encoding: .utf8) {
let accessTokenResponse: AccessTokenResponse = try! JSONDecoder().decode(AccessTokenResponse.self, from: response.data(using: .utf8)!)
let defaults = UserDefaults.standard
authorized = true
startingWebAuthenticationSession = false
defaults.set(accessTokenResponse.access_token, forKey: DefaultsKeys.accessToken) //TODO: Store securely
ConnectivityService.shared.send(authorized: true)
}
}
task.resume()
}
.prefersEphemeralWebBrowserSession(false)
}
}
}
else {
VStack {
Text("Authenticated!")
}
}
}
}
}
WatchOS
import SwiftUI
struct ConnectView: View {
#ObservedObject var connectivityService: ConnectivityService
var body: some View {
if !$connectivityService.authorized.wrappedValue {
VStack {
Text("Open the app on your primary device to connect.")
}
}
else {
//Some other view
}
}
}
EDIT:
Trying with Watch Connectivity API but the issue I'm experiencing is that when I authenticate from the phone, it'll take some time for the ConnectView to update the authorized variable. I know Watch Connectivity API doesn't update right away but at minimum I'd need some way for the watch to pick up that a secret access token has been retrieved and it can transition to the next view; whether that's through a shared state variable, UserDefaults, or whatever other mechanism.
Here is the ConnectivityService class I'm using:
import Foundation
import Combine
import WatchConnectivity
final class ConnectivityService: NSObject, ObservableObject {
static let shared = ConnectivityService()
#Published var authorized: Bool = false
override private init() {
super.init()
#if !os(watchOS)
guard WCSession.isSupported() else {
return
}
#endif
WCSession.default.delegate = self
WCSession.default.activate()
}
public func send(authorized: Bool, errorHandler: ((Error) -> Void)? = nil) {
guard WCSession.default.activationState == .activated else {
return
}
#if os(watchOS)
guard WCSession.default.isCompanionAppInstalled else {
return
}
#else
guard WCSession.default.isWatchAppInstalled else {
return
}
#endif
let authorizationInfo: [String: Bool] = [
DefaultsKeys.authorized: authorized
]
WCSession.default.sendMessage(authorizationInfo, replyHandler: nil)
WCSession.default.transferUserInfo(authorizationInfo)
}
}
extension ConnectivityService: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { }
func session(
_ session: WCSession,
didReceiveUserInfo userInfo: [String: Any] = [:]
) {
let key = DefaultsKeys.authorized
guard let authorized = userInfo[key] as? Bool else {
return
}
self.authorized = authorized
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
self.authorized = true
}
#if os(iOS)
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
WCSession.default.activate()
}
#endif
}
I tried doing these two lines but they have varying results:
WCSession.default.sendMessage(authorizationInfo, replyHandler: nil)
WCSession.default.transferUserInfo(authorizationInfo)
In the first line, XCode will say that no watch app could be found, even though I'm connected to both physical devices through XCode; launch phone first then watch. I believe the first one is immediate and the second is more of when the queue feels like it. Sometimes if I hard close the watch app, it'll pick up the state change in the authorized variable, sometimes it won't. Very frustrating inter-device communication.
UserDefaults doesn't pick up the access token value on the watch side. Maybe I have to use App Groups?
I do see this error on the Watch side:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
So I thought to try and encapsulate the self.authorized = authorized call into something like:
DispatchQueue.main.async {
self.authorized = authorized
}
But it didn't do anything as far as solving the immediate state change issue.

iOS SWIFT: Unable to delete user from Firebase Database

I want to delete my current user from Firebase. The authenticated user gets deleted however, I am unable to delete the data for that user in the database. What am i doing wrong?
This is my delete user method....
FIRAuth.auth()?.signIn(withEmail: (emailTextField?.text)! , password: (passwordTextField?.text)!, completion: { (user, error) in
if error == nil {
print("User Authenticate!!!")
let user = FIRAuth.auth()?.currentUser
user?.delete(completion: { (error) in
if error != nil {
print("Error unable to delete user")
} else {
DataService.ds.deleteCurrentFirebaseDBUser()
KeychainWrapper.standard.removeObject(forKey: KEY_UID)
self.performSegue(withIdentifier: "goToLogin", sender: nil)
}
})
} else {
//Password was wrong, unable to authenicate user. Data is not updated
print("!!!ALERT!!! Unable to authenticate user")
let alert = UIAlertController(title: "Incorrect Password", message: "Please re-enter your password", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
})
Firebase Rules:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
Database:
App
-> users
->
4erkjkl543jfe46
->name
->email
ERRORS:
2017-01-21 21:33:10.321704 APP[11582:4102711] [FirebaseDatabase] setValue: or removeValue: at /users/4erkjkl543jfe46 failed: permission_denied
Optional(Error Domain=com.firebase Code=1 "Permission denied" UserInfo={NSLocalizedDescription=Permission denied})
I'm having the same issue. You are not able to make use of your function deleteCurrentFirebaseDBUser() because the Firebase delete function (if successful) removes the user auth object.
As a result user is not authenticated anymore at the time you want to delete user's data in database with deleteCurrentFirebaseDBUser().
Currently I delete user's data in database before Firebase delete function which is not the ideal solution.
We can delete user from both side authentication and database.But before that we need to reauthenticate user first then we get latest token to delete the user.
Here is the pretty code:
let user = Auth.auth().currentUser
user?.reauthenticate(with:credential) { error in
if let error = error {
// An error happened.
showAlertWithErrorMessage(message: error.localizedDescription)
} else {
// User re-authenticated.
user?.delete { error in
if let error = error {
// An error happened.
showAlertWithErrorMessage(message: error.localizedDescription)
} else {
// Account deleted.
let userID = HelperFunction.helper.FetchFromUserDefault(name: kUID)
Database.database().reference(fromURL: kFirebaseLink).child(kUser).child(userID).removeValue()
try! Auth.auth().signOut()
showAlertWithErrorMessage(message: "Your account deleted successfully...")
return
}
}
}
}
100% working in my project and well tested
for just to delete a child from Firebase use "removeValue()"
var db: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
db = Database.database().reference()
deleteByID()
}
func deleteByID(){
db.child("YOURID").removeValue()
}
Swift 5 | Firebase 8.11.0
As #SvshX said, deleting the user data before deleting the actual user is the only available solution.
The problem with this method is that deleting the user might give an error like AuthErrorCode.requiresRecentLogin, then the data will be deleted but the user will not.
This error is given when the last authentication of the user was more than 5 minuets ago (from Firebase Docs)
So, fixing both of the issues can be achieved by using DispatchGroup and checking the lastSignInDate.
This is my final solution (just call deleteUserProcess()):
let deleteDataGroup = DispatchGroup()
func deleteUserProcess() {
guard let currentUser = Auth.auth().currentUser else { return }
deleteUserData(user: currentUser)
// Call deleteUser only when all data has been deleted
deleteDataGroup.notify(queue: .main) {
self.deleteUser(user: currentUser)
}
}
/// Remove data from Database & Storage
func deleteUserData(user currentUser: User) {
// Check if `currentUser.delete()` won't require re-authentication
if let lastSignInDate = currentUser.metadata.lastSignInDate,
lastSignInDate.minutes(from: Date()) >= -5 {
deleteDataGroup.enter()
Database.database().reference().child(userId).removeValue { error, _ in
if let error = error { print(error) }
self.deleteDataGroup.leave()
}
// Delete folders from Storage isn't possible,
// so list and run over all files to delete each one independently
deleteDataGroup.enter()
Storage.storage().reference().child(userId).listAll { list, error in
if let error = error { print(error) }
list.items.forEach({ file in
self.deleteDataGroup.enter()
file.delete { error in
if let error = error { print(error) }
self.deleteDataGroup.leave()
}
})
deleteDataGroup.leave()
}
}
}
/// Delete user
func deleteUser(user currentUser: User) {
currentUser.delete { error in
if let error = error {
if AuthErrorCode(rawValue: error._code) == .requiresRecentLogin {
reauthenticate()
} else {
// Another error occurred
}
return
}
// Logout properly
try? Auth.auth().signOut()
GIDSignIn.sharedInstance.signOut()
LoginManager().logOut()
// The user has been deleted successfully
// TODO: Redirect to the login UI
}
}
func reauthenticate() {
// TODO: Display some UI to get credential from the user
let credential = ... // Complete from https://stackoverflow.com/a/38253448/8157190
Auth.auth().currentUser?.reauthenticate(with: credential) { _, error in
if let error = error {
print(error)
return
}
// Reload user (to update metadata.lastSignInDate)
Auth.auth().currentUser?.reload { error in
if let error = error {
print(error)
return
}
// TODO: Dismiss UI
// Call `deleteUserProcess()` again, this time it will delete the user
deleteUserProcess()
}
}
}
The minuets function can be added in an extension to Date (thanks to Leo Dabus):
extension Date {
/// Returns the amount of minutes from another date
func minutes(from date: Date) -> Int {
return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
}

How to get MSMessage summary text from Received Message?

I am passing summary message in MSMessage , but when try to get when message received at other end, it returns nil.
Below is code for create Message.
fileprivate func composeMessage(with url: String, andEventInfo eventInfo: NSDictionary?) -> MSMessage {
let message = MSMessage(session:MSSession())
message.url = URL(string: url)
message.layout = createTemplateForEvent(eventInfo: eventInfo!)
message.summaryText = "SAMPLE MESSAGE"
return message
}
To Send Message in Current Conversation
let message = composeMessage(with: url!,andEventInfo: eventInfo)
activeConversation?.insert(message, completionHandler: { (error) in
print(error)
})
Now, At receiving end
Here summaryText returns nil.
override func didReceive(_ message: MSMessage, conversation: MSConversation) {
print("DID RECEIVE MESSAGE: \(message.summaryText)")
}
Also when user tap on message, then also it returns nil
override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
guard let conversation = activeConversation else { fatalError("Expected an active converstation") }
// Present the view controller appropriate for the conversation and presentation style.
if presentationStyle == .expanded {
if conversation.selectedMessage != nil {
print(conversation.selectedMessage?.summaryText)
presentViewController(for: conversation, with: presentationStyle)
}
}
}
Any one have idea, why this happens or any thing is going wrong ?
May this will be helpful
override func didReceive(_ message: MSMessage, conversation: MSConversation) {
// Called when a message arrives that was generated by another instance of this
// extension on a remote device.
// Use this method to trigger UI updates in response to the message.
guard let messageURL = message.url else { return }
guard let urlComponents = NSURLComponents(url: messageURL, resolvingAgainstBaseURL: false), let queryItems = urlComponents.queryItems else { return }
print("URL Components", urlComponents)
print("queryItems", queryItems)
for item in queryItems {
print("Received \(item.name) with value \(item.value)")
}
}
Reference & helped Source: https://www.hackingwithswift.com/ios10
And Also Refer : iOS10 iMessage : Unable to insert data into iMessage using MSConversation

Notifications with Swift 2 and Cloudkit

I am making a "texting app" you can call it and it uses cloudkit and I have been looking everywhere to add notifications that work with cloudkit... Would someone be able to tell me the code to add push notifications for cloudkit in detail because I am very lost... Also I wan't the notifications to go to different "texting rooms" (in cloudkit it would be record types...) For instance I have one record type called "text" and another one called "text 2" I don't want notifications from "text" to get to people who use "text2" and vise versa.
Using Swift 2.0 with El Captain & Xcode 7.2.1
Elia, You need to add this to your app delegate. Which will arrive in a userInfo packet of data, which you can then parse to see which database/app sent it.
UIApplicationDelegate to the class
application.registerForRemoteNotifications() to the
func application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Than this method
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let notification = CKQueryNotification(fromRemoteNotificationDictionary: userInfo as! [String : NSObject])
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
if queryNotification.queryNotificationReason == .RecordUpdated {
print("queryNotification.recordID \(queryNotification.recordID)")
// Your notification
}
}
print("userInfo \(userInfo["ck"])")
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: self, userInfo:dataDict)
}
}
}
}
}
That'll get you started.
You can use this method to check your subscriptions programmatically, of course while your developing you can use the dashboard.
func fetchSubsInPlace() {
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
publicDB.fetchAllSubscriptionsWithCompletionHandler({subscriptions, error in
for subscriptionObject in subscriptions! {
let subscription: CKSubscription = subscriptionObject as CKSubscription
print("subscription \(subscription)")
}
})
}
And finally when you got it; you can this routine to ensure you capture any subscriptions you missed while your app was sleeping and make sure that subscriptions don't go to all your devices, once you treated them too.
func fetchNotificationChanges() {
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
var notificationIDsToMarkRead = [CKNotificationID]()
operation.notificationChangedBlock = { (notification: CKNotification) -> Void in
// Process each notification received
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
let reason = queryNotification.queryNotificationReason
let recordID = queryNotification.recordID
print("reason \(reason)")
print("recordID \(recordID)")
// Do your process here depending on the reason of the change
// Add the notification id to the array of processed notifications to mark them as read
notificationIDsToMarkRead.append(queryNotification.notificationID!)
}
}
operation.fetchNotificationChangesCompletionBlock = { (serverChangeToken: CKServerChangeToken?, operationError: NSError?) -> Void in
guard operationError == nil else {
// Handle the error here
return
}
// Mark the notifications as read to avoid processing them again
let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
markOperation.markNotificationsReadCompletionBlock = { (notificationIDsMarkedRead: [CKNotificationID]?, operationError: NSError?) -> Void in
guard operationError == nil else {
// Handle the error here
return
}
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(markOperation)
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(operation)
}
}

HealthKit Observer not working while app is in background mode

I have followed Apple Docs and Several threads on stackoverflow on how to achieve background fetching of data from Health Store.
So far I have:
Added HealthKit Entitlement to my appID
Added Required Background Modes
Added the code to AppDelegate.swift as Apple suggest (the snippet below is not following OOP just for facility to state this way here)
This is my code (swift):
If your answer is in Obj-C and works, please state it as well, I will have to translate it, but that's no problem.
AppDelegate.swift
var healthStore: HKHealthStore?
var bpmSamples: [HKQuantitySample]?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let dataTypesToWrite = [ ]
let dataTypesToRead = [
HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate),
HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex),
HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth)
]
if self.healthStore == nil {
self.healthStore = HKHealthStore()
}
self.healthStore?.requestAuthorizationToShareTypes(NSSet(array: dataTypesToWrite as [AnyObject]) as Set<NSObject>,
readTypes: NSSet(array: dataTypesToRead) as Set<NSObject>, completion: {
(success, error) in
if success {
self.addQueryObserver()
println("User completed authorisation request.")
} else {
println("The user cancelled the authorisation request. \(error)")
}
})
return true
}
func addQueryObserver(){
let sampleType =
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let query = HKObserverQuery(sampleType: sampleType, predicate: nil) {
query, completionHandler, error in
if error != nil {
// Perform Proper Error Handling Here...
println("*** An error occured while setting up the stepCount observer. \(error.localizedDescription) ***")
abort()
}
println("query is running")
self.performQueryForHeartBeatSamples()
completionHandler()
}
healthStore?.executeQuery(query)
healthStore?.enableBackgroundDeliveryForType(sampleType, frequency:.Immediate, withCompletion:{
(success:Bool, error:NSError!) -> Void in
let authorized = self.healthStore!.authorizationStatusForType(sampleType)
println("HEALTH callback success", success)
println("HEALTH callback authorized", sampleType)
})
if HKHealthStore.isHealthDataAvailable() == false {
println("HEALTH data not available")
return
} else {
println("HEALTH OK")
self.performQueryForHeartBeatSamples()
}
}
// MARK: - HealthStore utility methods
func performQueryForHeartBeatSamples() {
let endDate = NSDate()
let startDate = NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitMonth, value: -2, toDate: endDate, options: nil)
var heartRate : HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let query = HKSampleQuery(sampleType: heartRate, predicate: predicate, limit: 0, sortDescriptors: nil, resultsHandler: {
(query, results, error) in
if results == nil {
println("There was an error running the query: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.bpmSamples = results as? [HKQuantitySample]
let heartRateUnit: HKUnit = HKUnit.countUnit().unitDividedByUnit(HKUnit.minuteUnit())
if self.bpmSamples?.count > 0 {
if let sample = self.bpmSamples?[self.bpmSamples!.count - 1] {
println(sample.quantity!.description)
let quantity = sample.quantity
var value = quantity.doubleValueForUnit(heartRateUnit)
println("bpm: \(value)")
}
}
else {
println("No Data")
}
}
})
self.healthStore?.executeQuery(query)
}
So, the problem is that I only receive updates when I resume my app from background to active state manually.. HKObserverQuery doesn't seems to be working for me while on background mode.
Any suggestions?
In my experiences, frequency ".Immediate" doesn't work well. The handler of queries are inserted into background queue. If the matching samples are frequently added or iOS is busy, the immediate frequency doesn't work well.
In addition, you cannot use HKSampleQuery in HKObserverQuery. updateHandler of HKObserverQuery may work, but resultHandler of HKSampleQuery will not. The handler of an observer query can be executed in the background mode but the one of a sample query cannot be executed in the background mode.
You should know that ONLY HKObserverQuery can be used in the background

Resources