Why is CLLocationManager giving me the wrong authorization status? - ios

I'm building a running-based app in Swift that requests the user's location. For the sake of this question, let's just assume the app runs only on iOS 13+. Shortly after launching the app, we prompt the user for location access via the CLLocationManager method: requestWhenInUseAuthorization().
However, this app actually needs access to the user's location all the time, so only being able to request "When In Use" location access (per iOS 13) restricts the UX (other apps like Zenly and Snapchat do this as well since getting constant location updates improves the experience for their users and/or their friends).
After prompting for the location permission, we then grab the latest location authorization status. If that value is not equal to authorizedAlways (and it won't be unless the user changes that value in his/her Settings app), we present a new screen basically telling the user, "Since this is a running app, we really need your location all the time, so please go to settings and change the permission to 'Always' since we can't do it for you."
The issue I'm running into here is that when listening for updates on the CLLocationManagerDelegate method: locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus), sometimes that method provides the incorrect location authorization status.
Specifically, after a user selects "When In Use" and runs the app a few times, the value returned from that delegate method frequently reads as "Always," even though the Settings app on the phone still reflects the original, unchanged value ("When In Use").
Has anyone run into this before and, if so, do you have any ideas on what might cause it or how to fix it? Happy to provide more info on my setup. Thanks.

Related

Bug in iOS 13 Location Authorization Flow?

I am experiencing unexpected behavior in the iOS location authorization process. Here are my steps including a screen-grab
I request "Always" authorization on iOS 13.
I only grant the app the "Allow Once" authorization, which grants the user a temporary "When In Use" authorization.
I request "Always" authorization again.
This time, no authorization prompt is shown and the didChangeAuthorization delegate method gives .authorizedAlways
However, when I look at the authorization status in the Settings app, it says "Ask Next Time", which corresponds to an authorization status of .notDetermined.
My questions:
Why is there no second prompt when I request "Always" authorization a second time?
Why does the delegate method give the .authorizedAlways status without me actually giving that authorization?
Why does the Settings app indicate that the authorization status is .notDetermined when the delegate method said it was .authorizedAlways.
I feel like this is a bug in iOS, but I might also just be misunderstanding the way the authorization flows should work. What do you think?
I haven't been able to test this on a real device with iOS13, so I don't know if it's maybe just a problem in the simulator?
The user has already been asked for a location permission in this session; they selected "allow once". If iOS prompted them a second time it would be annoying and confusing.
Your app is upgraded to "provisional always" state after the second request; it is allowed to think that it has always so that any permission checking logic you may have built won't keep asking the user to go to settings or whatever. Since the app doesn't really have always there is no impact to user privacy, so there is no harm in reporting "always"
The settings app reflects the user's actual choice which is "ask me again next time".
The WWDC video that #Don suggested in the comments explains about provisional always permission and its purpose in satisfying app permissions logic.
Why is there no second prompt when I request "Always" authorization a second time?
In iOS 13.0, just requesting “always” permission a second time does not qualify as the “event” to trigger the prompt to the user. You actually have to to perform the action that requires “always”.
For example, I request always, and got the “when in use” prompt. I turned on significant change service, but did not receive prompt for always. Only when I hopped in my car and started to drive away did the significant change actually trigger the “always” prompt.
In WWDC 2020 video What's new in location, they describe a change introduced in iOS 13.4. You can ask for “when in use” and assuming the user granted it, you can ask for “always” and get the second alert (this time asking if the user would like to upgrade to “always” or not). You just need both “Privacy - Location When In Use Usage Description” and the “Privacy - Location Always and When In Use Usage Description” usage strings.

"Always" location permission... only used occasionally but always showing arrow?

I've found that if you have the "Always" location permission on iOS for an app... even without initializing the CLLocationManager, it always shows the location arrow in the status bar unless the app is "force quit" using the task switcher.
I'm sure that this is on purpose for full disclosure, but it's not accurate considering the location isn't being used. Is there a mistake here that's causing the arrow to display (again, CLLocationManager is NOT initialized) or is this behavior normal these days in iOS 10 since the app could suddenly request location at any point?
The permission states that the app can request the location in the background.
iOS gives this as full-disclosure to the user. They can then visit privacy settings for Location Services and see a colour-coded indicator for a more accurate depiction of when location updates were made.

requestAlwaysAuthorization after requestWhenInUseAuthorization is accepted

My app can have two level of LocationHandler status. First, I launch requestWhenInUseAuthorization and then, if the user activates some specific features, I launch requestAlwaysAuthorization.
I need to be notified if user refuses the requestAlwaysAuthorization to let him know the feature won't work as expected. The problem is that in this case didChangeAuthorizationStatus is not called because the authorization status stays the same (it was AuthorizedWhenInUse and it's still AuthorizedWhenInUse).
Do you have any idea how I could be notified if user refuses AuthorizedAlways after accepting AuthorizedWhenInUse ?
since iOS 10 or so it is no longer possible to call requestAlwaysAuthorization() after you have called requestWhenInUseAuthorization() even if the user accepted when-in-use.
in prior versions (at least iOS8) you could "step up" the authorization and ask for always-authorization after the user accepted when-in-use. This is no longer possible.
best thing to do is check CLLocationManager.authorizationStatus()
once in a while and show a dialog pointing the user to the right settings page with UIApplicationOpenSettingsURLString

iOS - Turn on Location Services with Settings and Cancel buttons -How do I Capture Cancel button click

When the user launches the app for the first time and attempts to login, they are prompted with the iOS dialog - "Turn On Location Services".
I need to capture when the user clicks "cancel". Is there a Notification sent? If so, what is its name? I've been unable to locate it.
The CLAUthorizationStatus is kCLAuthorizationDenied when Location Services are Disabled OR the user clicked "Don't allow". When the user clicks "Cancel", it does not fire the authorizationChange event. When user clicks "Cancel", the app just hangs.
Short answer: You can't catch that notification. You can infer about the user choice and act consequently by using CLLocationManager methods (the longer answer below).
Longer answer:
Firstly, welcome on Stack Overflow. Before kindly posing your question, and trying to be collaborative with people that are here to help, it's a good idea to search if somebody else previously posed the same question.
A brief search gave (just to mention some of them):
How to handle “Cancel” button on Alert pop up for Location services
How to get location services to reprompt the user for location permission if they accidentally refused it?
locationManager:didFailWithError: not called if user Location Services are off
How to prompt user to turn on Location Services…again
How can I prompt the user to turn on location services after user has denied their use
How to ask permission from user for second time to allow to access the current location?
Now, let's try to summarize them all, starting from iOS docs:
If your app relies on location services to function properly, you should include the UIRequiredDeviceCapabilities key in the app’s Info.plist file. You use this key to specify the location services that must be present in order for your app to run. The App Store uses the information in this key from preventing users from downloading apps to devices that do not contain the listed features.
Important: If your app uses location services but is able to operate successfully without them, do not include the corresponding strings in the UIRequiredDeviceCapabilities key.
So, if your app really needs to access the user's position you should add location-services and eventually gps to UIRequiredDeviceCapabilities.
Then, somewhere in your code - when needed, you have to check if the location services are enabled.
[CLLocationManager locationServicesEnabled]
they may be disallowed for three reasons:
The user can disable location services in the Settings app.
The user can deny location services for a specific app.
The device might be in Airplane mode and unable to power up the necessary hardware.
You are interested in the second case: the user refused to allow your app to use the location services.
Again, from the docs:
Important: In addition to hardware not being available, the user has the option of denying an application’s access to location service data. During its initial uses by an application, the Core Location framework prompts the user to confirm that using the location service is acceptable. If the user denies the request, the CLLocationManager object reports an appropriate error to its delegate during future requests. You can also check the application’s explicit authorization status using the authorizationStatus method.
[CLLocationManager authorizationStatus]
That may return:
kCLAuthorizationStatusNotDetermined if the user has not yet made a choice regarding whether this application can use location services.
kCLAuthorizationStatusRestricted this application is not authorized to use location services. The user cannot change this application’s status, possibly due to active restrictions such as parental controls being in place.
kCLAuthorizationStatusDenied The user explicitly denied the use of location services for this application or location services are currently disabled in Settings.
kCLAuthorizationStatusAuthorized This application is authorized to use location services.
If[CLLocationManager locationServicesEnabled] returns NO and you attempt to start location services anyway (i.e. calling [locationManager startUpdatingLocation]), the system prompts the user to confirm whether location services should be re-enabled. Given that location services are very likely to be disabled on purpose, the user might not welcome this prompt.
I suppose you know, and did all the previous steps (I'm only sure you checked the authorizationStatus). You refused to show us the significant code of your app so I can only suppose the overall logic behind. Now you said your app hangs. This should be because you didn't catch the error properly? Catching the error is the way to re-prompt the user, if you wish.
After calling [locationManager startUpdatingLocation], if not authorized, your delegate should define a locationManager:didFailWithError: in order to catch the kCLErrorDenied.
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
You may show, at this point, a UIAlert to insist asking the user to give you access to its position, or trigger a change in the UI or whatever you like.
Final notes
I hope you understand why I was asking for the code: the reason was to offer you an alternative solution instead of reply "You can't catch the 'Cancel' notification".
If this answer does not satisfy your question please elaborate why you need to catch the pushing of the "Cancel"/"Do not allow" button, so we can provide alternatives.
Clearly my advice is to not annoy people to death by continuously ask them for enabling location services if they don't want.
Post scriptum: Maybe that the answer looks pedantic and obvious in certain parts if not all to you, but we are here to provide answers also for future readers.

How to get IOS to ask users whether they want to allow application to use their location everytime?

If we try to access the user's location, iOS will tell the user that our application wants to use their location.
If I do this
[locationManager startUpdatingLocation];
An alert will show.
However, that only happens once.
It looks like some variable or default must have been set up once that display pops out.
How do I reset those default so that next time my app wants to use location users will be asked again?
Google map can displays that again and again.
It's Apple that asks them for permission, not you
Translation: You don't have any control over that part of the process. The little popup:
is only shown by Apple when you first ask for it - so the user always feels in control. After they accept for the first time, Apple assumes they are OK with your app getting their location information from their device, and won't show it again*.
*Unless they specifically go into Settings and disable Location Services for you app.
It's only showed on the first time and there's nothing you can do to change that. What you can do is ask your users to allow it on settings.
You can check if your app has permissions by checking:
[CLLocationManager locationServicesEnabled]
From the docs:
The user can enable or disable location services from the Settings application by toggling the Location Services switch in General.
You should check the return value of this method before starting location updates to determine whether the user has location services enabled for the current device. If this method returns NO and you start location updates anyway, the Core Location framework prompts the user to confirm whether location services should be reenabled.

Resources