How to clear a remote pushed notification for iOS?
Asked Answered
C

3

6

So I've been reading up on remote notifications, and have finally made it work; our app is receiving notifications from our server. We now want to remove or update an unread notification if a certain condition meets on our server (e.g the notification is no longer valid). I understand that "silent" notifications are the only way to go, but I am still confused as to how. If a silent notification triggers my app to wake up, I would be able to schedule local notifications, but will I be able to remove already existing remote notifications?

Is the only solution to exclusively use silent notifications from the server, and schedule all notifications as local notifications with a custom identifier which I can later remove? E.g, can I never use fire&forget remote push notifications from my server to devices if I want this feature?

Edit: This app supports down to iOS 9 :/

Cabotage answered 11/10, 2017 at 13:23 Comment(0)
C
5

I ended up doing it like TawaNicolas suggested in his answer, by getting the received notifications with getDeliveredNoti...., then check the userInfo of every notification to find which ones I wanted to delete. I stored the removable notifications' identifiers in an array, and called removeDelivered....

This is exactly as his answer suggests, but this didn't work at first, and I had a hard time finding out why. I'm still not completely sure I have fixed it, but my tests shows that it's working - and my solution somewhat makes sense.

The thing was, inside the didReceiveRemote..-function, you have to call completionHandler(.newData) at the end. This is to notify the NotificationCenter that something has changed. I started to suspect that this callback was called before the removable notifications actually got removed. I checked the documentation, and removeDeliveredNotifications is indeed async. This means that when I do this:

UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removableIDs)
completionHandler(.newData)

it is not guaranteed that the first function is completed before calling the second function. This means that the completionHandler, which tells my NotificationCenter that something has been updated, is completed first, and THEN the notifications gets removed from the system. (NotificationCenter apparently does not invoke its own completionHandler to update the UI after that function).

All this may or may not happen. As I was experiencing; when connected to the debugger, the removeDeliveredNotifications-function was so fast that it was always completed before the completionHandler was invoked, meaning that the notifications were removed before updating the system. So everything looked great when developing this. When I disconnected from the debugger, the removeDeliveredNotifications-function was slightly slower, and since it's async, it was completed after I invoked the completionHandler - causing the system to update too soon.

The best way to solve this would be for Apple to give us a completionBlock for removeDeliveredNotifications and call our completionHandler inside it, but they haven't.

To solve this now I have gone dirty by adding a fixed delay of 0.2 seconds. It could probably be lower than 0.2, but it isn't really important with a second from or to for what we're doing.

This is a class and function I created to easily delay something from anywhere:

class RuntimeUtils{
    class func delay(seconds delay:Double, closure:@escaping ()->()){
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay*Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
    }
}

And here I use it inside didReceiveRemoteNotification: in AppDelegate:

UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removableIDs)
RuntimeUtils.delay(seconds: 0.2, closure: {
    completionHandler(.newData)
})

After adding this delay to the completionHandler, it is always executed after my deletion of notification, and it seems to work every time, with or without debugger connected.

This was a disgusting problem, with a nasty fix.

Cabotage answered 7/12, 2017 at 12:49 Comment(5)
Thanks for the detailed answer, I just implemented the same things in our app and will report whether your delay fix will be necessary in prd as well. Without the debugger it still worked properly in a local dev setup, but let's see.Welter
@Welter This was 4 years ago. Do you actually still need to support iOS 9? If not, there are other solutions.Cabotage
Indeed it worked without the workaround, reliably so far. Still thanks for your documentation and effort.Welter
@Cabotage hello, i have implemented same code, as you explained instead of remving specif notiifcation it removes all notifications in the notification center (tray) please help meHelmuth
@ArpitBParekh This was 6 years ago and was a hack to support notification in iOS 9. Surely you don't support down to iOS 9 anymore? You can use UNUserNotificationCenter now.Cabotage
L
4

After your app wakes up from the silent notification, you need to loop over received notifications using:

Objective-C:

[[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {

}];

Swift:

UNUserNotificationCenter.current().getDeliveredNotifications { (notifications: [UNNotification]) in

}

And then check each notification's request.content.userInfo property in order to find out if that's the notification that you want to remove.

Then, you use the following to remove it from the notification center:

Objective-C:

[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[identifier]];

Swift:

UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])

Where identifier is the notification.request.identifier property.

Edit: If you want to identify the right notification, you have to find out what you sent in the payload that is in userInfo that can identify the right one. (Maybe a userID or an eventID? This depends on your project.)

Lansquenet answered 11/10, 2017 at 13:31 Comment(8)
Good tip! Each notification will contain a custom ID-field to identify which should be removed. However, this app has to support down to iOS 9, and from what I gather, I can't use UNUserNotificationCenter :( But a great answer!Cabotage
@Cabotage thank you, and honestly, it sucks that you have to support iOS 9 regarding this. Cause I don't think it was possible before iOS 10. But anyways, you can make your app support this feature using iOS 10 and above, but for iOS 9 it should have the default behavior, which means that the app clears all the notifications when it enters the foreground.Lansquenet
Followup-question.. What part of the app "wakes up" from the silent notification? I don't understand where I should put his code..Cabotage
@Cabotage I'm not certain, but I think this is the method that handles it: developer.apple.com/documentation/uikit/uiapplicationdelegate/…Lansquenet
Hey, I ended up doing this for iOS 10+, but I'm experiencing something weird.. Everything works perfectly when I'm connected to the debugger, but when installing this app in AdHoc/TestFlight/AppStore it seems like my "Background"-work isn't happening at all. I am just implementing didReceiveRemoteNotification... and using getDeliveredNotifications, find any notifications matching my relevant userInfo-value, and calling removeDeliveredNotifications with the other ID's than the last one. This works perfectly when connected to debugger, but seems to work 10% of the time when testing releaseCabotage
@Cabotage What is working only 10% of the time? Is it receiving the notifications or getting the delivered ones?Lansquenet
I always receive the notifications. But my intention is to replace the old notifications with the new notifications. 90% of the time, when I receive the second notification, they will both show on the lock screen. 10% of the time (or 100% when debugging), the old notification will be removed when the new notification appears, which is what I want to happen.Cabotage
None of that code will run while your app is suspended; iOS will receive and display notifications on your behalf, according to the notification payload. Running with the debugger attached keeps iOS from suspending your app (for some amount of time, at least), so that's why you're seeing a difference. If you need the ability to intercept notifications and execute code while your app is suspended, you need to implement a Notification Service Extension: developer.apple.com/documentation/usernotifications/…Caroylncarp
M
2

For iOS >= 10 you can use UNUserNotificationCenter.current().getDeliveredNotifications and/or UNUserNotificationCenter.current().removeAllDeliveredNotifications

You will have to add import UserNotifications as well, it is a nice new notifications framework.

Mazzola answered 11/10, 2017 at 13:36 Comment(1)
Oh man, looks nice, but our app has to support down to iOS 9 :(Cabotage

© 2022 - 2024 — McMap. All rights reserved.