app only requesting location acces when in use - ios

I want to use requestAlwaysAuthorization but for some reason it only gives the options to allow once, allow when in use, or dont allow. This is my code.
if CLLocationManager.locationServicesEnabled() {
switch locationManager.authorizationStatus {
case .notDetermined, .restricted, .denied:
locationManager.requestAlwaysAuthorization()
case .authorizedAlways:
break
case .authorizedWhenInUse:
locationManager.requestAlwaysAuthorization()
#unknown default:
break
}
} else {
locationManager.requestAlwaysAuthorization()

From Apple's documentation for requestAlwaysAuthorization:
You must call this or the requestWhenInUseAuthorization() method before your app can receive location information. To call this method, you must have both NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription keys in your app’s Info.plist file. You may call requestAlwaysAuthorization() when the current authorization state is either:
Not Determined — CLAuthorizationStatus.notDetermined
When In Use — CLAuthorizationStatus.authorizedWhenInUse
Use the locationManager(_:didUpdateLocations:) method on the CLLocationManager delegate to receive updates when the user makes permission choices.
Core Location limits calls to requestAlwaysAuthorization(). After your app calls this method, further calls have no effect.
Based on this, few things could be wrong with your code.
Too many calls to requestAlwaysAuthorization. This should be sufficient:
if CLLocationManager.locationServicesEnabled() {
switch locationManager.authorizationStatus {
case .notDetermined, .authorizedWhenInUse:
locationManager.requestAlwaysAuthorization()
default:
print("Cannot ask user for requestAlwaysAuthorization")
}
}
When user has already denied location permission, asking for it again will not show the popup.
Missing NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription in Info.plist. Define non-empty strings for these in your Info.plist.
Finally try reinstalling the app on device/simulator to clear previously granted or denied location permissions

Related

How to force 'always' location access in iOS app

The app that I'm building needs always location access to work properly. The app basically tracks location and puts it on a map and stuff (details not important, lol).
My goal is this:
Prompt user to enable "always" location access
If always location access has been requested but the user said no, make the app unusable - basically just show a little button that redirects them to settings where they can change that setting.
My AppDelegate.swift is implementing CLLocationManagerDelegate, and the code is as follows:
alreadyRequestedLocationWhenInUse = UserDefaults.standard.bool(forKey: "alreadyRequestedLocationWhenInUse")
alreadyRequestedLocationAlways = UserDefaults.standard.bool(forKey: "alreadyRequestedLocationAlways")
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
if (!alreadyRequestedLocationWhenInUse) {
print("Requesting location access 'while in use'.")
self.locationManager.requestWhenInUseAuthorization();
UserDefaults.standard.set(true, forKey: "alreadyRequestedLocationWhenInUse")
alreadyRequestedLocationWhenInUse = true
} else {
promptToChangeLocationSettings()
}
case .restricted, .denied:
print("No Location access")
promptToChangeLocationSettings()
break;
case .authorizedWhenInUse:
if (!alreadyRequestedLocationAlways) {
print("Requesting location access 'Always'.")
self.locationManager.requestAlwaysAuthorization()
UserDefaults.standard.set(true, forKey: "alreadyRequestedLocationAlways")
alreadyRequestedLocationAlways = true
} else {
promptToChangeLocationSettings()
}
break;
case .authorizedAlways:
self.startLocationMonitoring();
break;
default:
self.locationManager.requestAlwaysAuthorization();
return
}
}
where promptToChangeLocationSettings() is a properly working function that takes the user to the settings page for my app.
The problem is that the user isn't prompted to enable "Always location tracking" until they exit the app and come back. They are asked for 'while in use' permissions (and I know that the way it works is they have to say yes to that first), but I want the always prompt to happen right away! In theory, the locationManagerDidChangeAuthorization function should be called AGAIN after the 'while use' authorization is granted, but this does not happen! Why does this not happen? Instead, promptUserToChangeLocationSettings() runs and makes the app unusable BEFORE the user gets the little poup that asks whether they want to enable 'always' location access.
Can someone help me fix this?
By the way, I am using UserDefaults to keep track of whether we have done a location permission request (as the request can only be done once).
A few observations about this flow where we request “when in use” first, and when that's granted, only then request “always” (as discussed in WWDC 2020 What's New in Location):
Make sure you run this on a device, not the simulator. You may not see the subsequent “upgrade ‘when-in-use’ to ‘always’” permission alert when using a simulator.
This feature was introduced in iOS 13.4. Make sure you are not attempting this on an earlier iOS 13 version. On those earlier versions, you won’t see the second alert to upgrade to “always”.
Make sure you don’t have a lingering requestAlwaysAuthorization elsewhere in your code-base that might have put the app in a “provisional always” state. Once in provisional state, you are locked into the provisional flow of 13.0.
I know it isn’t what you’re looking for, but for the sake of future readers, the alternative to the above is the simpler “provisional always” flow introduced in iOS 13.0 (outlined in WWDC 2019's What's New in Core Location). You just call requestAlwaysAuthorization (never calling requestWhenInUseAuthorization). Apple's intent here was to let the user better reason about what’s going on, showing the “when in use” alert while the app is in use and automatically showing the “always” upgrade alert when location services are used while the app isn't running.
This is a solution that got the result I desired:
Firstly: Make a call to locationManagerDidChangeAuthorization(locationManager) in the AppDelegate.swift didFinishLaunchingWithOptions function. I also called it in applicationWillEnterForeground just so that it rechecks every time the app opens.
Secondly, this is my new locationManagerDidChangeAuthorization function. Just need to remove the return/break statements, but I'm just gonna answer this now before I forget:
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
UserDefaults.standard.set(false, forKey: "alreadyRequestedAlwaysLocationAccess")
alreadyRequestedAlwaysLocationAccess = false
DispatchQueue.main.async{
self.coreLocationManager.requestWhenInUseAuthorization()
self.locationManagerDidChangeAuthorization(manager)
}
break;
case .restricted, .denied:
print("No Location access")
promptToChangeLocationSettings()
break;
case .authorizedWhenInUse:
if (!alreadyRequestedAlwaysLocationAccess) {
print("Requesting location access 'Always'.")
UserDefaults.standard.set(true, forKey: "alreadyRequestedAlwaysLocationAccess")
alreadyRequestedAlwaysLocationAccess = true
DispatchQueue.main.async{
self.coreLocationManager.requestAlwaysAuthorization()
self.locationManagerDidChangeAuthorization(manager)
}
} else {
promptToChangeLocationSettings()
}
break;
case .authorizedAlways:
self.startLocationMonitoring();
break;
default:
return
}
}

What makes provisional Always authorization provisional?

According to Apple, if you ask for your Core Location app to get Always authorization when the authorization is "not determined", the user sees the dialog for When In Use authorization but in fact your app gets Always authorization — provisionally.
This is supposed to mean that if you don't actually use your Always powers, you will lose them, reverting to When In Use.
Okay, but when will that reversion happen? I can't seem to make it happen. My app just stays at Always authorization, even though the user thinks it is only When In Use authorization.
Here's the entire code of my test app (iOS 14):
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var label: UILabel!
let locman = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locman.delegate = self
}
#IBAction func doAskForAlways(_ sender: Any) {
self.checkForLocationAccess(always:true)
}
func checkForLocationAccess(always:Bool = false, andThen f: (()->())? = nil) {
let status = self.locman.authorizationStatus()
switch status {
case .authorizedWhenInUse:
if always { // try to step up
self.locman.requestAlwaysAuthorization()
} else {
f?()
}
case .authorizedAlways:
f?()
case .notDetermined:
if always {
self.locman.requestAlwaysAuthorization()
} else {
self.locman.requestWhenInUseAuthorization()
}
case .restricted:
break
case .denied:
break
default: fatalError()
}
}
fileprivate func updateStatus(_ status: CLAuthorizationStatus) {
self.label.text = {
switch status {
case .authorizedAlways: return "Always"
case .authorizedWhenInUse: return "When In Use"
default: return ""
}
}()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus()
print("authorization is", status.rawValue)
updateStatus(status)
}
#IBAction func doStatus(_ sender: Any) {
self.updateStatus(self.locman.authorizationStatus())
}
}
You need two buttons and a label. Tap the first button to ask for Always authorization when you have no authorization to start with ("not determined"). You see the When In Use authorization dialog. Grant authorization. Now play with the app and keep watching the status display in the label. You can tap the second button to update the status if needed.
The problem is that it stays at Always. When will my "provision" come to an end so that the authorization reverts to When In Use? How can I encourage this to happen?
In WWDC 2019's What's New in Core Location, they outline the basic process in iOS 13.0:
Your app requests “always” permission.
The user sees “when in use” permissions alert, not an “always” permission alert:
If the user grants “when in use” the app is in “provisional always” state.
In this case, and somewhat confusingly, the authorizationStatus will return .authorizedAlways when you are in this “provisional always” state and the Settings app on the phone will suggest it’s in “when in use” state. But in reality, it’s in this “provisional always” state, not quite what one might infer from authorizationStatus nor from what you see in the Settings app.
Needless to say, if the user doesn't even grant “when in use” (e.g. they deny or chose “only once”), then obviously you won’t be in “provisional always” state.
It remains in this provisional state until, as the video says, you “start using ‘always’ powers”. For example, if you start significant change service and move a distance sufficient to trigger a significant change.
When the app does “start using ‘always’ powers”, the OS will ask the user if they are is willing to upgrade “when in use” to “always”. (It won't always happen immediately, but will wait until the user is not busy doing other things, to reduce the risk that they'll dismiss the alert just to get back to what they were doing.)
So, it’s not a question of “reverting” to some other state. The app will remain in this “provisional always” state until there is final “agreement” (where the user sees the second alert and either agrees to upgrade to .authorizedAlways or denies and it is set to .authorizedWhenInUse).
I know you know this, but for the sake of future readers:
In WWDC 2020 video What's new in location, they describe a change introduced in iOS 13.4. Instead of the flow above (where you ask for “always”, the user sees “when in use” permissions, and they don’t see the “upgrade to always” until “always” services are actually triggered), iOS 13.4 introduced a new flow, where you can ask for “when in use” (rather than “always”) and assuming the user granted it, you can ask for “always” later, where appropriate in the app, and the user get the second alert (this time asking if the user would like to upgrade to “always” or not). You just need the appropriate permissions strings.

MPMediaPickerControllerDelegate Permission Check Swift

I have the following code in my project to select audio files through 'MPMediaPickerControllerDelegate'. When run the below code, for the first time the user is prompted to grant permission. If the user declines the loading the picker would silently fail.
Is there a way to prompt user to grant permission if they return after declining at first?
I was wondering if it's a bug. Is there any way we can detect the permission of the MPMediaPickerControllerDelegate so that we can decide to show the audio or not.
Below is my code:
let myMediaPickerVC = MPMediaPickerController(mediaTypes: MPMediaType.anyAudio)
myMediaPickerVC.allowsPickingMultipleItems = false
myMediaPickerVC.delegate = self
present(myMediaPickerVC, animated: true, completion: nil)
You have to first check the authorization permission, then run your code if authorizationStatus permission is authorized. If it is not notDetermined then ask for permission. If it denied then show Alert asking user to go to Settings and enable this permission.
MPMediaLibrary.requestAuthorization({(newPermissionStatus: MPMediaLibraryAuthorizationStatus) in
// This code will be called after the user allows or denies your app permission.
switch (permissionStatus) {
case MPMediaLibraryAuthorizationStatus.authorized:
print("permission status is authorized")
case MPMediaLibraryAuthorizationStatus.notDetermined:
print("permission status is not determined")
MPMediaLibrary.requestAuthorization(MPMediaLibraryAuthorizationStatus -> permissionStatus)
case MPMediaLibraryAuthorizationStatus.denied:
print("permission status is denied")
case MPMediaLibraryAuthorizationStatus.restricted:
print("permission status is restricted")
}
})

Location permission issue iOS 11 and iOS 10

I am having an issue when requesting location permissions from user when I use iOS11 my info.plist contains
<key>NSLocationWhenInUseUsageDescription</key>
<string>When in use permissions</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>always permissions</string>
<key>NSLocationAlwaysAndWhenInUsageDescription</key>
<string>Always and in usage permissions</string>
I have two maps one for customer and another for employees. For employees I need to know their location even if the app is not running or is backgrounded (they are able to turn it off when signing out) and request permission using
locationManager.requestAlwaysAuthorization()
For customer i only need locations while the app is in use and request the permission using
locationManager.requestWhenInUseAuthorization()
In iOS 11 this only requests permission when in usage and never the always on permission.
In iOS 10 it has the correct behaviour.
The behaviour I want is as follows:
When they are a customer (not signed in) it only asks for when in use permission. If they sign in (employee) it request location even when not in use.
If anyone can shed some light on what I am missing / done wrong it would be much appreciated.
Something to note if i remove the permission NSLocationAlwaysUsageDescription iOS10 and iOS11 have the same issue of not requesting always permission.
A bit more clarification.
I have implemented didChangeAuthorization delegate function and it gets called when a users allow the permission from alert from calling requestWhenInUseAuthorization()
however when I call requestWhenInUseAuthorization() function on location manager the delegate method is not called it is like it's never receiving that call and no alert dialog is shown to the user.
I figured out the issue by creating a quick stand alone app that only asked for permissions, I was given an error log that stated the keys I was using were wrong.
I had NSLocationAlwaysAndWhenInUsageDescription instead of NSLocationAlwaysAndWhenInUseUsageDescription which is odd because from the docs it states that NSLocationAlwaysAndWhenInUsageDescription should be used. Switching to include the correct key fixed issue and now permissions works as expected for iOS 11 and 10.
Thanks for all the help.
In your info.plist file add this:
<key>NSLocationUsageDescription</key>
<string></string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
Now in your swift file, don't forget to add delegate: CLLocationManagerDelegate
In your viewDiDLoad(), add this:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()
// Here you can check whether you have allowed the permission or not.
if CLLocationManager.locationServicesEnabled()
{
switch(CLLocationManager.authorizationStatus())
{
case .authorizedAlways, .authorizedWhenInUse:
print("Authorize.")
break
case .notDetermined:
print("Not determined.")
break
case .restricted:
print("Restricted.")
break
case .denied:
print("Denied.")
}
}
For both cases, customers and employees, you first need to call locationManager.requestWhenInUseAuthorization()
Then, only if they are employees, add a call to
locationManager.requestAlwaysAuthorization()
See https://developer.apple.com/documentation/corelocation/choosing_the_authorization_level_for_location_services/request_always_authorization
Overview To configure always authorization for location services, do
the following: Add the NSLocationWhenInUseUsageDescription key and the
NSLocationAlwaysAndWhenInUsageDescription key to your Info.plist file.
(Xcode displays these keys as "Privacy - Location When In Use Usage
Description" and "Privacy - Location Always and When In Use Usage
Description" in the Info.plist editor.) If your app supports iOS 10
and earlier, add the NSLocationAlwaysUsageDescription key to your
Info.plist file. (Xcode displays this key as "Privacy - Location
Always Usage Description" in the Info.plist editor.) Create and
configure your CLLocationManager object. Call the
requestWhenInUseAuthorization() initially to enable your app's basic
location support. Call the requestAlwaysAuthorization() method only
when you use services that require that level of authorization.
** Latest Working code in Swift 5.1:**
Plist: Add the entry
<key>NSLocationWhenInUseUsageDescription</key>
<string>Needs Location when in use</string>
import UIKit
import CoreLocation
class ViewController: UIViewController {
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
//Make sure to set the delegate, to get the call back when the user taps Allow option
locationManager?.delegate = self
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
print("not determined - hence ask for Permission")
manager.requestWhenInUseAuthorization()
case .restricted, .denied:
print("permission denied")
case .authorizedAlways, .authorizedWhenInUse:
print("Apple delegate gives the call back here once user taps Allow option, Make sure delegate is set to self")
}
}
}

How to request for permission before the actual object is initialized?

How can I request for permissions to location, camera, bluetooth etc. without initializing proper object instances?
I want to ask for permissions/authorizations during app's onboarding, then I'd like to initialize CLLocationManager etc afterwards.
I tried Googling it, but failed to find anything relevant. Is it actually possible?
For permissions like location access ,we need a manager instance to show the location access prompt ( refer :http://nshipster.com/core-location-in-ios-8/). These instances can be used only to request access (if you want them to only request for access) and in the future if you want to access the resource or data we can again use these manager instances.
For Example:
CLLocation manager should be used to access user's location, so in first screen if you just want to ask location permission then can use following code
CLLocationManager().requestAlwaysAuthorization() //Requesting always permission
And if you want to access user's location in some other screen you can access it as:
locationManager.startUpdatingLocation() // use delegate methods to handle the values.
So these kind of managers can be initialised only for requesting permission, then can be reinitialised when required.
Here is an article about best way to ask location permissions (same can be applied for other types of permission requests) https://techcrunch.com/2014/04/04/the-right-way-to-ask-users-for-ios-permissions/
For each action do the similar approach
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized:
case .denied, .restricted :
//handle denied status
case .notDetermined:
// ask for permissions
PHPhotoLibrary.requestAuthorization() { (status) -> Void in
switch status {
case .authorized:
// as above
case .denied, .restricted:
// as above
case .notDetermined: break
// won't happen but still
}
}
}

Resources