I'm using the below code to set a badge on my app icon when a notification is received while the app is running in the background. That said, my code/log is never triggered when a notification is received while the app is minimized (see log: "NSLog APP WAS IN BACKGROUND") and I'm not sure why?
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[NSNotificationCenter defaultCenter] postNotificationName:#"pushNotification" object:nil userInfo:userInfo];
NSLog(#"application Active - notication has arrived while app was opened");
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"Notification received when open");
if(application.applicationState == UIApplicationStateInactive) {
NSLog(#"Inactive - the user has tapped in the notification when app was closed or in background");
completionHandler(UIBackgroundFetchResultNewData);
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UITabBarController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"tabBarController"]; // determine the initial view controller here and instantiate it with [storyboard instantiateViewControllerWithIdentifier:<storyboard id>];
[viewController setSelectedIndex:0];
;
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotificationReceived" object:nil];
}
if (application.applicationState == UIApplicationStateBackground) {
NSLog(#"APP WAS IN BACKGROUND");
static int i=1;
[UIApplication sharedApplication].applicationIconBadgeNumber = i++;
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application{
static int i=0;
[UIApplication sharedApplication].applicationIconBadgeNumber = i;
NSLog(#"Triggered!");
}
This is the correct behaviour for a remote notification.
Your app won't receive the didReceiveRemoteNotification call when your app is in the background, unless the user taps the notification alert.
Here's how it works.
1) When your app is in the background (or suspended) and a remote notification is received, an iOS system alert is shown.
2) If the user opens your app by tapping the notification alert, then your app will move the foreground and didReceiveRemoteNotification will be called.
3) If the user ignores the notification, or dismisses it, then your app remains in the background, and didReceiveRemoteNotification will not be called.
That said, there is no need to set the application badge in code. Your push notification payload, can include a key which iOS uses to set the application badge when the system receives your notification.
You simple include the key badge in your notification's payload:
{
"aps" : {
"alert" : {
"title" : "Notification title",
"body" : "Notification body"
},
"badge" : 5
}
}
I'd suggest you take a look at Apple's documentation for creating a remote notification payload, which explains all the options:
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification?language=objc
That all being said, it is possible to make sure iOS calls didReceiveRemoteNotification when your app is in the background.
To achieve this, you need to set the content-available parameter in your payload and send a "silent" notification. A silent notification means that the user will never see an alert, but your app will be silently brought to the foreground for a finite amount of time, and didReceiveRemoteNotification will be called.
It's not an appropriate choice for your scenario, though. It's intended for updating an app's content, not just updating the badge.
However, if you're interested in silent notifications, you can see the documentation here:
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_updates_to_your_app_silently?language=objc
[UIApplication sharedApplication].applicationIconBadgeNumber = 3;
Related
I'm sending push notification to app which is terminated and it seems that only this method is triggered. I want to open ViewController when app is launched with push notification, but it doesn't do anything just opens app.
I tried to achieve that with this code:
if (launchOptions != nil) {
NSDictionary* userInfo = [launchOptions valueForKey:#"UIApplicationLaunchOptionsRemoteNotificationKey"];
NSDictionary *apsInfo = [userInfo objectForKey:#"aps"];
if (apsInfo)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"openNews" object:nil userInfo:userInfo];
}
}
and tried this as well ..
if (launchOptions != nil) {
NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (userInfo != nil)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"openNews" object:nil userInfo:userInfo];
}
}
Any ideas how to launch ViewController when app is terminated with push notification?
So I have tried to save notification info to NSUserDefaults if launchOptions != nil just to check if Im receiving notification info and that part of code is triggered and it is but for some reason this part is not working:
[[NSNotificationCenter defaultCenter] postNotificationName:#"openNews" object:nil userInfo:userInfo];
but Im using same method and everywhere and it works fine
That won't work because you are posting a notification with no one to catch it yet.
Hmm, what you can do here is to set the initial ViewController of your application when it receives the launchOptions you specified.
You can set it using this:
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"YourStoryboard" bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"YourStoryboardId"];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
Be careful though in designing your navigations. Especially if you want your initial ViewController to be a page with back button that access the navigation stack.
Since you will make it your initial view controller, if it tries to popViewController, it will pop to nothing.
EDIT:
If you want it to be opened from the MainVC with a delay, you can put tnis in your MainVC
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(openAnotherVC:)
name:#"YourPostNotificationName" object:nil];
Then navigate to your desired VC in openAnotherVC: method
I'm struggling to get my iOS app to behave as intended when opened via a remote push notification (using Swift). What I want is, when the app is opened via tapping of a push notification, it should jump directly to a certain ViewController, but still maintain the navigation stack. And to complicate it further, the destination view controller depends on the push message.
Example: My app is closed and I get a push notification: "You got a new message". I click the notification and the app opens and shows the new message, instead of the regular initial view controller. If my app is open and I get a push notification, nothing happens.
In general, here are the methods to respond to a notification. Through these methods, you need to implement code that will present the appropriate view to your stack based on the notification.
To respond to notifications when the app is running in the foreground OR background implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method. If you enabled the remote notifications background mode, the system launches your app (or wakes it from the suspended state) and puts it in the background state when a remote notification arrives. However, the system does not automatically launch your app if the user has force-quit it.
To respond to notifications when your app is running in the foreground NOT background implement the application:didReceiveRemoteNotification: method
To respond to notifications when your app is NOT running implement the application:willFinishLaunchingWithOptions: OR application:didFinishLaunchingWithOptions: method
So what I ended up doing was:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .Inactive || application.applicationState == .Background {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = self.window?.rootViewController as? UINavigationController
let destinationController = storyboard.instantiateViewControllerWithIdentifier("dashboard") as? DashboardViewController
navigationController?.pushViewController(destinationController!, animated: false)
let destinationController2 = storyboard.instantiateViewControllerWithIdentifier("notificationsSettings") as? AppSettingsTableViewController
navigationController?.pushViewController(destinationController2!, animated: false)
}
}
So in didReceiveRemoteNotification I check which state the app comes from, and then I navigate to the viewController I want to present to the user. The reason I don't just go directly to the ViewController is because I want the navigation stack to be "intact" so the user can navigate back via the navigationController.
I have done same this in Objective C based on #NikMos Answer
// Handle notification messages after display notification is tapped by the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
#if defined(__IPHONE_11_0)
withCompletionHandler:(void(^)(void))completionHandler {
#else
withCompletionHandler:(void(^)())completionHandler {
#endif
if (([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) ||
([UIApplication sharedApplication].applicationState == UIApplicationStateInactive)) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UINavigationController *navigationController=[[UINavigationController alloc] init];
self.window.rootViewController =nil;
self.window.rootViewController = navigationController;
ScannerViewController *scannerVc = [storyboard instantiateViewControllerWithIdentifier:#"ScannerID"];
[navigationController pushViewController:scannerVc animated:YES];
NotificationVC * notificationVC = [storyboard instantiateViewControllerWithIdentifier:#"NotificationVCID"];
[navigationController presentViewController:notificationVC animated:YES completion:nil];
[self.window makeKeyAndVisible];
}
Do coding and enjoy coding. cheers :)
I have a textview and an imageview in my app so i can display the notification text and the image that i want to show. i receive this image from a url that i send via the notification. When i send notifications to my application and the app isn't running, the textview and the imageview doesnt show the content, but when the app is running or when the app state is in background the content updates.
I suppose that i have to configure the didFinishLaunchingWithOptions function when the app starts in order to update the fields of the view controller.
how can i do that? and why the didReceiveRemoteNotification function doesnt update the view when the app starts and updates the view only in two states (active & background)?
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSLog(#"New notification received!");
[self.window makeKeyAndVisible];
//Notification recieved & handle the aps
NSDictionary * notificationDict = [userInfo objectForKey:#"aps"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"refreshView" object:notificationDict];
NSString * alertString = [notificationDict objectForKey:#"alert"];
NSString * serverUrl = [notificationDict objectForKey:#"acme"];
NSLog(#"Notification text is: %#", alertString);
[[NSUserDefaults standardUserDefaults] setObject: alertString forKey: #"alertMSG"];
[[NSUserDefaults standardUserDefaults] synchronize];
//handle the notification when the app is active, inactive or in background
if ( application.applicationState == UIApplicationStateActive )
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"viewController2"];
[self.window.rootViewController presentViewController:vc animated:YES completion:nil];
application.applicationIconBadgeNumber = 0;
}
else if(application.applicationState == UIApplicationStateInactive)
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"viewController2"];
[self.window.rootViewController presentViewController:vc animated:YES completion:nil];
application.applicationIconBadgeNumber = 0;
}
else //ApplicationState Background
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"viewController2"];
[self.window.rootViewController presentViewController:vc animated:YES completion:nil];
application.applicationIconBadgeNumber = 0;
}
}
EDIT:
I have configure the didFinishLaunchingWithOptions to starts my controller when the app is launched from a notification, but the content still doesn't update beside the fact that i have signed an observer between the view controller and the app delegate. what am i missing ?
here is the code :
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions{
if (launchOptions != nil)
{
NSDictionary * dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (dictionary != nil)
{
[self handleRemoteNotification:dictionary];
}
}
}
-(void)handleRemoteNotification:(NSDictionary*)payload{
NSDictionary * notificationDict = [payload objectForKey:#"aps"]; //extract some information from payload
[[NSNotificationCenter defaultCenter] postNotificationName:#"refreshView" object:notificationDict];
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"viewController2"];
[self.window.rootViewController presentViewController:vc animated:YES completion:nil];
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;}
ViewController2.m:
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//Observer for remote notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshView:) name:#"refreshView" object: nil];
notifTextf.delegate = self;
}
//Update View when notification is received
-(void)refreshView:(NSNotification *) notification
{
// Do whatever you like to respond to text changes here.
NSLog(#"observer notification message text is: %#", [notification.object objectForKey:#"alert"]);
NSLog(#"observer notification message url is: %#", [notification.object objectForKey:#"acme"]);
[self.notifTextf setText: [notification.object objectForKey:#"alert"]];
//Download image from Url sended by notification and display it
NSString * imageurl = [notification.object objectForKey:#"acme"];
NSURL *url = [NSURL URLWithString:imageurl];
self.notifImg.image = [UIImage imageWithCIImage:[CIImage imageWithContentsOfURL:url]];
}
why the didReceiveRemoteNotification function doesnt update the view
when the app starts and updates the view only in two states (active &
background)?
If the app is not running when a push notification arrives, the system launches the app and provides the appropriate information in the launch options dictionary. The app does not call application:didReceiveRemoteNotification: 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.
Implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method instead whenever possible (iOS 7 and above).
Unlike the application:didReceiveRemoteNotification: method, which is called only when your app is running in the foreground, the system calls the application:didReceiveRemoteNotification:fetchCompletionHandler: method when your app is running in the foreground or background. In addition, if you enabled the remote notifications background mode (to support this mode, include the UIBackgroundModes key with the remote-notification value in your app’s Info.plist file), the system launches your app (or wakes it from the suspended state) and puts it in the background state when a push notification arrives. However, the system does not automatically launch your app if the user has force-quit it. In that situation, the user must relaunch your app or restart the device before the system attempts to launch your app automatically again.
Continue reading...
I am using local notifications on my app. I handle a notification perfectly when it arrives and you press on it even the app is running even not, and when you open the notification from the "Notifications" of the device. BUT when the app is not running and you open direct the app then the notification screen doesn't open and the badge number is not going to 0. What am I missing?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UILocalNotification *locationNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (locationNotification) {
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
NotificationsViewController *vc = nil;
if (localNotif) {
//base on notification create right view controller
vc = [[NotificationsViewController alloc] init];
NSLog(#"Recieved Notification %#",localNotif);
}
else
{
//create default view controller
vc = [[NotificationsViewController alloc] init];
}
// Add the view controller's view to the window and display.
_window.rootViewController = vc;
[_window makeKeyAndVisible];
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
return YES;
}
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
NotificationsViewController *vc = nil;
vc = [[NotificationsViewController alloc] init];
_window.rootViewController = vc;
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
//We take the rootViewController first which is expected to be a UINavigationController in your case
}
if you don't open the app from a notification, the app doesn't know there are notifications
Checking applicationIconBadgeNumber you can know if there is a notification, but you can't get the notification info (message or other data you send)
When your app is not running
if you "didFinishLaunchingWithOptions" will be called not didReceiveLocalNotification, you should track the parameter launchOptions to detect start mode:
If the app start direct from app icon you should not redirect user to notification screen.
If the app start from NSNotification you should redirect user to notification, unschedule notification, update app icon badges etc...
So in this case when you open the app from app icon locationNotification may nil.
I am working on an application that uses iOS's UILocalNotifications to alert the user that action needs to be completed on their part. I am able to create my notifications and have them fire properly. However, when returning to the app I am having an issue. After the first time a notification fired my 'application: didRecieveLocalNotification' runs every time, with the 1st notifications data. I can log out all the notifications in the queue, and even if the queue is empty it will still run the notification block. Has anyone else ran into this problem or know how to fix it. I have included my didRecieveLocalNotification code below.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if (notification) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
[(UITabBarController *)self.window.rootViewController setSelectedIndex:1];
UINavigationController *nav = [[(UITabBarController *)self.window.rootViewController viewControllers] objectAtIndex:1];
IMTUpdateRewardViewController *rvc = [storyboard instantiateViewControllerWithIdentifier:#"updateReward"];
[rvc loadPushNotification:notification];
[nav pushViewController:rvc animated:NO];
}
}
I have ran into a similar problem. Eventually the culprit was a bug on my part, which was related to generating reminders to events. And if upon return from background, there were events which have already began, then a local notification was generated, and fired immediately. In short, when you encounter something like this, put a small debug printout at your UILocalNotification generating methods