iOS push notification: how to detect if the user tapped on notification when the app is in background?

IosSwiftPush NotificationApple Push-Notifications

Ios Problem Overview


There are a lot of stackoverflow threads regarding this topic, but I still didn't find a good solution.

If the app is not in the background, I can check launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] in application:didFinishLaunchingWithOptions: call to see if it's opened from a notification.

If the app is in the background, all the posts suggest to use application:didReceiveRemoteNotification: and check the application state. But as I experimented (and also as the name of this API suggests), this method gets called when the notification is received, instead of tapped.

So the problem is, if the app is launched and then backgrounded, and you know a notification is received from application:didReceiveNotification (application:didFinishLaunchWithOptions: won't trigger at this point), how do you know if user resumed the app from by tapping the notification or just tapping the app icon? Because if the user tapped the notification, the expectation is to open the page mentioned in that notification. Otherwise it shouldn't.

I could use handleActionWithIdentifier for custom action notifications, but this only gets triggered when a custom action button is tapped, not when the user taps on the main body of the notification.

Thanks.

EDIT:

after reading one answer below, I thought in this way I can clarify my question:

How can we differentiate these 2 scenarios:

(A) 1.app goes to background; 2.notification received; 3. user taps on the notification; 4. app enters foreground

(B) 1.app goes to background; 2.notification received; 3. user ignores the notification and taps on the app icon later; 4. app enters foreground

Since application:didReceiveRemoteNotification: is triggered in both cases at step 2.

Or, should application:didReceiveRemoteNotification: be triggered in step 3 for (A) only, but I somehow configured my app wrong so I'm seeing it at step 2?

Ios Solutions


Solution 1 - Ios

OK I finally figured out.

In the target settings ➝ Capabilities tab ➝ Background Modes, if you check "Remote Notifications", application:didReceiveRemoteNotification: will get triggered as soon as notification arrives (as long as the app is in the background), and in that case there is no way to tell whether the user will tap on the notification.

If you uncheck that box, application:didReceiveRemoteNotification: will be triggered only when you tap on the notification.

It's a little strange that checking this box will change how one of the app delegate methods behaves. It would be nicer if that box is checked, Apple uses two different delegate methods for notification receive and notification tap. I think most of the developers always want to know if a notification is tapped on or not.

Hopefully this will be helpful for anyone else who run into this issue. Apple also didn't document it clearly here so it took me a while to figure out.

enter image description here

Solution 2 - Ios

I've been looking for the same thing as you and actually found a solution that does not require remote notification to be ticked off.

To check whether user has tapped, or app is in background or is active, you just have to check the application state in

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
    
    if(application.applicationState == UIApplicationStateActive) {
        
        //app is currently active, can update badges count here
        
    }else if(application.applicationState == UIApplicationStateBackground){
        
        //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
        
    }else if(application.applicationState == UIApplicationStateInactive){
        
        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
        
    }

For more info check:

UIKit Framework Reference > UIApplication Class Reference > UIApplicationState

Solution 3 - Ios

According to https://stackoverflow.com/questions/12937359/ios-xcode-how-to-know-that-app-has-been-launched-with-a-click-on-notification you have to check for the application state in didReceiveLocalNotification like this:

if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive)
{
    // user has tapped notification
}
else
{
    // user opened app from app icon
}

Although it does not make totally sense to me, it seems to work.

Solution 4 - Ios

I ran into this problem, too — but on iOS 11 with the new UserNotifications Framework.

Here for me it is like this:

  • New Launch: application:didFinishLaunchingWithOptions:
  • Received from a terminate state: application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
  • Received in Foreground: userNotificationCenter(_:willPresent:withCompletionHandler:)
  • Received in Background: userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

Solution 5 - Ios

If somebody wants it in swift 3.0

switch application.applicationState {
    case .active:
        //app is currently active, can update badges count here
        break
    case .inactive:
        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
        break
    case .background:
        //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
        break
    default:
        break
    }

for swift 4

switch UIApplication.shared.applicationState {
case .active:
    //app is currently active, can update badges count here
    break
case .inactive:
    //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
    break
case .background:
    //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
    break
default:
    break
}

Solution 6 - Ios

There are two Funcs to handle received PushNotification inside PushNotificationManager class:

class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate{

}

As I tested the first one trigger as soon as Notification arrived

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    
    completionHandler(UNNotificationPresentationOptions.alert)
    
    //OnReceive Notification
    let userInfo = notification.request.content.userInfo
    for key in userInfo.keys {
         Constants.setPrint("\(key): \(userInfo[key])")
    }
    
    completionHandler([])
    
}

And second one when Tapped on Notification:

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    
    //OnTap Notification
    let userInfo = response.notification.request.content.userInfo
    for key in userInfo.keys {
        Constants.setPrint("\(key): \(userInfo[key])")
    }
    
    completionHandler()
}

I also tested it with both ON and OFF states of Remote Notification(in Background Modes)

Solution 7 - Ios

If you have "Background Modes" > "Remote notifications" checked == YES, tap on notification event will arrive in:

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

It helped me. Please enjoy :)

Solution 8 - Ios

For iOS 10 and above put this in AppDelegate, to get to know notification is tapped(works even app is closed or open)

func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}

Solution 9 - Ios

In my case, background mode OFF did not make any difference. However when the app was suspended and the user tapped the notification, I could handle the case in this callback method:

func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {

}

Solution 10 - Ios

SWIFT 5.1

UIApplication.State did not work for me, because once I read fingerprint (modal is shown) in my app, notification is also thrown in upper bar and user must click it.

I've created

public static var isNotificationFromApp: Bool = false

in AppDelegate and I set it true in my starting viewController and then in my notification storyboard/viewControllerI just check that :)

Hope it can come in handy

Solution 11 - Ios

Solved with Deployent target change, after lots of trial and error.

I was experiencing the same issue with iOS Deployment Target 9.0 and 10.0, XCode Version 11.6 (11E708). - did not get any delegate method calls from tapping the notification when the app is in background.

What solved this was changing the Target to iOS 11.0. I finally started getting the calls on notification tap from cold start and background scenarios in the UNUserNotificationCenter delegate didReceive: implementation.

Additionally, the willReceive: now also gets the call in the foreground scenario (as expected).

The payload I'm using is following. Note, that there's no content-available, or mutable-content set.

{
  "aps": {
    "sound": "default",
    "badge": 1,
    "alert": {
      "body": "test",
      "title": "test"
    }
  }
}

Solution 12 - Ios

There is a 'hacky' way to know if notification was received while App is running (foreground) or from user tap.

The TLDR version:
The Application lifecycle goes through applicationWillEnterForeground when it's brought from the background. Hence, user tap.

Implementation:
First thing to filter (as stated multiple times) is if the App is inactive.

if application.applicationState == .inactive // You can ignore all the rest

Background state will never happen and active if obviously foreground. The remaining issue with the App state is really inactive (user pulled control center) or opened by user tap.
In both cases state will be inactive.
However, when a user taps the notification, the app first goes through applicationWillEnterForeground. Simply checking the time gap from applicationWillEnterForeground to didReceiveRemoteNotification should close this issue.

// In AppDelegate

var applicationWillEnterForegroundTime = Date()

func applicationWillEnterForeground(_ application: UIApplication) {
    applicationWillEnterForegroundTime = Date()
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    
    if application.applicationState == .inactive,
       Date().timeIntervalSince(applicationWillEnterForegroundTime) < 1.0 {
        // Push notification was opened by user tap
    }
}

Solution 13 - Ios

As mentioned by Aleks, we can use userNotificationCenter(_:didReceive:withCompletionHandler:) (doc) method to capture the user response to a notification. We need to set the delegate of UNUserNotificationCenter's shared object in the application(_:didFinishLaunchingWithOptions:) or application(_:willFinishLaunchingWithOptions:) methods of the AppDelegate. Basically what I did was the following.

First,

import UserNotifications

Second, make AppDelegate conform to protocol UNUserNotificationCenterDelegate

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
// your app delegate code
}

Third, set delegate of UNUserNotificationCenter object as AppDelegate inside application(_:willFinishLaunchingWithOptions:)

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    return true
}

Fourth, implementing the delegate method userNotificationCenter(_:didReceive:withCompletionHandler:)

func userNotificationCenter(_ center: UNUserNotificationCenter,
           didReceive response: UNNotificationResponse,
           withCompletionHandler completionHandler: @escaping () -> Void) {
    let application = UIApplication.shared
    if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
        let userInfo = response.notification.request.content.userInfo
        self.handlePushNotification(application, userInfo: userInfo)
    }
}

And finally, handlePushNotificationMethod which is your private method where you implement all logic to handle the notification.

func handlePushNotification(userInfo: [AnyHashable : Any]) {
    if let aps = userInfo["aps"] as? NSDictionary {
    ...
    }
}

This method (userNotificationCenter(_:didReceive:withCompletionHandler:)) gets called whenever user taps a notification or when user performs some kind of action on the notification. In my case, all I wanted was to get the tap on notification. So I compared the actionIdentifier of the response to default action identifier.

Please note the availability of UserNotifications framework while implementing.

Solution 14 - Ios

You can configure your push notification's payload to call app delegate’s application:didReceiveRemoteNotification:fetchCompletionHandler: method when the app is in background. You can set some flag here so that when user launch your application next time, you can perform your operation.

From apple’s documentation you should use this methods to download new content associated with push notification. Also for this to work, you have to enable Remote notification from Background modes and your push notification payload must contain content-available key with its value set to 1. From more info please see Using Push Notifications to Initiate a Download section from apple doc here.

Another way is to have badge count in push notification payload. So next time your application launches you can check application badge count. If its grater than zero, perform your operation and zero/clear badge count from server also.

Hope this helps you.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBao LeiView Question on Stackoverflow
Solution 1 - IosBao LeiView Answer on Stackoverflow
Solution 2 - IosDennismView Answer on Stackoverflow
Solution 3 - IosWerner KratochwilView Answer on Stackoverflow
Solution 4 - IospreView Answer on Stackoverflow
Solution 5 - IosHamid ShahsavariView Answer on Stackoverflow
Solution 6 - IosSaeidView Answer on Stackoverflow
Solution 7 - IosAleksView Answer on Stackoverflow
Solution 8 - IosYatin AroraView Answer on Stackoverflow
Solution 9 - IosDoruChideanView Answer on Stackoverflow
Solution 10 - IosHourglasserView Answer on Stackoverflow
Solution 11 - IosAnton NarusbergView Answer on Stackoverflow
Solution 12 - IosbauerMusicView Answer on Stackoverflow
Solution 13 - IosSkywalkerView Answer on Stackoverflow
Solution 14 - IosAmeet DhasView Answer on Stackoverflow