In my app I have a tab called \"Discover\". The Discover tab will use the users current location to find \"stuff\" near them. Instead of presenting the user with a generic Autho
This solution isn't the best in all scenarios, but it worked for me so I thought I'd share:
import Foundation
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
static let sharedInstance = LocationManager()
private var locationManager = CLLocationManager()
private let operationQueue = OperationQueue()
override init(){
super.init()
//Pause the operation queue because
// we don't know if we have location permissions yet
operationQueue.isSuspended = true
locationManager.delegate = self
}
///When the user presses the allow/don't allow buttons on the popup dialogue
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
//If we're authorized to use location services, run all operations in the queue
// otherwise if we were denied access, cancel the operations
if(status == .authorizedAlways || status == .authorizedWhenInUse){
self.operationQueue.isSuspended = false
}else if(status == .denied){
self.operationQueue.cancelAllOperations()
}
}
///Checks the status of the location permission
/// and adds the callback block to the queue to run when finished checking
/// NOTE: Anything done in the UI should be enclosed in `DispatchQueue.main.async {}`
func runLocationBlock(callback: @escaping () -> ()){
//Get the current authorization status
let authState = CLLocationManager.authorizationStatus()
//If we have permissions, start executing the commands immediately
// otherwise request permission
if(authState == .authorizedAlways || authState == .authorizedWhenInUse){
self.operationQueue.isSuspended = false
}else{
//Request permission
locationManager.requestAlwaysAuthorization()
}
//Create a closure with the callback function so we can add it to the operationQueue
let block = { callback() }
//Add block to the queue to be executed asynchronously
self.operationQueue.addOperation(block)
}
}
Now every time you want to use location information, just surround it with this:
LocationManager.sharedInstance.runLocationBlock {
//insert location code here
}
So whenever you try using the location information, the authorization status is checked. If you don't have permission yet, it requests permission and waits until the user presses the "Allow" button or the "Don't allow" button. If the "Allow" button is pressed, any requests for location data will be processed on separate threads, but if the "Don't Allow" button is pressed, all location requests will be canceled.
When the authorisation status for location changes, the delegate method didChangeAuthorizationStatus:
will be called.
When you call requestWhenInUseAuthorization
the first time after your app is installed the delegate method will be called with status kCLAuthorizationStatusNotDetermined
(0).
If the user declines location services access then the delegate method will be called again with status kCLAuthorizationStatusDenied
(2).
If the user approves location services access then the delegate method will be called again with status kCLAuthorizationStatusAuthorizedAlways
(3) or kCLAuthorizationStatusAuthorizedWhenInUse
(4) depending on the permission that was requested.
On subsequent executions of your app the delegate method will receive status kCLAuthorizationStatusDenied
or kCLAuthorizationStatusAuthorizedAlways
/kCLAuthorizationStatusAuthorizedWhenInUse
after calling requestWhenInUseAuthorization
based on the current state of location services permission for the app in the device settings.
You can use the
locationManager:didChangeAuthorizationStatus: CLLocationManagerDelegate
method as a "callback" of sorts.
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusDenied) {
// The user denied authorization
}
else if (status == kCLAuthorizationStatusAuthorized) {
// The user accepted authorization
}
}
And in Swift (update suggested by user Michael Marvick, but rejected for some reason...):
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if (status == CLAuthorizationStatus.denied) {
// The user denied authorization
} else if (status == CLAuthorizationStatus.authorizedAlways) {
// The user accepted authorization
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if (status == CLAuthorizationStatus.denied) {
// The user denied authorization
} else if (status == CLAuthorizationStatus.authorizedAlways) {
// The user accepted authorization
}
}
Similar to tdon's response above, I created a function with a completion block to communicate the status once it is retrieved from the device:
func retrieveAuthorizationStatus(completion: @escaping (TrackingState) -> ()) {
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedWhenInUse:
completion(.off)
default:
completion(.issue)
}
}
TrackingState is a separate enum I'm using to manage display within a view controller. You could just as easily pass the authorizationStatus() in the completion block:
func retrieveAuthorizationStatus(completion: @escaping (CLAuthorizationStatus) -> ()) {
let status = CLLocationManager.authorizationStatus()
completion(status)
}
Objective C
For a block callback on didChangeAuthorizationStatus add this in .h
@property void(^authorizationCompletionBlock)(BOOL);
and following in .m
-(void)locationManager:(CLLocationManager *)locationManager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
_authorizationStatus = status;
switch (status) {
case kCLAuthorizationStatusAuthorizedAlways:
case kCLAuthorizationStatusAuthorizedWhenInUse:
if (self.authorizationCompletionBlock) {
self.authorizationCompletionBlock(YES); // this fires block
}
default:
if (self.authorizationCompletionBlock) {
self.authorizationCompletionBlock(NO); // this fires block
}
break;
}
}
and add handler like this:
// this listens block
// in your VC or Utility class
authorizationCompletionBlock = ^(BOOL isGranted) {
completionBlock(isGranted);
};
Swift 3.2
var authorizationCompletionBlock:((Bool)->())? = {_ in}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch (status)
{
case (.authorizedWhenInUse):
if authorizationCompletionBlock != nil
{
authorizationCompletionBlock!(true)
}
default:
if authorizationCompletionBlock != nil
{
authorizationCompletionBlock!(false);
}
}
}
and handler like this
authorizationCompletionBlock = { isGranted in
print(isGranted)
}