When my App is not running and receives a Push Notification, if I click on that notification, the App is launched - but then it doesn't prompt the user with the Alert-View I set up, asking them whether they want to view the Notification's contents or not. It just launches, and sits there.
The Push Notifications do work perfectly when the App is running - either as the Active app or while in the background - but nothing works correctly when the app is not running.
I tried logging-out the launchOptions NSDictionary in application: didFinishLaunchingWithOptions: to see what load its bringing - but it comes up as "(null)". So It basically contains nothing - which doesn't make sense cause shouldn't it contain the Notification's load?
Anybody have any ideas how to make Push Notifications work when they arrive while the App was NOT running?
I mean how to handle the Push notifications when the App is in not running state. What if, if you receive many notifications & you did not open the app, neither did you tap the system's notification panel. How are you preserving those push for a later retrieval.
1) When application is running in background and When application is running in foreground
application:didReceiveRemoteNotification: method will called as below.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if (application.applicationState == UIApplicationStateInactive)
{
// opened from a push notification when the app was on background
NSLog(#"userInfo->%#", [userInfo objectForKey:#"aps"]);
}
else if(application.applicationState == UIApplicationStateActive)
{
// a push notification when the app is running. So that you can display an alert and push in any view
NSLog(#"userInfo->%#", [userInfo objectForKey:#"aps"]);
}
}
2) When application is not launched (close) then application:didFinishedLaunchingWithOptions method will called.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (launchOptions != nil)
{
// opened from a push notification when the app is closed
NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (userInfo != nil)
{
NSLog(#"userInfo->%#", [userInfo objectForKey:#"aps"]);
}
}
else
{
// opened app without a push notification.
}
}
3) There is no way to remove a specific notification as of. The way to remove all the notifications from your app so they don't show in the Notification Center when the user opens the app from one of them, is to set the app badge to 0.
As per your question, there is no way to hold all the notification when you open the app, better you call an api to get all notification as per time stamp from your back end/server that's how Facebook does.
You can retrieve notifications delivered to your app by using the getDeliveredNotifications(completionHandler:) method. Note that this only returns notifications currently displayed in the Notification Center and not the ones that have been manually cleared by the user.
UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
// notifications: An array of UNNotification objects representing the local
// and remote notifications of your app that have been delivered and are still
// visible in Notification Center. If none of your app’s notifications are
// visible in Notification Center, the array is empty.
// As said in the documentation, this closure may be executed in a background
// thread, so if you want to update your UI you'll need to do the following:
DispatchQueue.main.sync { /* or .async {} */
// update UI
}
}
The app does not process push notification in the background, what it really does the OS is wake up the app once you press in the notification. You can catch this moment in the following way:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]) {
// Your app has been awoken by a notification...
}
}
There is no way of handling this on application end.
You would need to maintain the unread badge count at the server.
When the app is killed the badge value is updated from the server.
So when you open the application any time , you would need to call a web service to get the required notifications and update the badges of tabbar(if used).
Perform actions after notification is received in the terminated state - iOS 13 - Scene Delegate
Recently I came across an issue where I received remote push notification when my app was in the terminated state. In the latest iOS versions Scene delegate is responsible to handle view life cycle methods rather than App Delegate.
Older iOS versions - Handled by App Delegate
When the app is terminated and remote push notification is received the payload is reflected in the didfinishLaunchingWithOptions method of App delegate. Using the launch parameter of this method it’s possible to get the payload data and perform any interaction.
New iOS version - Handled by Scene Delegate
Similarly when the app is terminated and remote push notification is received the payload is reflected in the scene(willConnectTo session) of the scene delegate. Using the connectingOption parameter of this method it’s possible to get the payload data and perform any interaction.
Hint: To perform any interaction or pass this payload to another view controller once the view controller is set as root view controller keep a delay of some seconds to pass the data.
Example code:
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
if defaults.string(forKey: UserDefaultsKeys.TenantID.rawValue) != nil && connectionOptions.notificationResponse != nil {
let rootViewController = UINavigationController(rootViewController: DashboardVC())
window?.rootViewController = rootViewController
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
NotificationCenter.default.post(
name: .passApnsDataToDashboardNotification,
object: nil,
userInfo: connectionOptions.notificationResponse?.notification.request.content.userInfo)
}
}
You can display the alert after launching a previously terminated app from a notification like this:
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
// display the alert
}
For your use case, a BGTask is the best solution which can call the server and get the data and update your database, so that next time, you have available data immediately.
BGTask scheduling is done by OS based on 7 factors (like app usage, batter life, rate limit etc), If your app does not execute any BGTask (you can check easily by saving it to UserDefalts), you can fetch the server data explicitly when the app is live.
Just FYI:
You can configure and Use UserNotificaions to receive a local/remote notification
https://developer.apple.com/documentation/usernotifications?language=objc
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void(^)(void))completionHandler
https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649501-usernotificationcenter?language=objc
Related
Under IOS, when the application is not running, and I send a message via firebase clound messaging with notification + data payload, then on the ios device a notification alert will be shown to the end user. However, when the user click on it, how to retrieve the data payload of the notification? Because the app will be launch, but no event will be fired in the application (ie: DidReceiveRemoteNotification is not fired)
Also, when the app is in the background, the notification alert will be also shown to the end user. However, when the user click on it, the event DidReceiveRemoteNotification will be fired with the data payload
When the app is closed, the contents of the notification will be delivered in your Appdelegate method didFinishLaunchingWithOptions as launchOptions parameter.
What you could do is, to call your DidReceiveRemoteNotification method if that happens, for example like this (Objective-C):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// check if a remote notification was received while the app was closed, then take the necessary actions
if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
[self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
}
}
In didFinishLaunchingWithOptions (AppDelegate ) :
// Do what you want to happen when a remote notification is tapped.
if launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] != nil {
let userInfo: NSDictionary = launchOptions![UIApplicationLaunchOptionsKey.remoteNotification] as! NSDictionary
}
Is there any way to find out after notification is been sent, how many users clicked on the notification and how many people didnt click on the noficiation event (badge) when the app is in the background?
I am more interested to find out how many people didnt click, as people who clicked can be tracked as app will go in the foreground and request can be made vs if app is in the background, your http request may get lost.
update your app delegate code to the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
[super application:application didFinishLaunchingWithOptions:launchOptions];
NSDictionary *remoteNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(remoteNotif)
{
//launched from push notification
}else{
//Did not launch from push notification (tapped on app icon, or from multi tasking)
//**Didn't click on notification**
}
}
and this:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if([application applicationState] == UIApplicationStateActive) {
// app was open, did not display the push alert/banner/badge
// **Didn't click on notification**
}else{
//launched from push notification
}
}
Its quite self explanatory. you can track when app was opened by tapping on a push notification and when it was opened without tapping on a notification.
I guess the closest you can come to know who didn't click your notification is by checking in your AppDelegate's didFinishLaunchWithOptions method that your app didn't get launched as a result of the user tapping a notification after you send out the notification. In other words, I think you answered your own question in your question.
I am not sure if this is possible, but I need to grab all of the push notification userinfo when the user opens up the App. I can get all of the push notification userinfo when the App is opened or in the background, but not when the App is completely closed. Any way around this? The code below is how I get the userInfo currently.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
id data = [userInfo objectForKey:#"data"];
NSLog(#"data%#",data);
}
Unfortunately, it's not currently possible client side with that method to query old notifications that have occurred while the app was completely closed. See this question: didReceiveRemoteNotification when in background.
A way around it is to keep track of which notifications you send from your server per user. When didReceiveRemoteNotification: is called, you can take that notification and compare it against the server's messages for the current user. If one of them matches, mark it some way on the server. That way, if there are messages sent when your app is backgrounded, you can query for messages that haven't been marked from the server and get all 'missed' notifications.
The method you are implementing cannot handle both cases. See the "Local and Push Notification Programming Guide":
If your app is frontmost, the application:didReceiveRemoteNotification: or application:didReceiveLocalNotification: method is called on its app delegate. If your app is not frontmost or not running, you handle the notifications by checking the options dictionary passed to the application:didFinishLaunchingWithOptions: of your app delegate...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Notifications
NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(userInfo){
//open from notification message
}
return YES;
}
You can add this code to your AppDelegate's applicationWillEnterForeground method:
-(void)applicationWillEnterForeground:(UIApplication *)application {
// this method is called when staring an app that was closed / killed / never run before (after applicationDidFinishLaunchingWithOptions) and every time the app is reopened or change status from background to foreground (ex. returning from a mobile call or after the user switched to other app and then came back)
[[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
NSLog(#"AppDelegate-getDeliveredNotificationsWithCompletionHandler there were %lu notifications in notification center", (unsigned long)[notifications count]);
for (UNNotification* notification in notifications) {
NSDictionary *userInfo = notification.request.content.userInfo;
if (userInfo) {
NSLog(#"Processed a notification in getDeliveredNotificationsWithCompletionHandler, with this info: %#", userInfo);
[self showPushNotificationInAlertController:userInfo]; // this is my method to display the notification in an UIAlertController
}
}
UIApplication.sharedApplication.applicationIconBadgeNumber = 0;
}];
}
}
Remove this line from the method application didFinishLaunchingWithOptions: if you had included it there, because it clears the badge number and also all notifications in notifications center:
UIApplication.sharedApplication.applicationIconBadgeNumber = 0;
This is currently working in iOS 12, hadn't had the chance to test it in earlier versions.
application: didReceiveRemoteNotification: fetchCompletionHandler:
is different from
application: didReceiveRemoteNotification:
How? from the docs:
Unlike the application:didReceiveRemoteNotification: method, which is
called only when your app is running, the system calls this method
regardless of the state of your app. If your app is suspended or not
running, the system wakes up or launches your app and puts it into the
background running state before calling the method. If the user opens
your app from the system-displayed alert, the system calls this method
again so that you know which notification the user selected.
My struggle is: I want to know if the method was called by the user tapping an a system-displayed alert from the Notification Center or from a silent push notification that wakes up the device. Currently, as far as I can see, there is no obvious way to differentiate.
- (BOOL)application: didFinishLaunchingWithOptions:
Tracking the launchOptions in the above method is not a solution because it's only called if the app is suspended/not running in background. If it's running in the background it doesn't get called.
The Apple docs are a bit confusing
application: didReceiveRemoteNotification: fetchCompletionHandler:
is used if your application supports the remote-notification background mode (ie you're doing BackgroundFetch.)
application: didReceiveRemoteNotification:
is called when the OS receives a RemoteNotification and the app is running (in the background/suspended or in the foreground.)
You can check the UIApplicationState to see if the app was brought to foreground by the user (tapping on notification) or was already running when notification comes in.
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
UIApplicationState state = [application applicationState];
// user tapped notification while app was in background
if (state == UIApplicationStateInactive || state == UIApplicationStateBackground) {
// go to screen relevant to Notification content
} else {
// App is in UIApplicationStateActive (running in foreground)
// perhaps show an UIAlertView
}
}
You could check the UIApplication's applicationState to distinguish silent calls from calls made with the application being actively used by the user:
typedef enum : NSInteger {
UIApplicationStateActive,
UIApplicationStateInactive,
UIApplicationStateBackground
} UIApplicationState;
Or keep your own flag set on the delegate's applicationDidEnterBackground:.
Application State is not reliable because if you have control center or Apple's notification center open over your app, application: didReceiveRemoteNotification: fetchCompletionHandler: will get called and the application state will be Inactive.
I'm having the same issue trying to respond to a click on the notification while the app is in the background and there doesn't seem to be a reliable way to solely identify this.
In case of swift
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let state : UIApplicationState = application.applicationState
if (state == .Inactive || state == .Background) {
// go to screen relevant to Notification content
} else {
// App is in UIApplicationStateActive (running in foreground)
}
}
In iOS 10.0+ you can use the method
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;
to detect when user taps on a system-displayed alert from the NotificationCenter.
If you implement the method above
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler; will be called only when a notification is received
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler; will be called when user taps on notification
When application: didReceiveRemoteNotification:fetchCompletionHandler: method is called app state is UIApplicationStateInactive if user taps on alert (in this case you would like to prepare some UI) and is UIApplicationStateBackground when app is silently woken (in this case you just load some data).
I'm not sure if I understand your question.
Do you want to differentiate between a silent push notification background fetch and a noisy push notification? You can simply check whether the push notification dictionary contains the "content-available" key: [[userInfo objectForKey:#"aps"] objectForKey:#"content-available"] If it does, then it should be a silent push. If not, it was a normal push.
Do you want to know if that background fetch method is called when the application receives a notification and it is in suspended/not running? If so, you can do the following:
Download and import LumberJacks into your app. Look through the directions and learn how to set it up such that you can save logs to the disk.
Put this in any method you want to see whether/when that method is invoked:
DDLogDebug(#"%# - %#",NSStringFromSelector(_cmd),NSStringFromClass([self class]));
This will print the class and the method to the log file.
Examine the log file after sending yourself a push notification to your background-fetch enabled app, and see if any of the methods get called by looking at your log file.
If you have set up your app correctly for background fetch, the method application: didReceiveRemoteNotification: fetchCompletionHandler: will be called even when the app is backgrounded/not running if you receive a push notification (silent push or not).
For Swift: In application(_:didFinishLaunchingWithOptions:) parse the application options. If they exist, you know the app was launched from them tapping.
if let remoteNotif = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? [String: Any] {
print("Remote notfi is \(remoteNotif)")
if let notification = remoteNotif["aps"] as? [AnyHashable : Any] {
/// - parse notification
}
}
Otherwise, you can handle the tap in, and you know that the app is open/background/inactiveapplication(_:didReceiveRemoteNotification:fetchCompletionHandler:)
I am using local notification in my app, the only thing i care about is what exact notification the user clicked.
the method
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
triggered when the notification is received but i need to handle the notification when the user clicked on it so this is useless to me.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
triggered only when app was not running, also not what i need
i searched the net but found only handles if the app is active or not running. how can i handle it when the app is on the background?
what i need in other words is to recognise the exact notification that the user clicked when the app was running on the background
When creating your UILocalNotification, you can set the userInfo to set any associated data/unique identifiers.
Ex.
UILocalNotification *someNotification = [[UILocalNotification alloc] init];
[someNotification setUserInfo:#{ kSomeUniqueIdentifierKey : #"identifier" }];
and then,
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if ([notification.userInfo[kSomeUniqueIdentifierKey] isEqualToString:#"identifier"]) {
// We know what notification was responded to based on userInfo
}
}
The above method fires either immediately upon receiving the notification while the app was running or when the user taps the notification that fired while your app was in the background.
If you want to ignore these notifications while the app is running, you could always check the state of the application to determine if it's responding to the notification while running or in the background.
I am working with iOS 9, and the solution is to check the launchOptions within the AppDelegate didFinishLaunchingWithOptions. As follows:
// Were we launched from a local notification?
if let lo = launchOptions
{
if let ln = lo[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification
{
// Do our thing...
}
}