20

I am banging my head. I am implementing push notification. Everything is working fine (push is received, badge is updated) but under iOS 13.3 the method application(_:didReceiveRemoteNotification:fetchCompletionHandler:) is not called when the app is in the background. If the app is in the foreground or using an iOS 12 device the method is called. I register for push notification in the following way:

[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication] registerForRemoteNotifications];
        });
    }
}];

The payload is set to the following

{"aps": {
    "badge": 10,
    "alert": "test",
    "content-available": 1
}}

I tried adding "Remote notifications" and "Background processing" as app capabilities in all variations (only "Remote notifications"/"Background processing", without any of those capabilities, enabling both) without any change. I set the delegate for the UNUserNotificationCenter but again without success. I set the headers accordingly:

curl -v \
 -H 'apns-priority: 4' \
 -H 'apns-topic: xx.xxxxx.xxxx' \
 -H 'apns-push-type: alert' \
 -H 'Content-Type: application/json; charset=utf-8' \
 -d '{"aps": {"badge": 10,"alert": "test", "content-available":1}}' \
 --http2 \
 --cert pushcert.pem \
 https://api.sandbox.push.apple.com/3/device/1234567890

From the docs it states that this method is called even when the app is in background:

Use this method to process incoming remote notifications for your app. Unlike the application:didReceiveRemoteNotification: method, which is called only when your app is running in the foreground, the system calls this method when your app is running in the foreground or background.

What am I missing here for iOS 13?

mfaani
  • 33,269
  • 19
  • 164
  • 293
MartinW1985
  • 268
  • 1
  • 2
  • 8
  • Check if you are using this method: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application?language=objc –  Jan 23 '20 at 11:57
  • 1
    Please see above: Everything is working fine (push is received, badge is updated) but under iOS 13.3 the method application(_:didReceiveRemoteNotification:fetchCompletionHandler:) is not called when the app is in the background. – MartinW1985 Jan 23 '20 at 15:56
  • application(_:didReceiveRemoteNotification:fetchCompletionHandler:) will be called when you tap on the notification banner –  Jan 24 '20 at 04:41
  • 1
    Yes, this is correct. My question is: Why is it not called when the app is in background. From my understanding, the docs say so. – MartinW1985 Jan 27 '20 at 08:38
  • Have you tried by implementing `application(_:didReceiveRemoteNotification:withCompletionHandler:)` method? – HardikS Jan 27 '20 at 09:05
  • @HardikS: yes. But beside beeing deprecated, the method is not called on an iOS device running 13.3 when in background. – MartinW1985 Feb 04 '20 at 16:04
  • when your application is in background and notification will come, at that time I think there is no one method called. You must tap on the notification to called this method. – HardikS Feb 05 '20 at 04:50
  • @MartinW1985 Have you found solution for this? I am also facing the same :( – Beu Apr 24 '20 at 05:43
  • Yes, you have to implement Apple's preferred way which is a notification extension. After that, you have to adapt the payload to include "mutable-content". Very easy. It would have saved me hours of testing and researching if it was mentioned correctly in the documentation. – MartinW1985 Apr 25 '20 at 12:12
  • this is how I receive the notificationpayload while in the background: https://stackoverflow.com/a/66247596/4833705 – Lance Samaria Feb 17 '21 at 18:18
  • 1
    @MartinW1985 can you explain what is Apple's preferred way? I am stuck on same problem that you faced. – S.S.D Nov 03 '22 at 16:27

9 Answers9

13

Have you set

"content-available": 1

in you backend APS payload?

Also need to make sure you have enabled background mode in your iOS App's info.plist file

<key>UIBackgroundModes</key>
<array>
    <string>processing</string>
    <string>remote-notification</string>
</array>
Zhang Zhan
  • 815
  • 8
  • 27
  • @matt the post was accidentally submitted before I type in all the answers. Now it is completed with app side change. – Zhang Zhan Feb 05 '20 at 05:43
  • 2
    Yes, I did. You can see from my CURL request in my initial post. I also added "content-available": 1 and tried all background modes (enabled, disabled, variations) but still application(_:didReceiveRemoteNotification:fetchCompletionHandler:) is not getting called. Only if the app is active. – MartinW1985 Feb 06 '20 at 09:11
  • 1
    processing => this is importnant line – Dong Phong Mar 23 '20 at 04:45
  • @ZhangZhan: Did you ever solve this problem? I am running into the same problem where silent/background notifications are not received even when content-available key is set correctly. On iOS 12, everything works properly but on iOS13, only the audible/visual notifications are working. I am aware that apple requires a new header type to be included in notification but that is already handled (we are using SNS). Interestingly, this isn't working in the simulator with the apns file with latest XCode version. – whawhat Apr 18 '20 at 00:13
6

This delegate method : -

-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{

is getting invoked when we click on notification for iOS 13 and app is in background.

JSA986
  • 5,870
  • 9
  • 45
  • 91
user13150044
  • 61
  • 1
  • 1
4

I spend a support ticket to get an answer to this problem.

It turns out that the documentation is not 100% "valid" for iOS 13 on this topic. The device decides whether to wake up or not. Although the documentation states a little different.

Apple's preferred way of implementation as a notification extension. After that, you have to adapt the payload to include "mutable-content".

I asked the support afterward if I should file a radar and they replied with "yes".

MartinW1985
  • 268
  • 1
  • 2
  • 8
  • if you provide a notification extension, is the app woken up? – Peter Lapisu Apr 30 '20 at 09:36
  • The extension of course runs as a separate process with a separate bundle identifier. So if you're trying to update some data in the app then you need to use some app group data sharing approach. – androidguy Nov 22 '20 at 10:20
1

Need activated "BackGround fetch" go to File Project - signIn capabilities - BackgroundModes - and check Background fetch and Remote notifications as well

remember in didFinishAlunchWithOptions

    Messaging.messaging().delegate = self
    UNUserNotificationCenter.current().delegate = self

with that I have a response in method - didReceive response - in background but only if tap the notification.

I found this info... but i dont know if is real

"If this is a new system behavior from iOS, then it's unlikely that FCM will be able to provide a workaround. A few things to note:

Apple's docs mention that silent notifications will be throttled by the system to conserve power. In the past, we've seen that testing silent notifications with your device plugged in to a power source will reduce throttling. In older iOS versions, Apple's docs have mentioned that force-quitting an application will prevent wakeups from silent notifications entirely. I'm not sure if this is still the case."

I Still looking around for the notifications in background for update badge icon in the app, at the momen I receive the notification.

0

Implement didRegisterForRemoteNotificationsWithDeviceToken and also didFailToRegisterForRemoteNotificationsWithError in your app's delegate, to check if the device makes a good connection with Apple's APN server. If this is not the case, restart the device and/or try to make a connection via another Wi-Fi network and restart the app.

Ely
  • 8,259
  • 1
  • 54
  • 67
0

I had the same problem. Read this :https://medium.com/fenrir-inc/handling-ios-push-notifications-the-not-so-apparent-side-420891ddf10b

I use Iphone XS Software Version 13.7

1. Before iOS 10, using the UIApplication method: registerUserNotificationSettings(:) iOS 10 onwards, using the UserNotifications framework methods: requestAuthorization(options:completionHandler:) setNotificationCategories(:)

if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
        {
            UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound,
                                                                    (granted, error) => InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications));
        }
        else if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
        {
            var pushSettings = UIUserNotificationSettings.GetSettingsForTypes(
                    UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound,
                    new NSSet());

            UIApplication.SharedApplication.RegisterUserNotificationSettings(pushSettings);
            UIApplication.SharedApplication.RegisterForRemoteNotifications();
        }
        else
        {
            UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
            UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
        }

“Push Notifications”, "Remote notifications" and Background Modes capability is required (info.plist and Entitlements.plist)

3.

Implement DidReceiveRemoteNotification method and RegisteredForRemoteNotifications

After this change the DidReceiveRemoteNotification method was called.

Try this to silent Push Notification:

{
    "aps" : {
        "alert" : "",
        "content-available" : 1
    },
}

Try this to Normal Push Notification:

{
    "aps" : {
        "alert" : "Alert!!!"
    },
}
0

I was facing the same problem too.

But it works after I reboot my device.

I'm using iOS 15.2

IMLeoZhao
  • 1
  • 1
0

I've ran into this problem as well with iOS 15. I've noticed that didReceiveRemoteNotification does not get called in the background if I build the target for Debug, but it does get called just fine if it's built for Release.

Alejandro
  • 1
  • 1
  • 1
    This may just have been coincidence. I am seeing it work/not work on and off. Sometimes rebooting the device appears to fix it, sometimes not. I get periods of it working for a day or so, then it quits working. Terribly frustrating. – Alejandro Mar 14 '22 at 23:03
-1

You need to implement a Notification Content Extension

As i was using OneSignal and it's setup code, this worked fine for me https://documentation.onesignal.com/docs/ios-sdk-setup

not sure if the OneSignal bits make a difference, but appending them anyway

import UserNotifications
import OneSignal

class NotificationService: UNNotificationServiceExtension {
    
    var contentHandler: ((UNNotificationContent) -> Void)?
    var receivedRequest: UNNotificationRequest!
    var bestAttemptContent: UNMutableNotificationContent?
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.receivedRequest = request;
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        if let bestAttemptContent = bestAttemptContent {
            OneSignal.didReceiveNotificationExtensionRequest(self.receivedRequest, with: self.bestAttemptContent)
            contentHandler(bestAttemptContent)
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            OneSignal.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent)
            contentHandler(bestAttemptContent)
        }
    }
    
}

ObjC

#import <OneSignal/OneSignal.h>

#import "NotificationService.h"

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNNotificationRequest *receivedRequest;
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.receivedRequest = request;
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    //If your SDK version is < 3.5.0 uncomment and use this code:
    /*
    [OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest
                       withMutableNotificationContent:self.bestAttemptContent];
    self.contentHandler(self.bestAttemptContent);
    */
    
    /* DEBUGGING: Uncomment the 2 lines below and comment out the one above to ensure this extension is excuting
                  Note, this extension only runs when mutable-content is set
                  Setting an attachment or action buttons automatically adds this */
    // NSLog(@"Running NotificationServiceExtension");
    // self.bestAttemptContent.body = [@"[Modified] " stringByAppendingString:self.bestAttemptContent.body];
    
    // Uncomment this line to set the default log level of NSE to VERBOSE so we get all logs from NSE logic
    //[OneSignal setLogLevel:ONE_S_LL_VERBOSE visualLevel:ONE_S_LL_NONE];
    [OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest
                       withMutableNotificationContent:self.bestAttemptContent
                                   withContentHandler:self.contentHandler];
}

- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    
    [OneSignal serviceExtensionTimeWillExpireRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];
    
    self.contentHandler(self.bestAttemptContent);
}

@end
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179