didReceiveLocalNotification fired twice - ios

I understand that didReceiveLocalNotification will fire twice. First it’ll fire when the local notification is fired and then when the user selects it.
Is there any way to know that the didReceiveLocalNotification is fired by user selection?
Currently when the notification fires (while keeping the app open), if I keep the drawer open the app automatically navigates to the screen. I don’t want it to happen. Only when user makes the selection it should navigate to specific screen.
Thanks in advance.

When app is in state inactive (with OS notifications pulled down), the didReceiveLocalNotification seems to be called twice.
1. when notification arrives
2. if user taps on notification
What you can do is to catch inactive state at step 1 and do not perform your indented action.
Or you can do the opposite which is to remove the notification from notification center so as user can't tap it (step 2)
if (UIApplication.sharedApplication().applicationState == .Inactive) {
application.cancelLocalNotification(notification)
}

When the Application is launch by notification, app delegate launch options should contain the key UIApplicationLaunchOptionsLocalNotificationKey which in gives you the UILocalNotification associated with the notification.
if App is open and your target is iOS 8 then a new handler added
// Called when your app has been activated by the user selecting an action from a local notification.
// A nil action identifier indicates the default action.
// You should call the completion handler as soon as you've finished handling the action.
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void(^)())completionHandler NS_AVAILABLE_IOS(8_0);
also try to check if LocalNotification is cause the App launch then
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
when you are setting the local notification you can get the user dictionary in which you can set the Unique ID to keep track
notification.userInfo = [NSDictionary dictionaryWithObject:unique_id forKey:#"uniqueId"];

Related

Detect user tap on local notification

I show my local notification like this periodically.
UILocalNotification *notification = [[UILocalNotification alloc]init];
[notification setAlertBody:#"Test test"];
[notification setUserInfo:#{#"test": #"test"}];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
I need to detect back that notification and I plan to write here.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
It always call that function whether user tap on notification or it automatically call in foreground.
So, I separate using this.
if (application.applicationState == UIApplicationStateActive)
When I show notification center, it become InActive. But, it still call didReceiveLocalNotification. I can't differentiate whether user tap on notification from notification center or because of my periodic posting notification.
How can I really know that I tap on notification (Either from InActive State or Background State) in didReceiveLocalNotification?
Assuming that I understood your issue correctly, I stumbled on the same obstacle and couldn't find a super clean solution.
So the situation where the following method
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
is called and applicationState is equal to UIApplicationStateInactive happens in two cases:
the app is in the foreground and the notification has just been fired
the notification has been fired some time ago, notification center is pulled down and user tapped on the notification
One way to distinguish these two cases is to check the notification's fireDate:
notification.fireDate.timeIntervalSinceNow < 0.5
If this expression is true, it's very likely that the first case happened. If the expression is false, it's very likely that the second case happened.
This solution depends on the system delivering the notification without delay and/or the user not being fast enough to click the notification in the notification center under 500ms since the notification's firing. I'm not sure how likely is it for a firing delay to happen. I guess it's possible if the device is under some kind of processing load.
I hope there is a cleaner solution, hopefully someone will share it.
First of all, read this from Apple Documentation:
The user taps a custom action button in an iOS 8 notification. In this
case, iOS calls either
application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
or
application:handleActionWithIdentifier:forLocalNotification:completionHandler:.
In both methods, you get the identifier of the action so that you can
determine which button the user tapped. You also get either the remote
or local notification object, so that you can retrieve any information
you need to handle the action.
The user taps the default button in the alert or taps (or clicks) the
app icon. If the default action button is tapped (on a device running
iOS), the system launches the app and the app calls its delegate’s
application:didFinishLaunchingWithOptions: method, passing in the
notification payload (for remote notifications) or the
local-notification object (for local notifications). Although
application:didFinishLaunchingWithOptions: isn’t the best place to
handle the notification, getting the payload at this point gives you
the opportunity to start the update process before your handler method
is called.
Second, this is how you can differentiate whether didReceiveLocalNotification: is called from active or inactive state:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState appState = UIApplicationStateActive;
if ([application respondsToSelector:#selector(applicationState)])
appState = application.applicationState;
if (appState == UIApplicationStateActive)
{
}
else
{
}
}
application:didReceiveLocalNotification:
Sent to the delegate when a running app receives a local notification.
Check this:
iOS UILocalNotification - No delegate methods triggered when app is running in background and the icon is clicked upon notification
Use KVO key-value-observing to know and do something when the button is tapped.

Identifying the trigger of iOS Remote notifications callback

Suppose you implement email client application.
When a new mail is received, a notification should be displayed, and the app should pull the new mail ASAP.
When the user taps the notification in the notification center, the app should display the new mail.
However, if he opens the application directly (not through the notification) he should see the list of mails already updated with the latest data.
Ideally, the app would have a callback called upon arrival of a notification with {content-available:1} for updating the data, and another callback when the user clicks the notification.
However, if the app implements application:didReceiveRemoteNotification:fetchCompletionHandler: the same callback might be called twice - once immediately when the notification arrives, and again if the user clicks the notification in the notification center.
So, when the callback is triggered, how should the app know if it should navigate to the new mail (when the user taps the notification) or fetch the data (called by the system)?
Update - I tried querying the applicationState according to this answer. This doesn't help as when the user opens the control center and a notification is received (but not tapped), the application is in inactive state, same as it is when the user taps the notification.
I agree that it's rather strange and inconvenient that the same method is called for both "background-fetch" and "user-did-tap-on-notification".
You should be able to use the app's UIApplicationState to take action on a Notification, either by triggering a background fetch or presenting UI.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
switch ([UIApplication sharedApplication].applicationState) {
case UIApplicationStateBackground: {
// Fetch data in background
// TODO
break;
}
case UIApplicationStateInactive:
// User Tapped Notification
// If needed:
// Store the Notification, to be presented when app becomes active.
self.notificationToPresentWhenActive = userInfo;
completionHandler(UIBackgroundFetchResultNoData);
break;
case UIApplicationStateActive:
// App was open
// Present the in-app Notification UI
[self presentNotification:userInfo];
completionHandler(UIBackgroundFetchResultNoData);
break;
}
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
if (self.notificationToPresentWhenActive) {
[self presentNotification:self.notificationToPresentWhenActive];
self.notificationToPresentWhenActive = nil;
}
}
Update - I tried querying the applicationState according to this answer. This doesn't help as when the user opens the control center and a notification is received (but not tapped), the application is in inactive state, same as it is when the user taps the notification.
I can't reproduce this issue (iOS 9.0.2) using either Control Center (bottom) or Notification Center (top). Perhaps it was fixed in iOS 9 or one of the betas?

Handling local notifications when the user presses the icon instead of the alert

Here is the situation that I want to handle quoted from Apple's documentation.
As a result of the presented notification, the user taps the action button of the alert or taps (or clicks) the application icon.
If the action button is tapped (on a device running iOS), the system launches the application and the application calls its delegate’s application:didFinishLaunchingWithOptions: method (if implemented); it passes in the notification payload (for remote notifications) or the local-notification object (for local notifications).
If the application icon is tapped on a device running iOS, the application calls the same method, but furnishes no information about the notification . If the application icon is clicked on a computer running OS X, the application calls the delegate’s applicationDidFinishLaunching: method in which the delegate can obtain the remote-notification payload.
How do I handle this situation if there is no information about the notification?
If I understand you correctly, it sounds like you have a UILocalNotification that has been fired, but you need to still handle it if the user taps the application icon instead of the notification. Correct?
If this is the case, then to my knowledge you won't be able to handle the notification from the app delegate, because the app is not being launched or brought out of the background by the notification, but instead by the user's interaction.
However, if you are setting a badgeNumber on the application with the notification then you could try something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {
UILocalNotification *notification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
} else if ([UIApplication sharedApplication].applicationIconBadgeNumber > 0) {
// Assume that user launched the app from the icon with a notification present.
}}
You may also have to check the badgeNumber in - (void)applicationDidBecomeActive:(UIApplication *)application as well.
Improve to #Aron Crittendon answer:
Consider also to handle that in applicationDidBecomeActive:
-(void)applicationDidBecomeActive:(UIApplication *)application
{
if ([UIApplication sharedApplication].applicationIconBadgeNumber > 0) {
//application is in background, fired notification and user tapped app icon with badge
}
}
As the documentation states, if you tap the icon on iOS (and not the notification's alert/banner) then the same method is called but you get no notification information. There is no way to handle a local notification simply by tapping the app icon.

Detect when fire of local notification

There is a way to detect when a localnotification is fire??
For example i have one notification that is fire 12:00 a.m it show the notification there is way to know if the user touch the notification.
Because if the user no touch the notification i want to set other alarm to fire at 20 minutes after if it not touch the notification when is fire.
Call -(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification in the app delegate to know when the user returns to the app from a notification. Note: this is only called if the application was not closed, i.e. closed via multi-tasking. I'd suggest setting a timer for 12:00AM to set another notifcation and if the user returns to the application from the first notification then cancel the second one.
while application in forground or background then this method call when fire notification in appDelgate
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
}
if application close then click on notification then didFinishLaunchingWithOptions method check is local notification
if ([[launchOptions allKeys] UIApplicationLaunchOptionsLocalNotificationKey]) {
}

Reload table view in real time

The app receives a UILocalNotification, but if the user is at the UITableViewController at the fire time, the table view (containing the scheduled notifications) does not reload. The user has to get out of that view and load the view again so that the cells are loaded and, as the notification was already fired, it will not be displayed on any cell of that table view.
Problem is: If the user touches the specific tableView cell that contained the notification that just fired, the app crashes, cause the notification is not there anymore.
I've implemented the - (void)reloadData in every place possible, and it still doesn't load in real time.
What would be a better solution for this?
Other detail, how can I push a specific view after the notification is displayed (when the user slides the app icon when the phone is locked)?
Any help will be truly appreciated, since theres are the last details remaining to publish my first App.
The problem you describe is caused by the current local notification: While the notification is handled, it is still in the list of scheduled notifications, so refreshing the table view will have no effect. A solution to this problem is to defer reloading the table view until after the notification is handled, something like
// In your app delegate
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.localNotificationsController reloadDataIfNeeded];
});
}
// In your controller that shows the local notifications
- (void)reloadDataIfNeeded
{
if (![self isViewLoaded]) return;
[self.tableView reloadData];
}
You could also delete the notification from the list if you only use notifications that only fire once (so that you are sure that the notification will disappear anyway):
// In your app delegate
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[application cancelLocalNotification:notification];
[self.localNotificationsController reloadDataIfNeeded];
}
As to the second part of your question ("how can I push a specific view after the notification is displayed (when the user slides the app icon when the phone is locked?"), there are two scenarios how an application can be activated by a local notification.
Your application was suspended, but still in memory. Then selecting the local push notification will make the app enter the foreground, -application:didReceiveLocalNotification: will be called, and [application applicationState] will be UIApplicationStateInactive
Your application is not running, i.e. not suspended, not in memory. Then you will receive the local notification in the launchOptions in -application:didFinishLaunchingWithOptions::
UILocalNotification *localNotification = [launchOptions valueForKey:UIApplicationLaunchOptionsLocalNotificationKey];
Despite what the documentation says, -application:didReceiveLocalNotification: will not be called in this case.
So to show the notification that woke up the application, you can push your controller in these two cases.

Resources