180

Is it possible to know if the app was launched/opened from a push notification?

I guess the launching event can be caught here:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if (launchOptions != nil) {
         // Launched from push notification
         NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

    }
}

However, how can I detect it was opened from a push notification when the app was in background?

Honey
  • 24,125
  • 14
  • 123
  • 212
joao
  • 3,847
  • 3
  • 29
  • 35
  • 7
    This is an old, but very useful post. Unfortunately the top answers don't actual solve the problem (as the comments indicate). Please consider marking a new answer as 'accepted' since the current one isn't complete. – MobileVet Aug 16 '16 at 17:14
  • 2
    This question has 100k+ views but the selected answer is incorrect or complete. To visitors, consider sorting by Active instead of by Votes to find modern solutions. – Albert Renshaw Apr 19 '20 at 02:23

28 Answers28

192

See This code :

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground  )
    {
         //opened from a push notification when the app was on background
    }
}

same as

-(void)application:(UIApplication *)application didReceiveLocalNotification (UILocalNotification *)notification
Mayur
  • 5,004
  • 7
  • 34
  • 62
shanegao
  • 3,482
  • 1
  • 16
  • 19
  • 19
    @ManuelM. This is a good answer in that it shows how to detect when an app in the background is brought to the foreground from a push notification. For when the app is not running, you need M.Othman's answer below. – OpenUserX03 May 21 '14 at 00:40
  • 6
    I am getting the call to application:didReceiveRemoteNotification: after tapping the notification regardless of whether the app is just in the background or not running at all so this answer suits my needs perfectly. Tested on iOS 7 & 8 – Newtz Feb 02 '15 at 22:42
  • However it is also called if the app is inactive for another reason such as if the control center or notification center menus are displayed or receiving a phone call. In these cases, the application state is UIApplicationStateInactive. Therefore testing applicationState will not accurately detect if the app was opened (or resumed) from a push notification. – JoGoFo Mar 09 '15 at 04:38
  • As Apple recommends, implement the `application:didReceiveRemoteNotification:fetchCompletionHandler: ` method instead of this one whenever possible on iOS7 and above. – Allen Mar 11 '15 at 07:37
  • I agree with @Newtz comment, but please use ```application:didReceiveRemoteNotification:fetchCompletionHandler:``` or else the method won't be fired when app re-launches. – Eric Chen Jul 06 '15 at 19:28
  • 17
    Like some others pointed out, this doesn't detect "launched/opened from a push notification". This is called when the notification is received, not when it's opened. So if you received a notification in the bg but tapped the app icon to open the app, the code you have here is still going to run, and you might open a page that the user didn't intend to open. – Bao Lei Aug 17 '15 at 23:20
  • 4
    @ManuelM. this method doesn't tell if the app was opened through notification center vs app icon if the background modes - remote notification is checked. It does when it's unchecked. I've documented the difference in this post: http://stackoverflow.com/questions/32061897/ios-push-notification-how-to-detect-if-the-user-tapped-on-notification-when-the/32079458#32079458 – Bao Lei Aug 18 '15 at 18:18
  • 2
    Confirmed that this works with Google Cloud Messaging. – CularBytes Sep 10 '15 at 17:03
  • This method is called twice, when receive the push and also when tap on push. So your answer incorrect. – Salmo Jul 24 '18 at 13:00
129

late but maybe useful

When app is not running

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

is called ..

where u need to check for push notification

NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (notification) {
    NSLog(@"app recieved notification from remote%@",notification);
    [self application:application didReceiveRemoteNotification:notification];
} else {
    NSLog(@"app did not recieve notification");
}
rptwsthi
  • 9,855
  • 10
  • 65
  • 102
M.Othman
  • 4,656
  • 3
  • 32
  • 38
  • 2
    Note that in the above snippet, notification should not be declared as a (UILocalNotification *) but an (NSDictionary *) – cosmix Mar 08 '16 at 20:37
  • 2
    This way you can see if there were any notifications for the app, while not running! The question was, how to detect if the app was opened from a notification. In this case the didReceiveRemoteNotification is called, even if the app was not running at all. - I like your answer, because it's quite important for many cases, but not the correct answer to the question. – Axel Zehden Sep 22 '16 at 08:31
  • Is your answer and [this answer](https://stackoverflow.com/questions/30297594/uiapplicationlaunchoptionsremotenotificationkey-not-getting-userinfo) both doing the same thing? – Honey Jun 27 '17 at 19:21
47

The issue we had was in correctly updating the view after the app is launched. There are complicated sequences of lifecycle methods here that get confusing.

Lifecycle Methods

Our testing for iOS 10 revealed the following sequences of lifecycle methods for the various cases:

DELEGATE METHODS CALLED WHEN OPENING APP

    Opening app when system killed or user killed
        didFinishLaunchingWithOptions
        applicationDidBecomeActive

    Opening app when backgrounded
        applicationWillEnterForeground
        applicationDidBecomeActive

DELEGATE METHODS CALLED WHEN OPENING PUSH

    Opening push when system killed
        [receiving push causes didFinishLaunchingWithOptions (with options) and didReceiveRemoteNotification:background]
        applicationWillEnterForeground
        didReceiveRemoteNotification:inactive
        applicationDidBecomeActive

    Opening push when user killed
        didFinishLaunchingWithOptions (with options)
        didReceiveRemoteNotification:inactive [only completionHandler version]
        applicationDidBecomeActive

    Opening push when backgrounded
        [receiving push causes didReceiveRemoteNotification:background]
        applicationWillEnterForeground
        didReceiveRemoteNotification:inactive
        applicationDidBecomeActive

The problem

Ok, so now we need to:

  1. Determine if the user is opening the app from a push
  2. Update the view based on the push state
  3. Clear the state so that subsequent opens don't return the user to the same position.

The tricky bit is that updating the view has to happen when the application actually becomes active, which is the same lifecycle method in all cases.

Sketch of our solution

Here are the main components of our solution:

  1. Store a notificationUserInfo instance variable on the AppDelegate.
  2. Set notificationUserInfo = nil in both applicationWillEnterForeground and didFinishLaunchingWithOptions.
  3. Set notificationUserInfo = userInfo in didReceiveRemoteNotification:inactive
  4. From applicationDidBecomeActive always call a custom method openViewFromNotification and pass self.notificationUserInfo. If self.notificationUserInfo is nil then return early, otherwise open the view based on the notification state found in self.notificationUserInfo.

Explanation

When opening from a push didFinishLaunchingWithOptions or applicationWillEnterForeground is always called immediately before didReceiveRemoteNotification:inactive, so we first reset notificationUserInfo in these methods so there's no stale state. Then, if didReceiveRemoteNotification:inactive is called we know we're opening from a push so we set self.notificationUserInfo which is then picked up by applicationDidBecomeActive to forward the user to the right view.

There is one final case which is if the user has the app open within the app switcher (i.e. by double tapping the home button while the app is in the foreground) and then receives a push notification. In this case only didReceiveRemoteNotification:inactive is called, and neither WillEnterForeground nor didFinishLaunching gets called so you need some special state to handle that case.

Hope this helps.

Honey
  • 24,125
  • 14
  • 123
  • 212
Eric Conner
  • 8,838
  • 4
  • 48
  • 60
  • Finally something that works, thanks! I wanted to create a flag "appResuming" and open the screen in the `receive` methods when the app state is *active* or the app is resuming. That could lead to issues with changing VCs when the app is still inactive. Your solution looks great, until Apple changes the lifecycle again. – shelll Feb 01 '17 at 12:20
  • What about iOS 9, are the lifecycle methods called in the same way and order? I already have no iOS 9 devices so I cannot test this properly. – shelll Feb 01 '17 at 12:43
  • 2
    There are two more edge cases except the app switcher. 1) When the notification center is pulled from the top and overlays the app 2) When the iOS's panel with wifi/BT/etc is pulled from the bottom and overlays the app. In all three cases just the `applicationWillResignActive` is called and then the `applicationDidBecomeActive`. So after the `applicationWillResignActive` is called do not save the received notification until a `applicationDidEnterBackground` or `applicationDidBecomeActive` is called. – shelll Feb 01 '17 at 13:12
  • Thanks for adding these cases @shelll. It always gets more complicated! I'm not sure about iOS9. I'd say it's probably safe to assume they're the same, but who knows. – Eric Conner Feb 01 '17 at 18:05
  • Just a heads up. I was testing iOS 11 Beta 9 today and found that in the case where you have your app in the foreground, lock the phone, and then select a push notification from the lock screen, it is calling didReceiveRemoteNotification:background just before calling applicationWillEnterForeground rather than what we are seeing on iOS 10 where it calls applicationWillEnterForeground and then didReceiveRemoteNotification:inactive - so this is an edge case not yet covered. In my opinion this is a bug in the iOS code, but given how close the iOS 11 release is, it is something to be aware of. – Roy Sep 07 '17 at 22:07
  • For me, `didReceiveRemoteNotification` stopped triggering when I implemented `userNotificationCenter(_:didReceive:withCompletionHandler:)` – Jannie Theunissen Jan 30 '18 at 14:54
  • Thank you for the explaining the answer and giving a better answer than just code. Nice to actually see answers that explain the theory and detail behind it rather than just throwing out a code example. – BrettS Feb 20 '18 at 17:29
26

This is a well worn post... but it is still missing an actual solution to the problem (as is pointed out in the various comments).

The original question is about detecting when the app was launched / opened from a push notification, e.g. a user taps on the notification. None of the answers actually cover this case.

The reason can be seen in the call flow when a notification arrives, application:didReceiveRemoteNotification...

gets called when the notification is received AND again when the notification is tapped by the user. Because of this, you can't tell by just looking at UIApplicationState wether the user tapped it.

Additionally, you no longer need to handle the situation of a 'cold start' of the app in application:didFinishLaunchingWithOptions... as application:didReceiveRemoteNotification... is called again after launching in iOS 9+ (maybe 8 as well).

So, how can you tell if the user tap started the chain of events? My solution is to mark the time at which the app begins to come out of the background or cold start and then check that time in application:didReceiveRemoteNotification.... If it is less than 0.1s, then you can be pretty sure the tap triggered the startup.

Swift 2.x

class AppDelegate: UIResponder, UIApplicationDelegate {

  var wakeTime : NSDate = NSDate()        // when did our application wake up most recently?

  func applicationWillEnterForeground(application: UIApplication) {    
    // time stamp the entering of foreground so we can tell how we got here
    wakeTime = NSDate()
  }

  func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    // ensure the userInfo dictionary has the data you expect
    if let type = userInfo["type"] as? String where type == "status" {
      // IF the wakeTime is less than 1/10 of a second, then we got here by tapping a notification
      if application.applicationState != UIApplicationState.Background && NSDate().timeIntervalSinceDate(wakeTime) < 0.1 {
        // User Tap on notification Started the App
      }
      else {
        // DO stuff here if you ONLY want it to happen when the push arrives
      }
      completionHandler(.NewData)
    }
    else {
      completionHandler(.NoData)
    }
  }
}

Swift 3

class AppDelegate: UIResponder, UIApplicationDelegate {

    var wakeTime : Date = Date()        // when did our application wake up most recently?

    func applicationWillEnterForeground(_ application: UIApplication) {
      // time stamp the entering of foreground so we can tell how we got here
      wakeTime = Date()
    }

  func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

      // ensure the userInfo dictionary has the data you expect
      if let type = userInfo["type"] as? String, type == "status" {
        // IF the wakeTime is less than 1/10 of a second, then we got here by tapping a notification
        if application.applicationState != UIApplicationState.background && Date().timeIntervalSince(wakeTime) < 0.1 {
          // User Tap on notification Started the App
        }
        else {
          // DO stuff here if you ONLY want it to happen when the push arrives
        }
        completionHandler(.newData)
      }
      else {
        completionHandler(.noData)
      }
    }
}

I have tested this for both cases (app in background, app not running) on iOS 9+ and it works like a charm. 0.1s is pretty conservative too, the actual value is ~0.002s so 0.01 is fine as well.

MobileVet
  • 2,850
  • 2
  • 24
  • 39
  • 1
    This appears to be the only working solution that differentiates between actually tapping the notification, and having the status-bar opened over the app. – liviucmg Aug 16 '16 at 17:08
  • 4
    This is the only working solution from all of the StackOverflow. The only thing i would like to add, is when you support iOS 10 and higher, you can simply use `UNNotificationCenter` API, specifically UNNotificationCenterDelegate methods. Those API call func `userNotificationCenter(UNUserNotificationCenter, didReceive: UNNotificationResponse, withCompletionHandler: @escaping () -> Void) ` method only when user has actually tapped on notification. – Den Telezhkin Oct 26 '16 at 11:27
  • how does it look for swift 3? – Jochen Österreicher Feb 05 '17 at 15:04
  • The solution does not work while an app is in inactive state (user swipes down notification center or swipes up control center) and receives a notification. When user taps on the notification the app does not receive `applicationWillEnterForeground ` call, as a result, the solution fails to detect the tap. – DevGansta Jan 18 '18 at 02:02
  • @DevGansta When you add your class as `UNUserNotificationCenter.current().delegate` in `application:didFinishLaunchingWithOptions`, the app will call `userNotificationCenter(didReceive response)` after the tap in the case you described – Dorian Roy Mar 14 '18 at 11:19
  • I just tested applicationWillEnterForeground, putting a print in it, and I see no printed result. I dont think it runs when the app first opens, only when it transitions to the foreground – roberto tomás Apr 27 '19 at 18:49
  • @DenTelezhkin even with `UNNotificationCenter` you can't detect if app was launched from terminated state unless you do a timestamp check... – Honey Feb 27 '21 at 12:42
25

When app is terminated, and user taps on push notification

public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
   if launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] != nil {
      print("from push")
    }
}

When app is in background, and user taps on push notificaion

If the user opens your app from the system-displayed alert, the system may call this method again when your app is about to enter the foreground so that you can update your user interface and display information pertaining to the notification.

public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
  if application.applicationState == .inactive {
    print("from push")
  }
}

Depending on your app, it can also send you silent push with content-available inside aps, so be aware of this as well :) See https://stackoverflow.com/a/33778990/1418457

LinusGeffarth
  • 21,607
  • 24
  • 100
  • 152
onmyway133
  • 38,911
  • 23
  • 231
  • 237
  • 4
    Only answer that does not feel like a dirty hack and correct. What i am missing is if app is in background and user opens it manually, how to check for that? While still being able to check for push cold start and push from the background. – Jochen Österreicher Feb 06 '17 at 14:12
  • 1
    @JochenÖsterreicher Hi, I summary it here, please check https://medium.com/@onmyway133/push-notification-in-ios-46d979e5f7ec#.jkigs84b9 – onmyway133 Feb 06 '17 at 14:58
19

Swift 2.0 For 'Not Running' State (Local & Remote Notification)

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {


// Handle notification
if (launchOptions != nil) {

    // For local Notification
    if let localNotificationInfo = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification {

        if let something = localNotificationInfo.userInfo!["yourKey"] as? String {
            self.window!.rootViewController = UINavigationController(rootViewController: YourController(yourMember: something))
        }


    } else

    // For remote Notification
    if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as! [NSObject : AnyObject]? {

        if let something = remoteNotification["yourKey"] as? String {
            self.window!.rootViewController = UINavigationController(rootViewController: YourController(yourMember: something))
        }
    }

}


return true
}
Meet Doshi
  • 3,983
  • 10
  • 35
  • 76
Włodzimierz Woźniak
  • 2,880
  • 24
  • 21
15

In application:didReceiveRemoteNotification: check whether you have received the notification when your app is in the foreground or background.

If it was received in the background, launch the app from the notification.

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
        NSLog(@"Notification received by running app");
    } else {
        NSLog(@"App opened from Notification");
    }
}
pkamb
  • 26,648
  • 20
  • 124
  • 157
Madhu
  • 984
  • 1
  • 11
  • 32
  • 3
    Note that "App opened from Notification" 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). – Kevin Cooper May 06 '16 at 21:57
  • 4
    @Kevin Exactly. It makes you wonder why apple seemingly put an intern to design the process of handling notifications... – Andreas Jun 02 '16 at 11:57
  • how can we detect if we tap on notification received in active state – Mayank Jain Sep 06 '17 at 06:51
15

For swift:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    PFPush.handlePush(userInfo)

    if application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background {
        //opened from a push notification when the app was in the background

    }

}
LondonGuy
  • 9,643
  • 9
  • 68
  • 143
4

Yes, you can detect by this method in appDelegate:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
      /* your Code*/
}

For local Notification:

- (void)application:(UIApplication *)application
didReceiveLocalNotification:(UILocalNotification *)notification
{
         /* your Code*/
}
Unnati
  • 2,421
  • 15
  • 36
Impossible
  • 1,237
  • 1
  • 16
  • 28
  • 2
    This method is not called if the app is not running. Thats what asked here – Pfitz May 06 '13 at 07:18
  • My problem is not handling the notification, but rather knowing if it was opened when you click the banner (when the app is on background). – joao May 06 '13 at 07:18
4

Posting this for Xamarin users.

The key to detecting if the app was launched via a push notification is the AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options) method, and the options dictionary that's passed in.

The options dictionary will have this key in it if it's a local notification: UIApplication.LaunchOptionsLocalNotificationKey.

If it's a remote notification, it will be UIApplication.LaunchOptionsRemoteNotificationKey.

When the key is LaunchOptionsLocalNotificationKey, the object is of type UILocalNotification. You can then look at the notification and determine which specific notification it is.

Pro-tip: UILocalNotification doesn't have an identifier in it, the same way UNNotificationRequest does. Put a dictionary key in the UserInfo containing a requestId so that when testing the UILocalNotification, you'll have a specific requestId available to base some logic on.

I found that even on iOS 10+ devices that when creating location notifications using the UNUserNotificationCenter's AddNotificationRequest & UNMutableNotificationContent, that when the app is not running(I killed it), and is launched by tapping the notification in the notification center, that the dictionary still contains the UILocalNotificaiton object.

This means that my code that checks for notification based launch will work on iOS8 and iOS 10+ devices

public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
    _logger.InfoFormat("FinishedLaunching");

    if(options != null)
    {
        if (options.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey))
        {
            //was started by tapping a local notification when app wasn't previously running.
            //works if using UNUserNotificationCenter.Current.AddNotificationRequest OR UIApplication.SharedApplication.PresentLocalNotificationNow);

            var localNotification = options[UIApplication.LaunchOptionsLocalNotificationKey] as UILocalNotification;

            //I would recommended a key such as this :
            var requestId = localNotification.UserInfo["RequestId"].ToString();
        }               
    }
    return true;
}
Wes
  • 891
  • 9
  • 18
3

if somebody wants the answer in swift 3

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
    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
    }
}
Hamid Shahsavari
  • 1,151
  • 18
  • 30
2

Straight from the documentation for

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo:nil

If the app is running and receives a remote notification, the app calls this method to process the notification.

Your implementation of this method should use the notification to take an appropriate course of action.

And a little bit later

If the app is not running when a push notification arrives, the method launches the app and provides the appropriate information in the launch options dictionary.

The app does not call this method to handle that push notification.

Instead, your implementation of the

application:willFinishLaunchingWithOptions:

or

application:didFinishLaunchingWithOptions:

method needs to get the push notification payload data and respond appropriately.

Mayur
  • 5,004
  • 7
  • 34
  • 62
Pfitz
  • 7,226
  • 4
  • 36
  • 51
2

I'll start with a state chart that I created for my own use to visualize it more accurately and to consider all other states: https://docs.google.com/spreadsheets/d/e/2PACX-1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml?gid=0&single=true

Using this chart, we can see what is actually required in order to develop a robust notification handling system that works in almost all possible use cases.

Complete solution ↓

  • Store notification payload in didReceiveRemoteNotification
  • Clear stored notification in applicationWillEnterForeground and didFinishLaunchingWithOptions
  • To tackle cases where control Center/ Notification center pulled, you can use a flag willResignActiveCalled and set it to false initially, Set this to true in applicationWillResignActive method,
  • In didReceiveRemoteNotification method, save notifications(userInfo) only when willResignActiveCalled is false.
  • Reset willResignActiveCalled to false in applicationDidEnterBackground and applicationDidBecomeActive method.

Note: A Similar answer is suggested in comments on Eric's answer, however, the state sheet helps in finding all possible scenarios as I did in my app.

Please find the complete code below and comment below if any specific case is not handled:

AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate {
  private var willResignActiveCalled = false

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    NotificationUtils.shared.notification = nil
    return true
  }
  func applicationWillResignActive(_ application: UIApplication) {
    willResignActiveCalled = true
  }
  func applicationDidEnterBackground(_ application: UIApplication) {
    willResignActiveCalled = false
  }
  func applicationWillEnterForeground(_ application: UIApplication) {
    NotificationUtils.shared.notification = nil
  }
  func applicationDidBecomeActive(_ application: UIApplication) {
    willResignActiveCalled = false
    NotificationUtils.shared.performActionOnNotification()
  }
  func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if !willResignActiveCalled { // Check if app is in inactive by app switcher, control center, or notification center
      NotificationUtils.shared.handleNotification(userInfo: userInfo)
    }
  }
}

NotificationUtils : This is where you can write all your code to navigating to different parts of the application, handling Databases(CoreData/Realm) and do all other stuff that needs to be done when a notification is received.

   class NotificationUtils {
  static let shared = NotificationUtils()
  private init() {}

  var notification : [AnyHashable: Any]?

  func handleNotification(userInfo : [AnyHashable: Any]){
    if UIApplication.shared.applicationState == UIApplicationState.active {
      self.notification = userInfo //Save Payload
      //Show inApp Alert/Banner/Action etc
      // perform immediate action on notification
    }
    else if UIApplication.shared.applicationState == UIApplicationState.inactive{
      self.notification = userInfo
    }
    else if UIApplication.shared.applicationState == UIApplicationState.background{
      //Process notification in background,
      // Update badges, save some data received from notification payload in Databases (CoreData/Realm)
    }
  }

  func performActionOnNotification(){
    // Do all the stuffs like navigating to ViewControllers, updating Badges etc
    defer {
      notification = nil
    }
  }
}
chetan anand
  • 83
  • 1
  • 7
1
func application(_ application: UIApplication, didReceiveRemoteNotification data: [AnyHashable : Any]) {
    print("Push notification received: \(data)")

    if let info = data["aps"] as? Dictionary<String, AnyObject> {
        let alertMsg = info["alert"] as! String
        print(alertMsg)
        switch application.applicationState {
        case .active:
            print("do stuff in case App is active")
        case .background:
            print("do stuff in case App is in background")
           // navigateToChatDetailViewControler(pushdata: data)
        case .inactive:
            print("do stuff in case App is inactive")
            // navigateToChatDetailViewControler(pushdata: data)
        }
    }
}
Hardik Bar
  • 1,482
  • 14
  • 23
1

There is only one reliable way, and it works only for iOS 10+ :

Using UNUserNotificationCenter implement UNUserNotificationCenterDelegate method:

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

    //Here you can get your original push if you need to
    NSDictionary* pusDict = response.notification.request.content.userInfo;

    if ([response.actionIdentifier isEqualToString: UNNotificationDefaultActionIdentifier]) {
        //User tapped the notification
    } else if ([response.actionIdentifier isEqualToString: UNNotificationDismissActionIdentifier]) {
        //User dismissed the notification 
    } else if ([response.actionIdentifier isEqualToString: MYCustomActionId]) {
        //User chose my custom defined action
    }
    ...
}
Luten
  • 4,901
  • 4
  • 24
  • 23
1

M.Othman's answer is correct for apps that don't contain scene delegate For Scene Delegate Apps This worked for me on iOS 13

Here is the code for that should be written in will connect scene

if connectionOptions.notificationResponse == nil { 
//Not opened from push notification
} else {
  //Opened from push notification
}

Code for app delegate to support earlier versions didFinishLaunchingWithOptions

let notification = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification]
        if (notification != nil) {

            //Launched from push notification
        } else {

            //Launch from other source
        }
0
     // shanegao's code in Swift 2.0
     func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject])
    {
            if ( application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background ){
                    print("opened from a push notification when the app was on background")
            }else{
                    print("opened from a push notification when the app was on foreground")
            }
    }
Sean Dev
  • 1,060
  • 1
  • 16
  • 28
  • But what if app was closed(terminated). Like Twitter or Instagram it somehow detects it and if app is even closed it redirects you to new post or pictures or your profile etc.. – Tarvo Mäesepp Jun 15 '16 at 13:19
0

The problem with this question is that "opening" the app isn't well-defined. An app is either cold-launched from a not-running state, or it's reactivated from an inactive state (e.g. from switching back to it from another app). Here's my solution to distinguish all of these possible states:

typedef NS_ENUM(NSInteger, MXAppState) {
    MXAppStateActive = 0,
    MXAppStateReactivated = 1,
    MXAppStateLaunched = 2
};

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // ... your custom launch stuff
    [[MXDefaults instance] setDateOfLastLaunch:[NSDate date]];
    // ... more custom launch stuff
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // Through a lot of trial and error (by showing alerts), I can confirm that on iOS 10
    // this method is only called when the app has been launched from a push notification
    // or when the app is already in the Active state.  When you receive a push
    // and then launch the app from the icon or apps view, this method is _not_ called.
    // So with 99% confidence, it means this method is called in one of the 3 mutually exclusive cases
    //    1) we are active in the foreground, no action was taken by the user
    //    2) we were 'launched' from an inactive state (so we may already be in the main section) by a tap
    //       on a push notification
    //    3) we were truly launched from a not running state by a tap on a push notification
    // Beware that cases (2) and (3) may both show UIApplicationStateInactive and cant be easily distinguished.
    // We check the last launch date to distinguish (2) and (3).

    MXAppState appState = [self mxAppStateFromApplicationState:[application applicationState]];
    //... your app's logic
}

- (MXAppState)mxAppStateFromApplicationState:(UIApplicationState)state {
    if (state == UIApplicationStateActive) {
        return MXAppStateActive;
    } else {
        NSDate* lastLaunchDate = [[MXDefaults instance] dateOfLastLaunch];
        if (lastLaunchDate && [[NSDate date] timeIntervalSinceDate:lastLaunchDate] < 0.5f) {
            return MXAppStateLaunched;
        } else {
            return MXAppStateReactivated;
        }
    }
    return MXAppStateActive;
}

And MXDefaults is just a little wrapper for NSUserDefaults.

skensell
  • 1,345
  • 11
  • 19
0

Xcode 10 Swift 4.2

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {

    let state : UIApplicationState = application.applicationState
    if (state == .Inactive || state == .Background) {
        // coming from background
    } else {
        // App is running in foreground
    }
}
Prashant Gaikwad
  • 2,229
  • 15
  • 23
0

If you are running iOS 13 or above use this code in your SceneDelegate:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    
    guard let notificationResponse = connectionOptions.notificationResponse else { return }
    
    let pushTitle = notificationResponse.notification.request.content.title
    let pushSubtitle = notificationResponse.notification.request.content.subtitle
    let pushBody = notificationResponse.notification.request.content.body
    
    // do your staff here
}
Arthur Stepanov
  • 191
  • 1
  • 4
0

M.Othman's answer for Swift 5. (Although using NSLog is not recommended anymore)

Add the following after you set everything needed to display your RootViewController. In your application(_:didReceiveRemoteNotification), you should add the logic that can differentiate between a first launch and come to background type of launch.

if let launchOptions = launchOptions, 
let notification = launchOptions[UIApplicationLaunchOptionsKey.remoteNotification] 
as? [AnyHashable : Any] {
    NSLog("app recieved notification from remote \(notification)")
    self.application(application, didReceiveRemoteNotification: notification)
} else {
    NSLog("app did not recieve notification")
}

Some other Swift specific answers to tackle this can be found at: How to handle launch options in Swift 3 when a notification is tapped? Getting syntax problems

Parth Tamane
  • 1,611
  • 10
  • 24
-1

You can use:

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo

to handle the remote push notifications.

Check here the documentation

Mayur
  • 5,004
  • 7
  • 34
  • 62
sunkehappy
  • 8,565
  • 5
  • 36
  • 60
-1

I haven't tried it yet but maybe you could send yourself a notification? http://nshipster.com/nsnotification-and-nsnotificationcenter/

Berendschot
  • 2,834
  • 1
  • 17
  • 42
-1

For swift

 func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]){

    ++notificationNumber
    application.applicationIconBadgeNumber =  notificationNumber;

    if let aps = userInfo["aps"] as? NSDictionary {

        var message = aps["alert"]
        println("my messages : \(message)")

    }
}
elp
  • 7,607
  • 7
  • 58
  • 113
idris yıldız
  • 2,039
  • 17
  • 20
-1

For Swift Users:

If you want to launch a different page on opening from push or something like that, you need to check it in didFinishLaunchingWithOptions like:

let directVc: directVC! = directVC(nibName:"directVC", bundle: nil)
let pushVc: pushVC! = pushVC(nibName:"pushVC", bundle: nil)

if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
     self.navigationController = UINavigationController(rootViewController: pushVc!)
} else {
     self.navigationController = UINavigationController(rootViewController: directVc!)
}
self.window!.rootViewController = self.navigationController
AAA
  • 1,720
  • 3
  • 19
  • 41
-1

When app is in background as shanegao you can use

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground  )
    {
         //opened from a push notification when the app was on background
    }
}

But if you want to launch the application and when app is closed and you want to debug your application you can go to Edit Scheme and in left menu select Run and then in launch select Wait for executable to be launched and then you application launch when you click on push notification

Edit Scheme > Run > Wait for executable to be launched

Muhammad Salman
  • 545
  • 4
  • 20
-2

IN SWIFT:

I'm running Push Notifications (with background fetching). When my app is in the background and I receive a push notification, I found that didReceiveRemoteNotification in appDelegate would be called twice; once for when notification is received and another when user clicks on the notification alert.

To detect if notification alert was clicked, just check if applicationState raw value == 1 inside didReceiveRemoteNotification in appDelegate.

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject]) {
    // If not from alert click applicationState(1)
    if (application.applicationState.rawValue != 1) {
        // Run your code here
    }
}

I hope this helps.

-2

For iOS 10+, you can use this method for knowing when your notification is clicked irrespective of the state of the app.

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

    //Notification clicked
    completionHandler()
}
manishsharma93
  • 911
  • 1
  • 9
  • 23