Detect if the app was launched/opened from a push notification
Asked Answered
O

29

193

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?

Ostracod answered 6/5, 2013 at 7:5 Comment(2)
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.Justifiable
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.Farwell
A
194

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
Alpestrine answered 6/5, 2013 at 7:21 Comment(9)
@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.Chazan
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 & 8Va
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.Solley
As Apple recommends, implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method instead of this one whenever possible on iOS7 and above.Ten
I agree with @Va comment, but please use application:didReceiveRemoteNotification:fetchCompletionHandler: or else the method won't be fired when app re-launches.Proctor
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.Semicircle
@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: #32062397Semicircle
Confirmed that this works with Google Cloud Messaging.Poesy
This method is called twice, when receive the push and also when tap on push. So your answer incorrect.Zee
H
136

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");
}
Henghold answered 6/2, 2014 at 18:26 Comment(6)
Note that in the above snippet, notification should not be declared as a (UILocalNotification *) but an (NSDictionary *)Kuchen
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.Spacesuit
Is your answer and this answer both doing the same thing?Orthodontia
Late but still a lifesaver, especially if you're using the React-Native framework and doing lots of additional version checks, credentials, and measurements or connecting socket when the start of your application. Then imagine sending silent notifications in order to delete some other revoked notifications. Bum your app is up and connected to the socket.Galwegian
@YadigarZENGİN how have you run react native code after knowing a push is received in this method . pls helpJetton
@AmbeshTiwari because of now you have the context when I invoked didReceiveRemoteNotification whatever you are using either react-native-notifications or oneSignal framework that libraries will handle this part. But still, you can put this notification somewhere reachable via application when queried with small RN-Native-Lib.Galwegian
S
52

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.

Sontich answered 31/1, 2017 at 22:49 Comment(7)
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.Lansing
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.Lansing
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.Lansing
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.Sontich
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.Hovis
For me, didReceiveRemoteNotification stopped triggering when I implemented userNotificationCenter(_:didReceive:withCompletionHandler:)Lacrimator
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.Godspeed
C
28

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://mcmap.net/q/136623/-remote-notification-method-called-twice

Carnarvon answered 22/6, 2016 at 12:13 Comment(2)
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.Althorn
@JochenÖsterreicher Hi, I summary it here, please check medium.com/@onmyway133/…Carnarvon
J
27

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.

Justifiable answered 28/7, 2016 at 20:0 Comment(7)
This appears to be the only working solution that differentiates between actually tapping the notification, and having the status-bar opened over the app.Trainbearer
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.Ronni
how does it look for swift 3?Althorn
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.Whippet
@Whippet 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 describedShapely
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 foregroundCagliostro
@DenTelezhkin even with UNNotificationCenter you can't detect if app was launched from terminated state unless you do a timestamp check...Orthodontia
Q
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
}
Quay answered 11/2, 2016 at 11:27 Comment(0)
D
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");
    }
}
Disoperation answered 6/5, 2013 at 7:25 Comment(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).Siloam
@Kevin Exactly. It makes you wonder why apple seemingly put an intern to design the process of handling notifications...Tabb
how can we detect if we tap on notification received in active stateAllium
H
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

    }

}
Hadwin answered 29/11, 2014 at 23:34 Comment(0)
W
6

If you have SceneDelegate in your app then you should use below code to manage local/remote notification, when your application is killed/terminated and you open application from tapping on notification

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    
    //Handle Notification Response
    guard let notifiResponse = connectionOptions.notificationResponse else { return }
    
    if notifiResponse.notification.request.trigger is UNTimeIntervalNotificationTrigger { //Local Notification
        Messaging.messaging().appDidReceiveMessage(notifiResponse.notification.request.content.userInfo)
        
        print("Receive Local Notifications")            
    }
    else if notifiResponse.notification.request.trigger is UNPushNotificationTrigger{ //Remote Notification
        
        print("Receive Remote Notifications")
    }                
}

Use your AppDelegate to manage local/remote notification when your application is in background/foreground state.

extension AppDelegate : UNUserNotificationCenterDelegate {   

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
                   
        if response.notification.request.trigger is UNTimeIntervalNotificationTrigger{
             print("Receive Local Notifications")
        }
        else if response.notification.request.trigger is UNPushNotificationTrigger{
             print("Receive Remote Notifications")
        }
    
        let userInfo = response.notification.request.content.userInfo
        completionHandler()
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
       print("willPresent Notifications")

       if notification.request.trigger is UNTimeIntervalNotificationTrigger{
            print("Receive Local Notifications")
       }
       else {
            print("Receive Remote Notifications")
       }
       completionHandler([.banner, .list, .sound])
    }
}
Whitebeam answered 11/2, 2022 at 10:28 Comment(1)
This should be new "selected" answer - current top ones are outdated and don't work!Stoppage
M
5

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;
}
Modulus answered 19/1, 2018 at 20:53 Comment(1)
Hello from 2022. This is still the correct way to do it for Xamarin. Adding the "Xamarin" keyword here in the hope that it boosts the chances of someone else coming across this answer.Saundra
I
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*/
}
Internist answered 6/5, 2013 at 7:13 Comment(2)
This method is not called if the app is not running. Thats what asked hereIridotomy
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).Ostracod
I
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
    }
}
Incommunicable answered 21/12, 2016 at 9:15 Comment(4)
but how to know if application is opened by tapping push notification when app is terminatedUintathere
when someone tapping on the push, the app will be open, no matter it was terminated or not. and the .inactive case is callingIncommunicable
i need to detect if app is opened by taping the push and wants to navigate to respective content i saw instagram doing thatUintathere
How about local notifications?Fossil
E
3

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
}
Eatton answered 7/10, 2020 at 17:7 Comment(1)
This is the correct approach in 2021. launchOptions in AppDelegate is nil if using SceneDelegate.Obvert
I
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.

Iridotomy answered 6/5, 2013 at 7:23 Comment(0)
C
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
    }
  }
}
Chevalier answered 16/11, 2017 at 9:6 Comment(2)
better put this as a comment since this is not the answer.Joettejoey
@Joettejoey Thanks for suggestion, Updated the answer with all the detailsChevalier
F
2

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
        }
Forbid answered 31/3, 2020 at 13:9 Comment(1)
Perfect answer!Plagal
D
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)
        }
    }
}
Donegal answered 31/5, 2018 at 12:35 Comment(0)
R
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
    }
    ...
}
Rodomontade answered 28/6, 2018 at 8:27 Comment(0)
E
1

2021, Swift 5, Local notifiactions only :

UNUserNotificationCenter.current().delegate = self

extension YourClass: UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let notificationIdentifier = response.notification.request.identifier

        // If this is called, then your app was opened from a local notification with this identifier
    }
}


Embarkment answered 16/7, 2021 at 15:33 Comment(0)
S
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")
            }
    }
Sirois answered 16/3, 2016 at 19:35 Comment(1)
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..Cortisol
U
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.

Uranus answered 14/3, 2017 at 14:0 Comment(0)
D
0

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

Deposal answered 16/11, 2017 at 9:47 Comment(0)
B
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
    }
}
Biramous answered 20/6, 2019 at 10:38 Comment(0)
S
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

Sac answered 1/2, 2021 at 8:51 Comment(0)
S
-1

You can use:

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

to handle the remote push notifications.

Check here the documentation

Sturdivant answered 6/5, 2013 at 7:12 Comment(0)
P
-1

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

Praedial answered 10/2, 2015 at 15:27 Comment(0)
H
-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)")

    }
}
Hawkes answered 21/8, 2015 at 15:1 Comment(0)
A
-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
Amann answered 13/1, 2016 at 21:22 Comment(2)
Delegate has no member navigationControllerJaqitsch
Create a navigation controller in AppDelegate.h file. Am using it and it works!Amann
I
-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.

Ichor answered 7/10, 2016 at 9:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.