During requesting for Always permission on iOS13, the user can tap "Allow Once" which will call appropriate delegate with status kCLAuthorizationStatusAuthorizedWhenInUse but requesting for "Always" again calls delegate with kCLAuthorizationStatusAuthorizedAlways. Why? When other combinations work only once like you request always, you get it and even calling again will not call delegate with status.
Sample code to test:
#import CoreLocation;
#interface ViewController () <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
}
- (IBAction)doauthloc:(id)sender {
[self.locationManager requestAlwaysAuthorization];
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
switch(status) {
case kCLAuthorizationStatusNotDetermined:NSLog(#"AUTH STATUS:kCLAuthorizationStatusNotDetermined"); break;
case kCLAuthorizationStatusRestricted:NSLog(#"AUTH STATUS:kCLAuthorizationStatusRestricted"); break;
case kCLAuthorizationStatusDenied:NSLog(#"AUTH STATUS:kCLAuthorizationStatusDenied"); break;
case kCLAuthorizationStatusAuthorizedAlways:NSLog(#"AUTH STATUS:kCLAuthorizationStatusAuthorizedAlways"); break;
case kCLAuthorizationStatusAuthorizedWhenInUse:NSLog(#"AUTH STATUS:kCLAuthorizationStatusAuthorizedWhenInUse"); break;
};
}
#end
It's a little confusing, isn't it? When you ask for Always and the user taps Allow Once, you are told that you got WhenInUse. But that doesn't actually matter. You have provisional Always. So:
When you subsequently go into the background and start monitoring visits or regions or whatever your location monitoring usage is, this will be converted into Always authorization for purposes of usage. (Your logging should confirm this.)
And then, because you got only Once authorization, when you come back to the foreground you will be Not Determined again.
So the takeaway is, just laugh an evil laugh and move on. Your background location monitoring will work, and that's all that matters. Not only does it work, but as a bonus you get to present the user with the authorization alert again, which is the reason for all this change in iOS 13. Don't worry, be happy.
Related
I'm developing a location tracking app. The user can start a tracking, do whatever he wants (put the app in the background, lock the phone, etc), go back to the app and stop the tracking.
I'd like the tracking to restart if the app is killed (by the system or the user). To do so, I saw in the doc that I have to use the significant location change service, but this service does not send enough locations. Is it possible to restart the standard location service when the app is restarted thanks to the significant location change service? Or would the app be rejected?
Following methods would wake your application up even if is killed in the background:
Region events
Visit events
Significant location events
Your application will be launched with location key in the applicationDidFinishLaunchingWithOptions method. Here is an example on how to handle it gracefully (from http://nshipster.com/launch-options/):
// .h
#import CoreLocation;
#interface AppDelegate () <CLLocationManagerDelegate>
#property (readwrite, nonatomic, strong) CLLocationManager *locationManager;
#end
// .m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
if (![CLLocationManager locationServicesEnabled]) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Location Services Disabled", nil)
message:NSLocalizedString(#"You currently have all location services for this device disabled. If you proceed, you will be asked to confirm whether location services should be reenabled.", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil] show];
} else {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
}
if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {
[self.locationManager startUpdatingLocation];
}
}
Also, you are not limited to using Significant location monitoring or region monitoring etc. You can use different mechanisms for different use cases. E.g. You can use region monitoring to setup a fallback region around the user and everytime an exit event happens, your application will be woken up with location key. You can then restart location services.
I am in the process of testing location updates via the background APIs that relaunch an app. I have successfully tested visits and regions and am using this same app to also test significantLocationChanges (SLC). I run my app, press home twice then swipe it off to kill it. Then I drive around. I can observe the app background activity without touching the phone via a server where (see code below) the singleton method sendBackgroundTestRecord uploads simple status messages to the server.
When I test visits and regions I have no problems. However when I test SLC, nothing happens until I turn the phone's screen on (pressing the power or home button once -- not re-running the app, or even unlocking the phone). I can drive a far distance without anything happening. I suspect the app has received an SLC event sometime during that drive because as soon as I turn the phone on for a about 2 seconds, the code below executes and I can observe the messages on the server.
I believe this is not just a network connectivity delay because the sendBackgrounTestRecord method uses a time stamp. The records on the server show a time stamp of when I turned the screen on, not when the SLC event is suspected to have fired.
I never have this problem with visits or regions, only SLC. By the way, the code has gone through many changes trying to chase this down. The latest attempt (code below) was to put everything in the appDelegate.
Any ideas?
Thanks in advance for any ideas whatsoever!
EDIT: I added beginBackgroundTaskWithExpirationHandler, which was in my original tests too.
// AppDelegate.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
// AppDelegate.m
#import "AppDelegate.h"
#import "clsCommon.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Ask iOS for more time to process - before we do anything else
[clsCommon sharedInstance].backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{}];
[[clsCommon sharedInstance] sendBackgroundTestRecord:#"didFinishLaunchingWithOptions"];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.activityType = CLActivityTypeAutomotiveNavigation;
self.locationManager.allowsBackgroundLocationUpdates = YES;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startMonitoringSignificantLocationChanges];
return YES;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
[[clsCommon sharedInstance] sendBackgroundTestRecord:#"didUpdateLocations"];
CLLocation * currentLocation = [locations objectAtIndex:0];
if(currentLocation !=nil) {
[[clsCommon sharedInstance] sendBackgroundTestRecord:#"didUpdateLocations curLoc not nill"];
}
}
#end
Well, knowing how aggressively iOS resolves all tradeoffs in favor of battery life I would not be surprised very much. For example, behavior of "breadcrumbs" app (set 100m geofence at your current location, move that geofence to new location on exit event) is very different depending on whether iPhone sleeps all the time in your pocket, or you check lock screen occasionally.
However, running an app very similar to yours while traveling back home yesterday did generate location updates for me.
Distances between SLC readings were in 4 to 14 km range, the smallest time gap between two consequent readings - 14 minutes. The app is very similar to yours, it does not include any networking part, just logging events to a local file (NSLog via stderr redirection). Also, I do not configure properties, which are irrelevant for SLC in location manager, like desiredAccuracy, distanceFIlter, activityType. I suggest you start from barebone app and watch for the improvement, which breaks it.
One thing to check is if your app crashes when handling location updates. If it happens in background you will not notice this unless you run logging or check crash logs via Settings.app.
Put some distance in self.locationManager.distanceFilterand is better for battery change self.locationManager.pausesLocationUpdatesAutomatically=YES;
Start Location Manager in iOS 7 from background task
After submitting my App I need to get user location with CoreLocation (#import ) but it NEVER ask me the permission alert.
ViewController.h
#import "RootViewController.h"
#import < UIKit/UIKit.h >
#import < CoreLocation/CoreLocation.h >
#interface AroundMeViewController : RootViewController <CLLocationManagerDelegate>
#property(nonatomic, strong) CLLocationManager *locationManager;
...
#end
__________
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"LOG: %d", [CLLocationManager locationServicesEnabled]);
self.locationManager = [[CLLocationManager alloc] init];
// Set a delegate to receive location callbacks
self.locationManager.delegate = self;
// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
....
// Start the location manager
[self.locationManager startUpdatingLocation];
NSLog(#"LOG: %#", self.locationManager);
.....
}
I really tried in so many way to solve this issue (different devices, allow manually from privacy settings) but I don't know where is my mistake.
According to NSHipster, permission to use location services must be asked explicitly, and is given asynchronously, so we can't immediately start location updates like we used to. We need to implement the
locationManager:didChangeAuthorizationStatus
delegate method and start location updates there. This method is called any time the auth status changes based on user input.
It seems this delegate method is also called when locationManager is set up, as mine was triggering while splash screen was still showing (when I set locationManager delegate, I suspect), and then again when I called triggerLocationServices(). This could be what is in effect "silencing" your request.
I changed my methods to the following:
func triggerLocationServices() {
if CLLocationManager.locationServicesEnabled() {
if self.locationManager.respondsToSelector("requestAlwaysAuthorization") {
// request authorization to use location
} else {
// start updating location
}
}
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus){
if status == .AuthorizedAlways || status == .AuthorizedWhenInUse {
// start updating location
}
else {
triggerLocationServices()
}
}
and removed any other calls to triggerLocationServices(). This, along with the correct info.plist keys, resolved it for me.
Apologies for Swift code, hope you can translate into Obj-C.
I want to make a simple IOS 6.0 application that shows the lat/lon on screen each time the location is changed. The ViewController files are pretty trivial.
I alloc a CCLocationManager objec, set its variables and start it.
didUpdateLocations is called once or twice and then it stops being fired, even if the iPhone is moved. If I press Home button and reopen the app, the data on screen is updated once or twice before it stops again.
At simulation it works fine but not on a real 3GS iPhone.
If I uncomment the start/stop inside didUpdateLocations and continuously stop and start the service, it works, but the battery gets drained in extreme rates.
Also, this is part of a much bigger project and didUpdateLocations must be called each time the location is changed.
ViewController.h
#import <CoreLocation/CoreLocation.h>
#interface ViewController : UIViewController
#property (nonatomic , strong) CLLocationManager *locationManager;
#property (weak, nonatomic) IBOutlet UILabel *lbl;
#end
ViewConroller.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLHeadingFilterNone; // whenever we move
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
_locationManager.PausesLocationUpdatesAutomatically = NO;
[_locationManager startUpdatingLocation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = [locations lastObject];
_lbl.text=[NSString stringWithFormat:#"%lf %lf",newLocation.coordinate.latitude, newLocation.coordinate.longitude];
//[_locationManager stopUpdatingLocation];
//[_locationManager startUpdatingLocation];
}
#end
If there's any advice on what's wrong, I would welcome it, I have lost a week already without solving it.
BTW, I have tested various values for the _locationManager variables but without any chnage in the behaviour
Additional info:
application is authorized to use location services
application is in foreground
Few tips for similar issues as per my experience with location-based programming on iOS.
If it works on simulator, then it should work on actual device as
well.
Unlike simulator, the actual device will only give update when its receives new information from GPS.
There are many cases where you won't get enough/required updates on a device, like when you are in a building (or covered area)
Try testing the app in open space (GPS works much better in open space rather than room/house/large-building).
PS. I don't know how much you moved but just to let you know, moving few steps doesn't make sure you will get a new location update, try using it on some vehicle (bike/car/bus).
I had this problem too — the easiest fix is simply to duplicate the target. You can then delete the original target.
It seems the problem occurs with older Xcode projects, not newly created ones.
You can add this call to your code:
locationManager.stopUpdatingLocation()
This is a location app. When I first run the app on my iPhone, an alert window asks me if I would like to allow location services. After selecting OK or Don't allow I can then tap a button that is setup to display my current location. The button is also setup to print an NSLog to the console with the value of a BOOL function called "locationServicesEnabled" and returns yes/1 if they are and no/0 if they are not.
My problem is that when I select the Don't Allow option, and I press the button, the NSLog still prints the value of 1 to the console which is incorrect. It should be 0 or false. A text label that is connected to my button even says "Your location is null", but for some reason the NSLog always shows a BOOL value of 1.
Here is my ViewController.m code:
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController () <CLLocationManagerDelegate>
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.gpsLM = [[CLLocationManager alloc]init];
[self.gpsLM startUpdatingLocation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
}
-(IBAction)gpsButton{
CLLocation * currentLocation = self.gpsLM.location;
self.gpsLabel.text = [NSString stringWithFormat:#"Your Location is %#", currentLocation];
NSLog(#"Location services enabled: %hhd",[CLLocationManager locationServicesEnabled]);
}
#end
You need to check [CLLocationManager authorizationStatus], not locationServicesEnabled. Authorization status returns an app-specific value, one of the following:
typedef enum {
kCLAuthorizationStatusNotDetermined = 0,
kCLAuthorizationStatusRestricted,
kCLAuthorizationStatusDenied,
kCLAuthorizationStatusAuthorized
} CLAuthorizationStatus;
The names are hopefully self-explanatory.
I just figured out the answer to my own question. The bool function will only return a false value if the iPhone's location services option is disabled entirely. If you only have 1 single app disabled, but have the main feature enabled and have other app's using it, then it will still return a value of 1. It looks like the locationServicesEnabled method is device specific and not app specific.