Detect if the app was launched/opened from a push notification
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?
Solution 1:
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
Solution 2:
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");
}
Solution 3:
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:
- Determine if the user is opening the app from a push
- Update the view based on the push state
- 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:
- Store a
notificationUserInfo
instance variable on the AppDelegate. - Set
notificationUserInfo = nil
in bothapplicationWillEnterForeground
anddidFinishLaunchingWithOptions
. - Set
notificationUserInfo = userInfo
indidReceiveRemoteNotification:inactive
- From
applicationDidBecomeActive
always call a custom methodopenViewFromNotification
and passself.notificationUserInfo
. Ifself.notificationUserInfo
is nil then return early, otherwise open the view based on the notification state found inself.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.
Solution 4:
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.
Solution 5:
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