Good evening everyone !
I have a simple question : How to enable [CLLocationManager startUpdateLocation] when I receive a correct push notification using didReceiveRemoteNotification:fetch on iOS 7 ?
Right now, I have :
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if ([userInfo objectForKey:#"aps"] && [[userInfo objectForKey:#"aps"] objectForKey:#"content-available"])
{
if ([userInfo objectForKey:#"update-location"])
{
[self performSelectorInBackground:#selector(handleLocationNotificationPush:) withObject:completionHandler];
}
if ([userInfo objectForKey:#"update-sensors"])
{
}
}
}
-(void)handleLocationNotificationPush:(void (^)(UIBackgroundFetchResult))completionHandler
{
[CLController.locMgr startUpdatingLocation];
++nbPushReceive;
[self.pushLockForLocation lock];
if ([self.pushLockForLocation waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:25]] == TRUE && self.lastKnownLocation != nil)
{
[self.pushLockForLocation unlock];
// Send my new location to server using HTTP request
[self sendLocationForPushUpdates:self.lastKnownLocation fetchCompletionHandler:completionHandler];
if (![UIApplication sharedApplication].applicationState == UIApplicationStateActive)
// it stops location updates
[self stopAllLocationUpdates];
return;
}
// In case we didn't receive any new position during 25 secondes
[self.pushLockForLocation unlock];
if (![UIApplication sharedApplication].applicationState == UIApplicationStateActive)
{
[self stopAllLocationUpdates];
}
completionHandler(UIBackgroundFetchResultNoData);
}
- (void)locationUpdate:(CLLocation *)location
{
NSLog(#" *** LocationContrller - LocationUpdate location");
self.lastKnownLocation = location;
if (location.horizontalAccuracy < 500)
{
[self.pushLockForLocation signal];
}
}
Of course, my CLController delegate is the same class (location updates work when application is in foreground). In my plist.file, I have the "Remote notifications" checked.
I am missing something ?
Thanks for your help ! :D
This could be related to the fact that in iOS7 enabling Location Services whilst in the background does not give you unlimited background processing time as it did in previous iOS version. Check out the WWDC 2013 What’s New in Core Location video at around 5 minutes 30. Therefore your app gets suspended again around 30 seconds after receiving the push notification.
I have a similar problem which as yet I've not found the solution to. However if you'd like to get to the same place I am try the following -
Firstly put an NSLog in didReceiveRemoteNotification, run your app on the device, put it into the background and send it a push notification. If you see your NSLog you'll know that the content-available flag is set correctly in your push.
Next add an NSLog in -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations. If this gets hint then you know that you are indeed enabling location services.
If you get this far you've probably got the same problem I have. 30 seconds isn't always long enough to get a location to the accuracy I want it.
BTW if you're using an iPhone 5 or newer you can use deferred location and keep location services running constantly. Sadly I need it to work on an iPhone 4s.
UPDATE -
I've now found that this is specifically related to background push notifications and didReceiveRemoteNotification. Enabling Location Manager from a standard background task will work as it did before iOS7. Therefore you can still use background tasks together with Location Manager to get your position every x minutes, you just can't start the whole thing off using a background push.
Answering to myself, and using severals stackoverflow's posts, it's apparently impossible to re-active the location update when you're in background. To keep being updates of your location updates, you have to let it run even if you're going in background !!
:)
Related
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.
My Apple Watch app requires some data and requests it from the corresponding iPhone app. To fulfill the request the iPhone app requires the users location.
After receiving and testing with a real Apple Watch I found out that my iPhone app does not receive location updates when running in background. If the iPhone app is active in foreground it works without issues. With the simulator it worked in both cases.
In both cases (active and background) the WatchKit extension calls and starts successfully the iPhone app and goes all the way until startUpdatingLocation is called in the iPhone app. But in case the app is running in background didUpdateLocations is never called.
I tried with requestAlwaysAuthorization as well as requestWhenInUseAuthorization. No difference.
I also activated then the "location updates" background mode within capabilities. But again no difference.
Has someone else faced the same problem and found a way to receive the location also in background?
Here some code. First the check if authorization is required.
// iOS 8 check to avoid crash on older iOS
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)])
{
[self requestLocationAlwaysAuthorization];
}
else
{
[self runLocationUpdate];
}
Here the check for the proper Location Manager rights.
- (void)requestLocationAlwaysAuthorization
{
CLAuthorizationStatus currentAuthStatus = [CLLocationManager authorizationStatus];
if (currentAuthStatus == kCLAuthorizationStatusDenied)
{
//request user to change setting
}
else if (currentAuthStatus == kCLAuthorizationStatusRestricted)
{
//request user to change setting
}
else if (currentAuthStatus == kCLAuthorizationStatusNotDetermined)
{
[self.locationManager requestAlwaysAuthorization];
[self runLocationUpdate];
}
else if (currentAuthStatus == kCLAuthorizationStatusAuthorizedWhenInUse)
{
//maybe when in use is also enough?
[self runLocationUpdate];
}
else if (currentAuthStatus == kCLAuthorizationStatusAuthorizedAlways)
{
//all ok
[self runLocationUpdate];
}
}
Here the call of startUpdatingLocation. The didUpdateLocations delegate will only be called when iPhone app is active.
-(void)runLocationUpdate
{
[self.locationManager startUpdatingLocation];
}
Three things to check and be aware of:
Location Permissions like [self.locationManager requestAlwaysAuthorization]; are only acknowledged once by the OS. If you have already requested permission, doesn't matter the level, the OS will NOT display a request to the user. The OS will just pass over the request and leave the permission level as is. The only time you can be assured that the OS will display the request to the user is if the [CLLocationManager authorizationStatus] returns kCLAuthorizationStatusNotDetermined. In every other case, you must manually request permission by displaying an Alert or other form of UI display. Also note that the OS retains whether or not it already displayed the request, even if you delete your app and reinstall it. So to test, you need to reset your Simulator's Content or your iPhone's Location Privacy.
Make sure you have added the plist keys for NSLocationAlwaysUsageDescription AND NSLocationWhenInUseUsageDescription If you don't add this to your plist, the OS will ignore any Location Permission Requests.
If you want to use requestAlwaysAuthorization to get location data from the phone (not the watch app extension) while the phone app is in the background, will also require you register for Background Modes Location updates under Project>Target>Capabilities.
UPDATE
Use a background task to give your app time to respond when in the background. Something like this:
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply{
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask __block = [app beginBackgroundTaskWithName:#"watchAppRequest" expirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//make your calls here to your tasks, when finished, send the reply then terminate the background task
//send reply back to watch
reply(replyInfo);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[app endBackgroundTask:bgTask];
bgTask=UIBackgroundTaskInvalid;
});
}
I am creating an application where I am retrieving data from the server like below:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self retrievedatafromserver];
dispatch_async(dispatch_get_main_queue(), ^{
//UIUpdation, fetch the image/data from DB and update into your UI
});
});
How do I retrieve data from the server even if application goes to background?
Thanks & Regards
sumana
If Your scope of project is in only iOS 7 then you can use A new background mode which comes in the iOS 7 and onwards. You can fetch the data in background mode without any extra efforts of coding.
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
Now that your app already knows to initiate background fetch, let’s tell it what to do. The method -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler will assist in doing so. This method is called every time that a background fetch is performed, and should be included in the AppDelegate.m file. The complete version is provided below:
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
UINavigationController *navigationController = (UINavigationController*)self.window.rootViewController;
id topViewController = navigationController.topViewController;
if ([topViewController isKindOfClass:[ViewController class]]) {
[(ViewController*)topViewController insertNewObjectForFetchWithCompletionHandler:completionHandler];
} else {
NSLog(#"Not the right class %#.", [topViewController class]);
completionHandler(UIBackgroundFetchResultFailed);
}
}
Now in your controller. Do like that
- (void)insertNewObjectForFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"Update the tableview.");
self.numberOfnewPosts = [self getRandomNumberBetween:0 to:4];
NSLog(#"%d new fetched objects",self.numberOfnewPosts);
for(int i = 0; i < self.numberOfnewPosts; i++){
int addPost = [self getRandomNumberBetween:0 to:(int)([self.possibleTableData count]-1)];
[self insertObject:[self.possibleTableData objectAtIndex:addPost]];
}
/*
At the end of the fetch, invoke the completion handler.
*/
completionHandler(UIBackgroundFetchResultNewData);
}
Note :- If you have to give supportability on iOS 6 and below then avoid this approach. Because it's not available.
When your app enters background mode. you can access code for couple of seconds. Suppose the background queue is still performing and you entered background. then you might need to recall the method when app entered foreground. (take a bool variable and check whether the process is completed or not, if process is completed no issues. if not call the method again.).
If you want to make app run in background mode also then you need to request for background run mode in plist. See this link for reference only for these features we can active background run mode and you can active any of them according to you usage http://blogs.innovationm.com/support-for-applications-running-in-background-ios/
I am trying to fetch user locations in foreground & background. I have to call api after I got a locaion update. To work in background I want to use Deferred method. I followed the same process as described in Apple WWDC. I am checking app on iPhone 5 (iOS 7). It is working fine when I am in foreground but did not give me update after I send the app into background. Below is the code which I am using to get location in background.
#import "AppDelegate.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.locationArray = [[NSMutableArray alloc] init];
self.locationErrorArray = [[NSMutableArray alloc] init];
self.manager_loc = [[CLLocationManager alloc] init];
self.manager_loc.activityType = CLActivityTypeFitness;
self.manager_loc.delegate = self;
[self.manager_loc setDesiredAccuracy:kCLLocationAccuracyBest];
[self.manager_loc startUpdatingLocation];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#pragma mark - Location Manager Delgate
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"update failed");
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self.locationArray addObject:locations];
NSLog(#"udate locations %f %f", manager.location.coordinate.latitude, manager.location.coordinate.longitude);
if (!self.deferredStatus)
{
self.deferredStatus = YES;
[self.manager_loc allowDeferredLocationUpdatesUntilTraveled:100 timeout:30];
}
[self.manager_loc stopUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error
{if (manager.location != nil)
{ [self.locationArray addObject:manager.location];
}
if (error != nil)
{
[self.locationErrorArray addObject:error.description];
}
self.deferredStatus = NO;
NSLog(#"deffered success %f %f", manager.location.coordinate.latitude, manager.location.coordinate.longitude);
}
#end
If I do not stop the location update in didUpdateToLocations Delegate then the location arrow (on status bar) do not go. In that case it gives me locations contionusly. I want location update after a particular time or particualar distance travelled, so that I can hit server with the user locations. Please help me on this.
Use LocationManger distanceFilter Property for update location at particualar distance travelled.
self.manager_loc.distanceFilter= 100;// In meters
If you want location updated in Backggriound then register your for background updates. Youca can do it in plist.
Set location manager to :
if ([self.locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)]) {
self.locationManager.pausesLocationUpdatesAutomatically = NO;
}
Then If you want to location updated after some time or distance then use:
- (void)allowDeferredLocationUpdatesUntilTraveled:(CLLocationDistance)distance
timeout:(NSTimeInterval)timeout // No guaranty it will work exactly or not
If you want location updated based on distance the you can use
Desired accuracty and distanceFilter property.
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;// Use other accurarcy as your need
self.locationManager.distanceFilter = 3000; //100, 200, etc
If you set activity type to CLActivityTypeFitness all above setting will overrided, And location manager updated according to this activity, which is as per my knowledge will eat Battery.
While using CLLocation Manager one thing you should accept it will not give all updartes 100% accurate.
See my answer for this post: StartUpdateLocations in Background, didUpdatingToLocation only called 10-20 times
If you need location updates in the background under iOS 7, you must call startUpdatingLocation while your App is in the foreground. You can no longer do this while your App is in the background, so you can no longer register for location updates only when you need them and while you need them. You are forced to register for them for the whole time your App is running (in the foreground and the background) and so you’re forced to waste a lot of energy.
You can reduce the battery usage a little bit by setting the accuracy to kCLLocationAccuracyThreeKilometers when you do not need the location updates and set them to kCLLocationAccuracyBest only when you need the updates. But this will nevertheless drain the battery faster than expected.
Please write a bug report to Apple and ask for the „old" behavior of iOS 4,5 and 6, where you could call „startUpdatingLocation“ in the background as well to get location updates in the background. If Apple gets enough requests to change this behavior back to the way it was implemented in iOS 5/6, the more likely it is that Apple will change this back.
The currents situation is really bad. Bad for developers, which are forced to waste energy, or to abandon their Apps, bad for the user, whose device needs to be plugged to a power source much earlier, or who can no longer use certain Apps.
So after much trial and error and reading Apples documents and SO threads I thought I had my didEnterRegion working properly.
This is what I finished up with...
- (void)locationManager:(LocationManager *)locationManager didEnterRegion:(CLRegion *)region{
NSLog(#"Location manager did enter region called");
[self.locationManager stopUpdatingLocation];
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
UILocalNotification *localNotification = [[UILocalNotification alloc]init];
localNotification.alertBody = #"You are about to arrive";
localNotification.alertAction = #"Open";
localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication]applicationIconBadgeNumber]+1;
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"App Running in foreground notification fired");
[self setupAlarmTriggeredView];
//vibrate device
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
});
}
}
As you can see it does a simple check to see if the app is active, if not it sets up a UILocalNotification which it presents immediately, if it is it just vibrates and changes the view.
When testing on the simulator using a GPX file to move the location over the boundary, both scenarios work perfectly. However when testing the app out and about, when the app is in the background the notification doesn't seem to fire until you wake the device. ie. you can cross the boundary and nothing happens, then when you unlock the device, boom, it vibrates and the view is changed accordingly. (if the app is not foremost when you unlock this doesn't happen until you re-launch the app).
Should I be doing this setup in appDidFinishLaunchingWithOptions? In my testing both that method and didRecieveLocalNotification: in the app delegate don't get called until 'after' the notification has been fired AND the user has re-launched the app by actioning the notification (at which point you can check the for the launch key in the options array), this doesn't seem to be of any use for the initial firing of the notification as part of didEnterRegion. At the moment I have no code in either of these methods.
As far as I'm aware I don't need to be doing any background location updates for didEnterRegion, this is handled automatically by iOS.