Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CLLocationManager.authorizationStatus() returns alwaysInUse even when authorizedWhenInUse selected in device settings

Reproduction steps are the following:

  1. Delete app from device completely
  2. Install it from XCode
  3. On prompt select "When in use"
  4. Turn on region monitoring in the app

On step 4 I have check this

CLLocationManager.authorizationStatus()

Basically if "always" was not selected - don't turn on region monitoring. It was working fine in iOS 12-. Now however there is a problem in iOS 13 where it returns me "always" even though settings clearly indicates that "when in use" was selected. I'm very puzzled what to do here. Do I need to rewrite the whole business logic to support this weird behaviour somehow or this is apple's bug?

According to topic here

https://forums.developer.apple.com/thread/117256

this is a known bug, but it's basically November and still no info on fixes. Any suggestions / ideas on workaround it?

like image 219
Alexander Avatar asked Sep 30 '19 12:09

Alexander


2 Answers

To add to what @greenOrange wrote in his update, what you're experiencing here relates to iOS 13's "provisional always" location permission "feature."

Some background: on iOS 12 and below, many apps in the App Store were requesting authorizedAlways location access but didn't actually need it. As such, Apple removed the option to ask for authorizedAlways right away. This has a few implications:

  1. If you call requestAlwaysAuthorization() on iOS 13.0+, the user-facing dialogue that pops up will never give the option to actually select authorizedAlways. The most pervasive option the user will see is authorizedWhenInUse.

  2. MOST IMPORTANTLY: when a user selects authorizedWhenInUse on the dialogue, the app will enter what is known as the "provisional always" state. What this means is that asking (programmatically) for the current location permission setting will return authorizedAlways, even though the Settings app will show authorizedWhenInUse. Yes, this is super confusing. Here's some more info on this (fast-forward to 4:23 for a helpful graphic): https://developer.apple.com/videos/play/wwdc2019/705

  3. Your app will stay in this "provisional always" state until the OS presents a dialogue to your user asking him/her to keep the permission set to authorizedWhenInUse or change it to authorizedAlways. This dialogue looks like a normal alert controller, but with a map at the top, showing all the places where the app has accessed the user's location data over the last 72ish hours. NOTE: this dialogue usually appears on the phone's home screen — not within your app itself.

  4. The biggest problem with the "provisional always" state is that there's no reliable way to know that your app is in it. Sure, you could check to see if the user installed the app recently (say within the last 72 hours), but since there's no documented API for this, such an approach seems like educated guessing, at best.

With iOS 13.4, however, Apple heard the pains of the development community and offered a new solution to this problem. Thus, as of iOS 13.4, we can first call requestWhenInUseAuthorization(), and, assuming it's granted by the user, immediately call requestAlwaysAuthorization() to get another system dialogue, prompting the user to grant authorizedAlways (and bypass the "provisional always" state). This obviously has implications on your UI/UX, but, overall, is a better experience for developers than that of iOS 13.0-13.3. It might not be a perfect solution, but at least it helps us avoid the confusing "provisional always" state.

For more information on this iOS 13.4 change, see Apple's release notes here (under the "Location Services" section): https://developer.apple.com/documentation/ios_ipados_release_notes/ios_ipados_13_4_release_notes

like image 76
Brian Sachetta Avatar answered Nov 17 '22 03:11

Brian Sachetta


UPDATE:

If you request your location authorization using requestAlwaysAuthorization, then the app will first treat your authorization as "AuthorizedAlways" until the app asks for the location in the background.

Then the permission prompt shows up again whether it will be "Always" or "While in Use". This time, if the user press "While in Use" the app correctly returns status of authorizedWhenInUse status.

So although the app location permission was set as "When In Use", in reality the app would be looking at this as "Always" permission until the user specifically choose "When In Use" in the second prompt.

I think authorizationStatus can be still used, but it's still safer to adapt to didChangeAuthorizationStatus to make sure you take appropriate action when user answers the second prompt.


From Apple document about authorizationStatus(), it says that using it is discouraged.

Important Use of authorizationStatus() is unnecessary and discouraged. Instead, implement the locationManager(_:didChangeAuthorization:) delegate callback to receive up-to-date authorization status.

As recommended, when I checked for locationManager(_:didChangeAuthorization:) response, I was able to get the result accurately for "WhenInUse" as kCLAuthorizationStatusAuthorizedWhenInUse and for other status.

You would be able to add that delegate to do whatever action you would like to proceed once the app is open when the user only opted in for "WhenInUse" or "Denied", etc.

like image 22
green0range Avatar answered Nov 17 '22 05:11

green0range