Reputation: 7565
I have an Apple Watch app with a button that can be tapped to log events. When an event is logged, I want to cancel all pending local notifications in the app. The problem is that sometimes the local notification get cancelled, while other times they do not.
Here is the code from the Watch app. When the button is tapped, I send a message to the iPhone app:
func buttonTapped() {
// check to make sure the session is reachable, so a message can be sent
if session.reachable == true {
// a dict containing the date of the event to be sent to the iPhone
let dict = ["date" : NSDate()]
// send the dict and specify the replyHandler
session.sendMessage(dict, replyHandler: { (reply) in
if reply["success"] as? Bool == true {
print("notifications were cancelled")
}
}, errorHandler: { (error) in
// some kind of error has occurred
}
)
}
}
In AppDelegate on the iPhone, I have implemented the WatchConnectivity delegate method to receive the message, which is where the notifications get cleared. Then, the replyHandler is called to indicate to the Watch app that the message was received and processed successfully:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if message["date"] != nil {
dispatch_async(dispatch_get_main_queue()){
let reply = ["success" : true]
UIApplication.sharedApplication().cancelAllLocalNotifications()
// send the reply to the Watch to confirm all this has happened
replyHandler(reply)
}
}
}
Even when I see that the successful reply came back to the Watch, the local notifications are sometimes not actually cancelled.
Does it seem likely that this is an iOS or watchOS bug, and not something I can work around? Maybe certain APIs are not guaranteed to be accessible when the app is launched in the background? (which seems to be what happens when sending a message with WatchConnectivity)
Upvotes: 1
Views: 72
Reputation: 7565
When a message is being received in AppDelegate this way, the app is being launched in the background (even if it doesn't show up in the Multitasking screen). But since I wasn't explicitly asking iOS for background time, my code in didReceiveMessage
wasn't always being fully executed.
The solution was to specifically ask for background time. Here's how I changed didReceiveMessage
to get it working:
// initialize a background task identifier
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if message["date"] != nil {
dispatch_async(dispatch_get_main_queue()){
// begin the background task
self.backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithName("CancelNotifications", expirationHandler: {
// this expirationHandler will be called to end and invalidate the background task in case it doesn't have enough time to complete
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
})
let reply = ["success" : true]
UIApplication.sharedApplication().cancelAllLocalNotifications()
// send the reply to the Watch to confirm all this has happened
replyHandler(reply)
// let iOS know the background task is done
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
}
}
Big thank you to David Walsh, creator of HeartWatch (an awesome Apple Watch and iPhone app), for showing me how to solve this problem!
Upvotes: 1