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

15

146

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?

Homemade answered 18/8, 2015 at 0:46 Comment(13)
Use a custom dictionary value for your payload and act accordingly. Pretty simple.Giltzow
@Giltzow a dictionary in the payload can represent whether the user tapped on the notification, right? e.g. your friend A posted an article B, you can say {user: A, article: B} in the payload, while the app is in the background and you get didReceiveRemoteNotification. How do you know when the app resumes, whether you should display the article?Homemade
See my answer here : https://mcmap.net/q/138079/-how-to-handle-received-push-notification-from-parse-rss it really is that simple FYI. You should educate yourself on what didReceiveRemoteNotification actually does. Here is what you need to get you started take time to learn some stuff, don't just go straight to SO ;)Giltzow
@Giltzow I did read the documentation and I did educate myself on what didReceiveRemoteNotification does. Did you actually read my question? Per Apple's official documentation didReceiveRemoteNotification "tells the delegate that the running app received a remote notification". I am asking what is a good way to tell if user tapped on a notification. The SO link you referred to is for when the app is launching from a fresh start, I'm asking the scenario when the app is in the background.Homemade
Whoa. It wasn't a diss mate. Just saying there are plenty of documents on how to create a JSON payload, and handle the notification, which you even hinted at, just gotta try things. It always feels more rewarding to figure this stuff out on your on. I learned how to do it from the same webpage I sent you. We're all here to learn, no disrespect. And yes. I did read it :) and apparently the only one helping you out. Code on mate. Code on!Giltzow
If you really learned how to do it and are trying to help, could you tell me what I missed in my own learning? I was saying (A) didFinishLaunchingWithOptions won't be triggered if the app is in the background, (B) didReceiveRemoteNotification is triggered when the notification arrives. Is either one of the statements wrong? If not, what is the answer for "how do you detect if a user tapped on the notification when the app is in the background"? The question has nothing to do with how to create JSON payload. It assumes that the JSON payload is already.Homemade
I am still trying to figure this stuff out on my own and if I do, i will self-answer this question. But I just posted it here in the mean time to see if someone else knows it, as I spent some time and didn't find it obvious.Homemade
I answered your question with the first comment and followed it up with an example in the link. When you create a custom payload you can retrieve the data from it in didFinishLaunching and then act accordingly, sending the user to whatever view you seem fit (which is what you hinted at in the first paragraph) that's essentially how you know if they opened the app with a notification. A) your right because it's a method that happens after it launches, which is where you want to handle the payload if user opened it from a notification. Read 2.5 of the docs in the link above. Good read.Giltzow
Start here : In iOS, the delegate, in its implementation of the application:didFinishLaunchingWithOptions: method, uses the UIApplicationLaunchOptionsRemoteNotificationKey key to access the payload from the launch-options dictionary. Then it has another link to 'The Notification Payload' to create your own dict. It's a duplicate question that's why I haven't answered it.Giltzow
possible duplicate of iOS / XCode: how to know that app has been launched with a click on notification or on springboard app icon?Giltzow
@Giltzow OK maybe I didn't state it clear enough. I mean if the app is completely quitted, not in the background, yes didFinishLaunching will get called. But if you launch your app, and then background the app, and now a notification gets in, and the user taps on the notification, and now the didFinishLaunching will not be called again. Instead applicationWillEnterForeground and applicationDidBecomeActive will be called. How can you tell the app is entering foreground because the user tapped on the notification or the app icon?Homemade
Let us continue this discussion in chat.Homemade
outdated, scroll to Yatin Arora answer in this threadEhlke
H
114

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

Homemade answered 18/8, 2015 at 17:56 Comment(9)
I have Background Modes set to "OFF" completely, but still get notified when a notification arrives with the app in background mode.Ladyship
Great! This helped me. It's a pity however that I need to switch off remote notifications. I would like to prefetch data when a push notification arrives AND detect the user tap. I can't find a way to achieve this.Gretchengrete
I set the Background mode to "ON" mode and enable the remote notifications. Still not able to detect the notification event.Brynne
I have same problem Background mode is set to "ON" and enabled remote notification, but still not get notified when notification arrives to the app in background modeLymphadenitis
@Bao - I think it will cause rejection on Appstore, as this option is basically used to download content related to notification in background. So if you are not downloading any content Apple may reject your app. developer.apple.com/library/ios/documentation/iPhone/Conceptual/…Worriment
Very useful, Thank you bodyBarren
i have implemented everything perfectly and i am receiving notifications too . but it never goes inside application:didReceiveRemoteNotification: method unless user taps on notification message . do you have any solution regarding this ?Billups
With a minimum deployment target of iOS 10, this is no longer necessary, as the new UserNotificationCenter api does not suffer from these pesky problems anymore.Haugen
The problem is this delegate function also gets called if you receive a notification while in the app.Thetes
C
80

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

Copyread answered 18/2, 2016 at 7:48 Comment(7)
What about app being UIApplicationStateInactive as a result of user engaging with Notification centre (swipe from top) or Control centre (swipe from bottom) while the app is on screen. Apparently, there is no way to tell whether APNS was received in Inactive state or user actually tapped it.Yaw
@KostiaKim You're right, but that's a super edge case...other than that, this answer is the most valid in terms of separating between app in foreground...user tapping...app in backgroundBestead
Thanks, this kind of demystifies everything. Remember that in order for the delegate method to be called when the app is in the background, the push notification has to include the content-available key, but then the notification must be silent (i.e. not include a sound or badge) as stated in the official docs.Layfield
@KostiaKim I was actually able to fix the issue of not knowing whether the control center was open or if the user actually tapped the notification by adding a boolean that is set to true in the func applicationDidEnterBackground(_ application: UIApplication) and false in the func applicationDidBecomeActive(_ application: UIApplication) this allowed me to show the in app notifications when the app is inactive due to the control center or the notifications listPisciform
it boggles my mind why Apple didn't present a different event or simply add a flag in the metadata to indicate that the user actually interacted with the notification. Having to 'deduce' whether the user took an action based on ambient information about the application state is pretty unreasonable for a key bit of information that affects the user's expectation of application behaviour. Perhaps the only thing to do is to create a custom action to open the app with that information?Gormless
Note in iOS10+ this approach is no longer necessary a UNUserNotificationCenterDelegate method can now be used instead which is called when a user interacts with a notification. You can also present stock notification alerts when your app is in the foreground as well. See https://mcmap.net/q/138081/-ios-10-unusernotificationcenterdelegate-not-called-push-notifications-not-working for detailed explanation.Saboteur
This answer helped me a lot, but just one question, what if I want to detect if I clicked the notification while the app is already open (as in that case the state will be 'active')Elgar
S
21

According to iOS / XCode: how to know that app has been launched with a click on notification or on springboard app icon? 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.

Shellishellie answered 30/11, 2015 at 15:4 Comment(8)
It wouldn't work in this scenario. I tried that before. When that checkbox was checked (see my accepted answer for details), when the notification arrives, your line with comment "// user has tapped notification" will be entered, even though the user didn't tap the notification (the notification just arrived).Homemade
I disagree. I have "Remote notifications" checked in my background capabilities and it works the way described in my answer. I have "Background fetch" checked as well. Maybe that causes the change?Shellishellie
could you please +1 my answer? ;-) I still need need some votes to participate more in stackoverflow. Thanks a lotShellishellie
Yupe, this is the solution. tnx.Ry
Note that this will be a false positive if the notification is sent while the user is on a different screen (for example, if they pull down the status bar and then receive a notification from your app).Neoteric
This works for me. I had been wondering why sometimes I'd get redundant notifications. It turns out that Werner is correct. If you open the App via the Alert display then you go thru didReceiveRemoteNotification another time, now while the App is UIApplicationStateInactive. Thx Werner.Whiting
i have implemented everything perfectly and i am receiving notifications too . but it never goes inside application:didReceiveRemoteNotification: method unless user taps on notification message . do you have any solution regarding this ?Billups
You have to set the content-available key to 1 if you want to hit didReceiveRemoteNotification methodMaltha
K
19

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:
Ken answered 4/10, 2017 at 13:44 Comment(2)
This is the way to do it in iOS 11 +Drilling
This is just so complicated 🤦‍♂️Arv
C
18

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
}
Cut answered 21/12, 2016 at 9:2 Comment(3)
On which trigger we should add this code , didReceiveRemoteNotification or didFinishLaunchingWithOptions?Choice
On didReceiveRemoteNotificationCut
@Hamid sh,,,, I received push notification in all state i.e. when app is in forground, background, close(terminated)..! but my problem is that how to increment badge count when app is in background state and close (terminate) state???? Please tell me in details how i do it....? my app badge count is only increase when app is in forground state.....? if possible please tell me in brief.......!Decrepit
M
10

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 :)

Multiplicity answered 15/9, 2017 at 8:2 Comment(3)
I saw that Apple added this but couldn't figure out how to get the notification's custom payload this way.Thetes
This is function is called when the app is in a background state and becomes active and also when the app starts from the killed stateElayne
@NikhilMuskur only if "remote notifications" are enabled yeh?Arv
A
10

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)

Aviles answered 16/5, 2019 at 10:44 Comment(0)
I
6

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")
}
Illjudged answered 23/4, 2019 at 9:8 Comment(1)
This is the correct answer for iOS 10+. Full explanation provided on another thread here: https://mcmap.net/q/138081/-ios-10-unusernotificationcenterdelegate-not-called-push-notifications-not-working.Saboteur
W
4

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) {

}
Wicketkeeper answered 19/6, 2018 at 12:39 Comment(1)
This is the official way which Apple says. According to the Apple's doc, it will be called whenever user interact with the push notification UI. If app is not in the background, it will launch the app in background mode and call this method.Hanan
C
1

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

Clyte answered 20/3, 2020 at 12:21 Comment(0)
I
1

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"
    }
  }
}
Iloilo answered 28/8, 2020 at 20:59 Comment(0)
F
1

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
    }
}
Fiche answered 17/1, 2022 at 13:57 Comment(2)
just a note; use < 1000.0 for 1 sec why timeIntervalSince returns in msJamboree
@Jamboree Are you sure? From what I know, timeIntervalSince returns in seconds. Documentation are unhelpful: " interval between the date object and ..." . Can you point me so a source?Fiche
L
0

This is worked fine for me. Please call UNUserNotificationCenter.current().delegate = self inside the willFinishLaunchingWithOptions. Do not forgot to add "content-available" = 1 inside the payload

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        //register UNUserNotificationCenterDelegate in case of app comes from background to forground.
        UNUserNotificationCenter.current().delegate = self
        return true
    }
Legofmutton answered 17/8, 2023 at 12:40 Comment(1)
There are 14 existing answers to this question, including a top-voted, accepted answer with over one hundred votes. Are you certain your solution hasn't already been given? If not, why do you believe your approach improves upon the existing proposals, which have been validated by the community? Offering an explanation is always useful on Stack Overflow, but it's especially important where the question has been resolved to the satisfaction of both the OP and the community. Help readers out by explaining what your answer does different and when it might be preferred.Raeannraeburn
D
-1

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.

Dynamics answered 22/2, 2021 at 5:23 Comment(4)
This is working when interacting with a single notification at a time, what about the case where the user removes all notifications, is there a way to find out this?Calli
duplicate to Yatin Arora / AleksEhlke
@ErkkiNokso-Koivisto I used their answer, but you really can't call it an exact duplicate. This is a much more detailed answer.Dynamics
@Calli I haven't tried that. Could you try to dismiss all and print the actionIdentifier? If its not calling the print, it means this method doesn't get called on this action. :(Dynamics
N
-7

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.

Newlin answered 18/8, 2015 at 9:54 Comment(2)
@bhusan have you read the details of my question? I'm seeing didReceiveRemoteNotification getting called when the notification just arrives (before the user taps on it). I wanted to find out whether the user tapped on it.Homemade
You didn't answer OP's question. He doesn't just want to know that there has been a notification. He wants to know if the user opened the App by tapping that remote notification.Whiting

© 2022 - 2024 — McMap. All rights reserved.