iOS UIBackgroundMode remote-notification doesn't work on 4G - ios

I'm testing push notifications with content-available=1, and they don't seem to be delivered to the app in the background unless on Wi-Fi.
I have a simple log statement at the beginning of the push notification handler:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^) (UIBackgroundFetchResult))completionHandler {
NSLog(#"Notification received: %#", userInfo);
completionHandler(UIBackgroundFetchResultNewData);
}
Here is my test:
Run the app, then press the home button to put the app in the background.
Send a push notification with content-available=1
Watch console logs
On Wi-Fi, the console log shows the notification. If I go to Settings and turn off Wi-Fi, switching to 4G, notifications no longer appear in the log (although they do slide in at the top of the screen, so I know they are being delivered).
There are no crash logs, and the notification is logged if I manually tap on it. Furthermore, this problem does NOT occur if I am debugging the app in Xcode. (i.e., if I am debugging in Xcode, the app will receive the notification in the background on 4G). Has anyone else experienced this behavior? Or am I doing something wrong?
EDIT:
To be specific: according to my tests, if the following conditions are true, then the remote notification delegate method above will not be called:
App is running in the background
Phone is on LTE network, not connected to Wi-Fi
App is NOT running in the Xcode debugger
Notification with content-available=1 is received by the phone
However if condition 2 is removed (i.e., the phone is connected to Wi-Fi), then the handler will be called.

Try the following code:
// AppDelegate.h
#class ViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSString *DeviceToken;
NSMutableDictionary *App_Messages;
NSString *Longitude,*Latitude;
NSMutableDictionary * badge;
}
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) ViewController *viewcontrollervc;
#property (strong, nonatomic) UINavigationController *navcontroller;
#property (nonatomic,retain)NSMutableDictionary *badge;
#property (nonatomic,retain)NSString *DeviceToken;
 
 
// AppDelegate.m
#import "ViewController.h"
#implementation AppDelegate
#synthesize badge,DeviceToken;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen]bounds]];
self.viewcontrollervc = [[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
self.navcontroller = [[UINavigationController alloc]initWithRootViewController:self.viewcontrollervc];
self.window.rootViewController = self.navcontroller;
self.navcontroller.navigationBarHidden = YES;
//Notification
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
NSDictionary * remoteNotificationObj = [launchOptions objectForKey:#"UIApplicationLaunchOptionsRemoteNotificationKey"];
if (remoteNotificationObj)
{
[self performSelector:#selector(handleRemoteNotificationWithUserInfo:) withObject:remoteNotificationObj afterDelay:3.0];
}
[self.window makeKeyAndVisible];
return YES;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self handleRemoteNotificationWithUserInfo:userInfo];
}
-(void)handleRemoteNotificationWithUserInfo:(NSDictionary *)userInfo
{
NSLog(#"userInfo - %#",userInfo);
NSDictionary *alertData = [userInfo objectForKey:#"aps"];
NSDictionary *returnDatalert=[alertData objectForKey:#"alert"];
NSString *alertmsg=[returnDatalert objectForKey:#"body"];
NSLog(#"alertmsg %#",alertmsg);
self.badge = [NSMutableDictionary dictionaryWithDictionary:[alertData objectForKey:#"badge"]];
NSString *notificationtype=[badge objectForKey:#"fnct"];
NSLog(#"%#",notificationtype);
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(#"didRegisterForRemoteNotificationsWithDeviceToken: %#", deviceToken);
NSString *dt = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
dt = [dt stringByReplacingOccurrencesOfString:#" " withString:#""];
self.DeviceToken=dt;
NSLog(#"~~~~devToken(dv)=%#",deviceToken);
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
NSLog(#"Failed to get token, error: %#", error);
}

Based on feedback from a commenter here and repeated testing on multiple devices, this appears to be a bug (or intended behavior) on iOS.

For me, it worked on Wi-Fi and on 4G (LTE forced off in cellular settings), but did not work on LTE.
Update: After extensive debugging, I've found this issue related to two things for me when on LTE. One is power. I discovered if the iPhone was plugged into the wall, the app was woken up as expected. If it wasn't plugged in, the app was not woken up in response to content-available = 1. Second, was device settings. Even though every related setting was set correctly, doing a 'Reset all Settings' fixed the issue for me.
Assuming this is not an Apple bug, my guess is as iOS develops a power profile for a given app identifier, it opts, under certain circumstances (network status, battery status, etc) to not wake up apps that use excessive background cycles. For example, using beginBackgroundTaskWithExpirationHandler incorrectly, causing an app to stay active in the background and forcing iOS to expire it. Even fixing excessive background usage might not correct the issue, as iOS has already determined your app is a background hog. This would explain the 'Rest all Settings' clearing up the issue for me.
Unfortunately, all of this is just a guess based on 2-3 days of debugging this issue and we will probably never know for sure as there are so many variables in play with push notifications, not to mention vague and varying documentation.

Related

iOS background fetch not working

I want to pull some data from server in every 30 min interval and set local notification to remind the user and i implemented below code to perform this operation.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSString *value = [[NSUserDefaults standardUserDefaults]objectForKey:#"bgFetch"];
if ([Utilities isInternetConnectionAvailable]) {
if ([self.window.rootViewController isKindOfClass:[HomeViewController class]]){
HomeViewController *homeController = (HomeViewController*)self.window.rootViewController;
[homeController fetchDatawithCompletionHandler:^(UserData *userData, NSString *errorResponse) {
if (userData) {
if (![homeController isNotificationAvailvableForTheData:userData]) {
[homeController scheduleLocalNotificationForUserData:userData];
completionHandler(UIBackgroundFetchResultNewData);
}
}else{
completionHandler(UIBackgroundFetchResultFailed);
}
}];
}else{
completionHandler(UIBackgroundFetchResultNoData);
}
}else{
completionHandler(UIBackgroundFetchResultFailed);
}
}
I also enabled background fetch in capability and added the key "Required background modes" in plist. When i checked after few hours, no local notification is set. Where am i doing wrong ?
I was facing the same issue. Whenever I simulated a background fetch via XCode it worked, whether with the emulator or a real device. If I unplugged the phone and just used it as I usually do. I never got a notification, respectively my code doesn't get executed. The solution is very simple and I think the difference to "simulate background fetch" is that your app is in a different stage then it is in your daily routine. To make it work, simply dispatch your code on a background thread. I just wrapped my code inside:
DispatchQueue.global(qos: .background).async
and it worked.

How to manage state in ios objective c

I’m new in iOS development. My question is, I’ve two view controllers.
viewController - A viewController - B
Now, if i killed the app from the viewController - A and than relaunch the app. than app must be open the viewController - A. and if i killed the app from the viewController - B and than relaunch the app. than app must be open the viewController - B.
Can anyone help me, I’ve done the RND but can not find the proper solution.
Thanks
Create a sharedDelegate in AppDelegate.m file
+(AppDelegate *)sharedDelegate {
return (AppDelegate *) [UIApplication sharedApplication].delegate;
}
in AppDelegate.h
+ (AppDelegate *)sharedDelegate;
#property (nonatomic, strong) NSString *currentViewContoller;
when push to any contoller then set AppDelegate's currentViewContoller to new VC
YourViewController *vc=[[YourViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
[AppDelegate sharedDelegate].currentViewContoller = NSStringFromClass([YourViewController class]);
now when app is terminated
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[[NSUserDefaults standardUserDefaults]setObject:[AppDelegate sharedDelegate].currentViewContoller forKey:#"currentVC"];
}
now when app launched first time check previous controller when app terminated
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *string=[[NSUserDefaults standardUserDefaults] valueForKey:#"currentVC"];
and push this class
UIViewController *object = [[NSClassFromString(string) alloc] init...];
}
applicationWillTerminate you can use but it will only get called if user quit app from forground If your app is in background and then user quit app then applicationWillTerminate will not get called.
So, you have to take care of applicationDidEnterBackground.
So, when app enter in background (i.e call applicationDidEnterBackground ) or call applicationWillTerminate save state(your current VC) in your user defaults.
Now in your didFinishLaunchingWithOptions set that view controller as rootviewcontroller or whatever way that you want to manage it.
reference : Apple documentation for applicationWillTerminate
PS : You should not manage app like this. It is horrible way!! If possible then run your app as normal flow!
If you're using Storyboards you can use the Restoration Identifier to communicate the App which controller to launch as first
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html

Cordova Application Monitoring evothings Estimote/iBeacon while the app is in killed state IOS

I've installed https://github.com/katzer/cordova-plugin-background-mode and https://github.com/katzer/cordova-plugin-local-notifications in my cordova application.
I am trying to monitor for beacons in the background when my app is closed and send a notification once a beacon has been detected and is in region.
Using the two plugins the app successfully works when the user has exited the app screen but the app is still on although does not work when the user has completely killed the process.
Can this be done solely using Javascript or would I have to modify the code in AppDelegate.m?
I've tried this using the following code:
#import "AppDelegate.h"
#import "MainViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface AppDelegate ()
#property (nonatomic, strong) CLLocationManager *locationManager;
#property(nonatomic, assign) BOOL notifyEntryStateOnDisplay;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
self.viewController = [[MainViewController alloc] init];
self.locationManager = [[CLLocationManager alloc] init];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
UILocalNotification *notification = [[UILocalNotification alloc] init];
NSLog(#"BLUETOOTH");
if(state == CLRegionStateInside)
{
notification.alertBody = [NSString stringWithFormat:#"You are inside region %#", region.identifier];
}
else if(state == CLRegionStateOutside)
{
notification.alertBody = [NSString stringWithFormat:#"You are outside region %#", region.identifier];
}
else
{
return;
}
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
#end
Although the application does not start. I've also changed settings in my xCode [General -> Capabilities] so that Background mode and push notifications is ON.
The app does what it needs to even if in the background and not killed although stops once the app is killed. I am trying to send a notification when the app is offline that a beacon is in range so that the user can turn the app on.
A few points:
You must set self.locationManager.delegate=self, and make your AppDelegate implement the CLLocationManagerDelegate protocol, specifically the didEnterRegion method that should be called when a beacon is discovered.
Beacon monitoring must be configured and initialized in the AppDelegate. You cannot rely the plugin to do this, because it won't necessarily leave beacon monitoring active when the app switches to the foreground.
When testing, set up a log message or breakpoint in the didEnterRegion method in your AppDelegate so you know if it gets called.

how to recieve local notification in ios when app is closed

I am using local notification for alarm system but I am facing some problem while handling local notification, When i clicked on alarm notification (When app is closed) its launches app but the problem is it should go to didFinishLaunchingWithOptions function but it's not going inside any of the function in appDelegate (I used breakpoints to check).
I'm using story board with navigation controller, I want to open a specific view controller on notification click when app is closed.
but when I'm launching that app normally it's going inside didFinishLaunchingWithOptions function.
Please suggest.
Any help would be appreciated.
main.m
#import "DEMOAppDelegate.h"
int main(int argc, char * argv[])
{
#autoreleasepool
{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([DEMOAppDelegate class]));
}
}
DEMOAppDelegate.m
#import "DEMOAppDelegate.h"
#implementation DEMOAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UILocalNotification *localNotif = [launchOptions objectForKey: UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif)
{
NSLog(#"Recieved Notification %#",localNotif);
}
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
}
- (void)applicationWillTerminate:(UIApplication *)application
{
}
-(void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:notification.alertAction message:notification.alertBody delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}
#end
It's not possible, when your app is not running notification it will not react on notification directly.
didFinishLaunchingWithOptions will contain information about notification only when user opened your app through this notifcation. If he cancels it and opens your app through dashboard icon you are not gonna see this in this method.
Unfortunatelly if you need to react on all notification that happened from last time the user opened the app, only way is to build your own tracking logic and get all events that were in the past based on time.
Also there is no way to even get a list of notification you scheduled for your app, so usually it is a good idea to build in time-based event logic and use notification on top of it, but all the logic happens on your own time-based code. This way even if user disable notifications your critical logic will work.
When the application is running in background you will get notification through application:didReceiveRemoteNotification. You can gather those notifications inside your app, and process them on applicationDidBecomeActive if you want to do anything specific inside your application for them after user comes back to the app from background.

iOS: Push notifications, UIApplicationStateInactive and fast app switching

According to the Apple Docs, in order to find out if a user tapped on your push notification you are supposed to check the applicationState in application:didReceiveRemoteNotification:
If the value is UIApplicationStateInactive, the user tapped the action button; if the value is UIApplicationStateActive, the application was frontmost when it received the notification.
I have found that this is not always true. For example:
Double-tap the home button to reveal the system tray and enter "fast app switching mode", your application slides up to reveal other running applications and your app is put into the inactive state (even though it's still mostyle visible). If you receive a push notification in this mode your app delegate will still receive the application:didReceiveRemoteNotification: and at this point your applicationState is UIApplicationStateActive. According to the docs you should treat it like the user tapped the alert... but in this case they didn't. Not only that, the user didn't even see the push notification (possibly because the top of your application is cut off in this mode).
Does anyone know of a way to detect being in 'fast app switching mode' or handle the notification correctly?
I was able to fix it myself with some nifty checks...
Essentially the key to this whole thing is
-(void)applicationDidEnterBackground:(UIApplication *)application;
This method isn't called when you enter fast app switching (or control center) so you need to setup a check based on it.
#property BOOL isInBackground;
#property (nonatomic, retain) NSMutableArray *queuedNotifications;
And when you receive a notification...
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
UIApplicationState appState = application.applicationState;
// Check if we're in this special state. If so, queue the message up
if (appState == UIApplicationStateInactive && !self.isInBackground) {
// This is a special case in which we're in fast app switching or control center
if (!self.queuedNotifications) {
self.queuedNotifications = [NSMutableArray array];
}
// Queue this to show when we come back
[self.queuedNotifications addObject:userInfo];
}
}
And then when we come back...
- (void)applicationDidBecomeActive:(UIApplication *)application {
application.applicationIconBadgeNumber = 0;
if (!self.isInBackground) {
// Show your notifications here
// Then make sure to reset your array of queued notifications
self.queuedNotifications = [NSMutableArray array];
}
}
One more thing you may want to do is check for this special case of going to fast app switching and the user going somewhere else. I do this just before setting the isInBackground BOOL. I choose to send them as local notifications
-(void)applicationDidEnterBackground:(UIApplication *)application {
for (NSDictionary *eachNotification in self.queuedNotifications) {
UILocalNotification *notification = [self convertUserInfoToLocalNotification:eachNotification];
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
self.queuedNotifications = [NSMutableArray array];
self.isInBackground = YES;
}

Resources