77

I have an application with an explicit user interaction that makes use of the user's current location. If the user denies access to location services, I would still like subsequent uses to prompt the user to go to settings and re-enable location services for my app.

The behavior I want is that of the built-in Maps app:

  1. Reset location warnings in Settings > General > Reset > Reset Location Warnings.
  2. Start Maps app.
  3. Tap Current Location button in lower left corner.
  4. Maps prompts with ""Maps" Would Like to Use Your Current Location" | "Don't Allow" | "Allow".
  5. Choose "Don't Allow" option.
  6. Tap Current Location button in lower left corner again.
  7. Maps prompts with "Turn On Location Services to Allow "Maps" to Determine Your Location" | "Settings" | "Cancel".

In my own app, the same basic flow results in my CLLocationManagerDelegate -locationManager:didFailWithError: method being called with a kCLErrorDenied error at the final step and the user is not given the option to open the Settings app to correct it.

I could display my own alert in response to the error, but it would not have the ability to launch the Settings app like the alert that the OS can provide as used by the built-in Maps app.

Is there something in the CLLocationManager class I am missing that would be able to give me this behavior?

GBegen
  • 6,107
  • 3
  • 31
  • 52
  • Right now, I just display an alert to the user, asking them to go to Settings to re-enable it. I'd love to hear a better solution, too. – donkim Feb 03 '11 at 23:22
  • I'd like an answer to this too, surely there's a better way – conorgriffin Feb 21 '11 at 19:46
  • I found CoreLocation wasn't satisfactory for this reason. I ended up using the skyhook library which is easy to integrate and well documented. Coordinates appear to be more precise too. The only drawback is having to bundle a 1.5MB dylib with the application. – Chris Mowforth Mar 17 '11 at 13:32
  • I think many of the answers are showing answers for https://stackoverflow.com/questions/5655674/opening-the-settings-app-from-another-app – Pramod More Jun 13 '18 at 05:15

11 Answers11

42

With iOS8, you can finally link user to Settings app via openURL. For example, you can create a UIAlertView with a single button that takes user to the Settings app:

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:ICLocalizedString(@"LocationServicesPermissionTitle")
                                                    message:ICLocalizedString(@"LocationPermissionGeoFenceMessage")
                                                   delegate:self
                                          cancelButtonTitle:@"Settings"
                                          otherButtonTitles:nil];
    [alert show];

In your UIAlertView delegate:

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    [alertView dismissWithClickedButtonIndex:buttonIndex animated:YES];
    [[UIApplication sharedApplication] openURL: [NSURL URLWithString: UIApplicationOpenSettingsURLString]];
}
mobileideafactory
  • 1,640
  • 1
  • 22
  • 30
  • 4
    This is much better than could be done in iOS 7 and earlier, but is still not perfect, since the URL takes you to a screen one level above where the user can actually change the setting. – GBegen Oct 08 '14 at 20:26
  • At least as of iOS 10, it takes you right to the app settings, where location is also listed. – Sea Coast of Tibet Oct 06 '16 at 08:59
32

Update:

As of iOS 8, there is now the constant UIApplicationOpenSettingsURLString which represents a URL that, when opened, opens the Settings app to your application's settings (where the user can then re-enable location services).


Original:

There is no way for you to do this. Your only real option is to display an alert informing the user that your application requires location services, and instructing them to manually go to the Settings app and turn it on.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • I've read on the Apple Docs and posted an answer below based on the docs, but I have not tested it my self. Is this a change in the SDK's or have they been the same. – Just a coder Jul 22 '12 at 17:49
  • I'm afraid this answer is no longer correct. You may wish to update or delete your answer. – James Webster Jul 10 '15 at 12:59
  • Here is a great example in Swift http://nshipster.com/core-location-in-ios-8/ that I found really helpful. It shows how to make an alert that redirects to `Settings` using `UIApplicationOpenSettingsURLString`. – dmk12 Apr 03 '16 at 16:31
17

AlertViews are deprecated in iOS 8. There is now a better way to handle alerts using the new AlertController:

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString( @"Enter your title here", @"" ) message:NSLocalizedString( @"Enter your message here.", @"" ) preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString( @"Cancel", @"" ) style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:NSLocalizedString( @"Settings", @"" ) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
   [[UIApplication sharedApplication] openURL:[NSURL URLWithString:
                                                    UIApplicationOpenSettingsURLString]];
}];

[alertController addAction:cancelAction];
[alertController addAction:settingsAction];

[self presentViewController:alertController animated:YES completion:nil];
Markus
  • 686
  • 1
  • 11
  • 18
7

According to Apple's Docs on the locationServicesEnabled method.

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.

So cant you just start location services updates any way to cause the alert to be prompted?

Community
  • 1
  • 1
Just a coder
  • 15,480
  • 16
  • 85
  • 138
  • 15
    It doesn't really work this way, and probably has to do with the subtle distinction between locationServicesEnabled and *authorized*. If they're disabled, it will prompt to enable, but if they're enabled but denied, it will do nothing. This is very confusing documentation, though. – SG1 Nov 06 '12 at 03:15
  • locationServicesEnabled is a deprecated method. – Ben Patch Oct 25 '16 at 17:16
6

Here is the swift 3 implementation of the code provided by Markus and bjc.

let alertController = UIAlertController(title: NSLocalizedString("Enter your title here", comment: ""), message: NSLocalizedString("Enter your message here.", comment: ""), preferredStyle: .alert)

let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)
let settingsAction = UIAlertAction(title: NSLocalizedString("Settings", comment: ""), style: .default) { (UIAlertAction) in
                UIApplication.shared.openURL(NSURL(string: UIApplicationOpenSettingsURLString)! as URL)
            }

alertController.addAction(cancelAction)
alertController.addAction(settingsAction)
            self.present(alertController, animated: true, completion: nil)
Munib
  • 957
  • 1
  • 14
  • 30
6

In Swift 4, there is an update in its syntax.

Swift 4

extension UIAlertController {

    func createSettingsAlertController(title: String, message: String) {

      let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)

      let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)
      let settingsAction = UIAlertAction(title: NSLocalizedString("Settings", comment: ""), style: .default) { (UIAlertAction) in
        UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)! as URL, options: [:], completionHandler: nil)
      }

      alertController.addAction(cancelAction)
      alertController.addAction(settingsAction)
      self.present(alertController, animated: true, completion: nil)

   }
}
YinKiet
  • 479
  • 6
  • 14
omi23
  • 106
  • 1
  • 7
2

latest swift version based on answers above.

func showSettingsAlert(_ from:UIViewController, title:String?, message:String?) {

        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)

        let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)

        let settingsAction = UIAlertAction(title: NSLocalizedString("Settings", comment: ""), style: .default) { (UIAlertAction) in

            guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
                return
            }

            if UIApplication.shared.canOpenURL(settingsUrl) {
                UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
            }
        }

        alertController.addAction(cancelAction)
        alertController.addAction(settingsAction)
        from.present(alertController, animated: true, completion: nil)
    }
Chris
  • 1,539
  • 13
  • 25
2

I guess you will have an answer to your question when Apple will think about a new SDK. At current time and as far as I know, it's not possible :

No URL-Handler available
No usable method to call

But... As Maps does it, this CAN be done, but probably using a private API. If you're not afraid with this kind of coding, you should search there in my opinion.

Oliver
  • 23,072
  • 33
  • 138
  • 230
1

Here's a Swift version of the code in the answer by Markus. This code creates an alert that gives the user the option to open Settings.

let alertController = UIAlertController(title: NSLocalizedString("Enter your title here", comment: ""), message: NSLocalizedString("Enter your message here.", comment: ""), preferredStyle: .Alert)

let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel, handler: nil)
let settingsAction = UIAlertAction(title: NSLocalizedString("Settings", comment: ""), style: .Default) { (UIAlertAction) in
    UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
}

alertController.addAction(cancelAction)
alertController.addAction(settingsAction)
self.presentViewController(alertController, animated: true, completion: nil)
bjc
  • 1,201
  • 12
  • 10
1

Swift,

Once you disable location services for an app, location manager delegate methods will start showing error. So, on receiving error we can check if location services are enabled/disabled. And according to the result, we can ask the user to go to settings and turn on location services.

In your location manager delegate method for error, add location permission check

func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
            //check  location permissions
            self.checkLocationPermission()
        }
}

Code for location permission checking

//check location services enabled or not

    func checkLocationPermission() {
        if CLLocationManager.locationServicesEnabled() {
            switch(CLLocationManager.authorizationStatus()) {
            case .notDetermined, .restricted, .denied:
                //open setting app when location services are disabled
            openSettingApp(message:NSLocalizedString("please.enable.location.services.to.continue.using.the.app", comment: ""))
            case .authorizedAlways, .authorizedWhenInUse:
                print("Access")
            }
        } else {
            print("Location services are not enabled")
            openSettingApp(message:NSLocalizedString("please.enable.location.services.to.continue.using.the.app", comment: ""))
        }
    }

Code to open settings app,

//open location settings for app
func openSettingApp(message: String) {
    let alertController = UIAlertController (title: APP_NAME_TITLE, message:message , preferredStyle: .alert)

    let settingsAction = UIAlertAction(title: NSLocalizedString("settings", comment: ""), style: .default) { (_) -> Void in
        guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
            return
        }

        if UIApplication.shared.canOpenURL(settingsUrl) {
            UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
        }
    }
    alertController.addAction(settingsAction)
    let cancelAction = UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .default, handler: nil)
    alertController.addAction(cancelAction)

    present(alertController, animated: true, completion: nil)
}
Pramod More
  • 1,220
  • 2
  • 22
  • 51
0

Swift 3 extension for creating settings alert controller:

import Foundation

extension UIAlertController {
    func createSettingsAlertController(title: String, message: String) -> UIAlertController {
        let controller = UIAlertController(title: title, message: message, preferredStyle: .alert)

        let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment:"" ), style: .cancel, handler: nil)
        let settingsAction = UIAlertAction(title: NSLocalizedString("Settings", comment:"" ), style: .default, handler: { action in
            UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
        })
        controller.addAction(cancelAction)
        controller.addAction(settingsAction)

        return controller
    }
}
Roman Barzyczak
  • 3,785
  • 1
  • 30
  • 44