First push notification disappears after few seconds - ios

My app first notification disappears after few seconds itself when app is in background even when the user has not taken any action to that notification. After that every notification remains in place.
Does setting application badge number to 0 or cancelling local notifications has any effect on this.(I tried commenting those parts but it did not fix).
`func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
-> Bool {
Fabric.with([Crashlytics.self])
registerForPushNotifications(application)
application.registerForRemoteNotifications()
//Firebase push notification
FIRApp.configure()
// Add observer for InstanceID token refresh callback.
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(self.tokenRefreshNotification),
name: kFIRInstanceIDTokenRefreshNotification,
object: nil)
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
}`
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
guard let aps = userInfo["aps"] as? [String: AnyObject] else{
return
}
let state = UIApplication.sharedApplication().applicationState
if state == .Background || state == .Inactive{
//do some background work
}
}
Any help would be greatly appreciated.

The issue is due to manually setting the badge number. I had code in my app to manually set the badge number on application launch (either to 0 or to a value saved in NSUserDefaults, depending on the situation) and found that doing either one of these would cause the notification banner to disappear and the sound would cut off mid-notification. To solve it, I checked the applicationState and if it was UIApplicationStateBackground I did not touch the badge number.
So, based on your code above, either remove the line in didFinishLaunching to set the badge to 0, or ensure the application state is not background before setting to 0.
I posted a similar answer here:
https://stackoverflow.com/a/53381819/2788608

Related

Swift didReceiveRemoteNotification firing while in app

Here is my code for didReceiveRemoteNotification:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
print("notification recieved: \(userInfo)")
// Pass push notification payload to the shared model
let payload: NSDictionary = userInfo as NSDictionary
if let variable = payload["variable"] as? String {
NotificationManager.SharedInstance.handleVariableNotification(variable)
}
}
The code works and properly does what I want it to when I click on the notification from outside the app.
My issue is: if I get a notification while I'm currently in the app, it still runs the code from the notification and overrides anything the user is currently doing in the app
I only want the code to run if the user clicks on the notification, not automatically if I'm already in the app.
Thanks in advance!
Wrap your code in:
if (application.applicationState != .active){}
It will check if you are currently in the app, and fires the code only if the app was inactive or in the background.
Inside your didReceiveRemoteNotification delegate method:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
switch application.applicationState {
case .active:
print("Application is open, do not override")
case .inactive, .background:
// Pass push notification payload to the shared model
let payload: NSDictionary = userInfo as NSDictionary
if let variable = payload["variable"] as? String {
NotificationManager.SharedInstance.handleVariableNotification(variable)
}
default:
print("unrecognized application state")
}
}
Also if your application is being launched via user opening a remote notification sent by your application you will need to do this inside your app delegate didFinishLaunchingWithOptions method:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Check to see if launchOptions contains any data
if launchOptions != nil {
// Check to see if the data inside launchOptions is a remote notification
if let remoteNotification = launchOptions![UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary {
// Do something with the notification
}
}
return true
}

Application icon badge number is not changing to zero on launching the application by touching the app icon

I am developing a local notification code . I am being able to display the badge number as per the number of notifications fired and also I am able to make all the icons to 0 when user opens the notification,But if the user launches the app the badge is not set to 0. It is only set to 0 when user opens the notification. I have read multiple answers & multiple articles and couldn't find a solution . In order to register my local notification I have the following code -
func addItem(item: TodoItem)
{
// persist a representation of this todo item in NSUserDefaults
var todoDictionary = NSUserDefaults.standardUserDefaults().dictionaryForKey(ITEMS_KEY) ?? Dictionary()// if todoItems hasn't been set in user defaults, initialize todoDictionary to an empty dictionary using nil-coalescing operator (??)
todoDictionary[item.UUID] = ["deadline" : item.deadline, "title" : item.title, "UUID" : item.UUID]// store NSData representation of todo item in dictionary with UUID as key
NSUserDefaults.standardUserDefaults().setObject(todoDictionary, forKey: ITEMS_KEY)
let notification = UILocalNotification()
notification.alertBody = "Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification
notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view"
notification.fireDate = item.deadline // todo item due date (when notification will be fired)
notification.soundName = UILocalNotificationDefaultSoundName // play default sound
notification.userInfo = ["title": item.title, "UUID": item.UUID] // assign a unique identifier to the notification so that we can retrieve it later
let nextbadge = (UIApplication.sharedApplication().scheduledLocalNotifications?.count)! + 1
notification.applicationIconBadgeNumber = nextbadge//here I provide an incremental badge number to my local notification.
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
In my appdelagate class
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification)
{
NSNotificationCenter.defaultCenter().postNotificationName("ToDoListShouldtRefresh", object: self)
application.applicationIconBadgeNumber = 0;
}
Now in objective C I was usually doing like this to clear badges on application launch -
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Handle launching from a notification
UILocalNotification *locationNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (locationNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
return YES;
}
I tried something like this but with vain -
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
application.registerUserNotificationSettings(UIUserNotificationSettings (forTypes:[.Alert, .Badge, .Sound], categories: nil))
if let notification:UILocalNotification = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification
{
application.applicationIconBadgeNumber = 0
}
return true
}
So how to do it now in SWIFT?
UIApplicationLaunchOptionsLocalNotificationKey will be set only if user opens app via notification.
So, if you need to set applicationIconBadgeNumber to 0 right after launch in all cases, you need no check for UIApplicationLaunchOptionsLocalNotificationKey:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
application.registerUserNotificationSettings(UIUserNotificationSettings (forTypes:[.Alert, .Badge, .Sound], categories: nil))
application.applicationIconBadgeNumber = 0
return true
}
You have to think if opening the app without clicking the notification will allow the user to see that he has available the notification content. And what if he has many of them?
If the user can see the content or events of all of the notifications at once, you can set applicationIconBadgeNumber to 0 every time the app becomes active, or you might keep decreasing it only when the user gets to click or see the content.
So I would do it on applicationDidBecomeActive so it also sets it to 0 if the app was in background.
func applicationDidBecomeActive(_ application: UIApplication) {
application.applicationIconBadgeNumber = 0
}
no problem with applicationIconBadgeNumber. the problem is with localnotification. addobserver and name to localnotification

Checking if iOS app was launched from local notification?

How do you detect if an app that is NOT in an active, inactive, or background state (terminated) is launched from a local notification? So far, I've tried two methods in the App Delegate's didFinishLaunchingWithOptions:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// METHOD 1:
if let options = launchOptions {
if let key = options[UIApplicationLaunchOptionsLocalNotificationKey] {
notificationCenter.postNotification(NSNotification(name: "applicationLaunchedFromNotification", object: nil))
}
}
// METHOD 2:
let notification = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as! UILocalNotification!
if (notification != nil) {
notificationCenter.postNotification(NSNotification(name: "applicationLaunchedFromNotification", object: nil))
}
return true
}
In my View Controller, I observe for the notification in ViewDidLoad and in response, set a UILabel's text:
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.addObserver(self, selector: "handleAppLaunchFromNotification", name: "applicationLaunchedFromNotification", object: nil)
}
func handleAppLaunchFromNotification() {
debugLabel.text = "app launched from notification"
}
But the UILabel's text is never set after launching the terminated app from a local notification.
My questions are:
What am I doing wrong?
Is there an easier way to debug a situation like this other than setting a UILabel? Once the app is terminated, Xcode's debugger detaches the app and I can't use print().
You are checking local notification in didFinishLaunchingWithOptions this methods contains launchOptions for only remote notifications. If app is in terminated state and you perform action on local notification then didReceiveLocalNotification gets call after didFinishLaunchingWithOptions method.

Clicking in UILocalNotifications when the app is closed

I'm doing this app that needs to send notifications when an event is coming.
Everything is working but when the app is closed and i open through the notification it doesn't fire how it is supposed to.
My App Delegate didFinishLaunchingWithOptions and didReceiveLocalNotification
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
createCopyOfDatabaseIfNeeded()
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: UIUserNotificationType.Sound |
UIUserNotificationType.Alert | UIUserNotificationType.Badge, categories: nil))
if let options = launchOptions {
let value = options[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification
if let notification = value {
self.application(application, didReceiveLocalNotification: notification)
}
}
return true
}
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
// Do something serious in a real app.
println("Received Local Notification:")
println(notification.alertBody)
if notification.alertAction == "editList" {
NSNotificationCenter.defaultCenter().postNotificationName("modifyListNotification", object: nil);
}
}
In my main tab controller:
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleModifyListNotification", name: "modifyListNotification", object: nil)
super.viewDidLoad()
self.repaint()
}
func handleModifyListNotification(){
dispatch_async(dispatch_get_main_queue(), {
// Show the alert
})
}
The main objective is when a notification is pressed with the app closed, it opens, executes didFinishLaunchingWithOptions and checks for UILocalNotification in launchOptions and calls didReceiveLocalNotification.
ps: i know with debug that the notifications is being well received, the system is just not calling the didReceiveLocalNotification method, like it should.
1º Edit
I'm really calling the didReceiveLocalNotifaction method because i just tried this and it worked
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
// Do something serious in a real app.
println("Received Local Notification:")
println(notification.alertBody)
if notification.alertAction == "editList" {
var xpto = UIAlertView()
xpto.title = notification.alertAction!
xpto.show() NSNotificationCenter.defaultCenter().postNotificationName("modifyListNotification", object: nil);
}
}
So i gess this must be a timing issue. I really want my tab controller to react to this notification.
NSNotificationCenter.defaultCenter().postNotificationName("modifyListNotification", object: nil);
If I'm not mistaken, -didReceiveLocalNotification: is only called when the app is in active state and being used.
If you're attempting to launch the app from a background state, I would suggest using -didFinishLaunchingWithOptions: and querying your options.
For this reason, maybe have your notification handling code in a separate method and have it called from both -didReceiveLocalNotification & -didFinishLaunchingWithOptions ?

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?
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
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");
}
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 both applicationWillEnterForeground and didFinishLaunchingWithOptions.
Set notificationUserInfo = userInfo in didReceiveRemoteNotification:inactive
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.
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.
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
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
}
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");
}
}
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
}
}
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;
}
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*/
}
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
}
}
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
}
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])
}
}
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.
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
}
}
}
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
}
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)
}
}
}
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
}
...
}
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
}
}
// 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")
}
}
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.
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
}
}
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
You can use:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
to handle the remote push notifications.
Check here the documentation
I haven't tried it yet but maybe you could send yourself a notification? http://nshipster.com/nsnotification-and-nsnotificationcenter/
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
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)")
}
}
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
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.

Resources