5

Up until recently (I believe prior to iOS 12 release), removing remote push notifications from the Notification Center worked as expected using removeDeliveredNotifications.

Suddenly, without any code change in the Notification Service Extension, notifications are not removed anymore.

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {

    self.contentHandler = contentHandler
    self.content = request.content.mutableCopy() as? UNMutableNotificationContent

    guard let content = content else {
        contentHandler(request.content)
        return
    }

    UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
        let matchingNotifications = notifications.filter({ $0.request.content.threadIdentifier == "myThread" && $0.request.content.categoryIdentifier == "myCategory" })
        UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: matchingNotifications.map({ $0.request.identifier }))
        contentHandler(content)
    }
}

The function just completes without removing the notification. When debugging on a real device, it shows that matchingNotifications contains notifications and the notification IDs to remove are correctly provided.

For testing, calling removeAllDeliveredNotifications() works and removes all notifications.

The function above is called in override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void)

What is the problem here?

Manuel
  • 14,274
  • 6
  • 57
  • 130
  • 2
    Hey @Manuel, did you find any solution to solve this problem ? – Skaal Jan 10 '19 at 11:09
  • @Skaal No, it is still an issue. Please post here if you find a solution. – Manuel Jan 11 '19 at 12:23
  • Can you post your complete `didReceive` implementation? I believe it's related to *when* you call the `contentHandler` completion. – Kymer Jan 25 '19 at 23:41
  • @Kymer Completion handler is called after removing the notifications, see updated code. Again, it *always* worked fine for months until suddenly it stopped working. – Manuel Jan 26 '19 at 00:12
  • From what I have read this behaviour indeed changed since iOS 12. The delete method returns immediately but executes asynchronously, calling the `contentHandler` unloads/destroys your extension (I assume) which may stop the asynchronous deletion. Can you try delay calling `contentHandler` for testing purposes? (delay for a couple of seconds for example) – Kymer Jan 26 '19 at 00:31
  • @Kymer I will test this and post an update. Do you have a reference for this new behavior? – Manuel Jan 26 '19 at 00:40
  • @Manuel It's not documented officially, just saw some people online experiencing the same behaviour since iOS 12. (a comment on [this blogpost](https://medium.com/the-guardian-mobile-innovation-lab/how-to-replace-the-content-of-an-ios-notification-2d8d93766446) by the Guardian for example) – Kymer Jan 26 '19 at 17:01
  • @Kymer have anyone founded the solution? It's only iOS 12+ problem. I'll much appreciate – Vlad Pulichev May 06 '19 at 09:38

1 Answers1

7

I tried the suggestion by @Kymer and verified that calling contentHandler after waiting some time (e.g. 3 seconds) resolved the issue for me, e.g.

// UNUserNotificationCenter *notificationCenter
// NSArray(NSString *) *matchingIdentifiers;
// UNNotificationContent *content;
if (matchingIdentifiers.count > 0) {
    NSLog(@"NotificationService: Matching notification identifiers to remove: %@.", matchingIdentifiers);               
    [notificationCenter removeDeliveredNotificationsWithIdentifiers:matchingIdentifiers];

    // Note: dispatch delay is in nanoseconds... :(
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3000000000), dispatch_get_main_queue(), ^{
        NSLog(@"Replacing content after 3 seconds.");
        self.contentHandler(content);
    });
}

So I think this means it's a timing issue, with iOS freezing the process aggressively after the contentHandler is invoked, and removing any pending removal requests in the notificationCenter

EDIT: Although the question wasn't about how to deal with it, the comment section brought concerns about an arbitrary time delay. In my testing, posting the callback on another loop sufficed, e.g.

dispatch_async(dispatch_get_main_queue(), ^{
    contentHandler(content);
});
  • If an arbitrary delay is the only solution, then this must be a bug. – Manuel Jun 18 '19 at 22:38
  • This workaround worked. I did not try to find the minimum required delay time, but empirically it is enough to use 500ms as delay. Side note: the delay may have a visible effect to the user in the notification center, because notifications are removed immediately when calling `removeDeliveredNotifications`, but whatever code comes next is executed after the delay. – Manuel Jun 30 '19 at 02:49
  • BTW: you don't need to add an arbitrary dispatch delay. i verified it works by posting the callback to self.contentHandler(content) on another loop, e.g. the next poll on the main queue `dispatch_async(dispatch_get_main_queue(), ^{ contentHandler(content); });` (sorry can't figure out code block in mini-Markdown) – Simon Bocanegra Thiel Aug 28 '19 at 20:08
  • @SimonBocanegraThiel that never worked for me, I used a 0.1 delay instead. – Lucas van Dongen May 14 '20 at 18:34