I have a view controller which implements the CLLocationManagerDelegate. I create a the CLLocationManager variable:
let locationManager = CLLocationManager()
Then in the viewDidLoad, I set properties:
// Set location manager properties
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 50
The problem comes that the function gets called even before I check the authorization status.
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if (status == .AuthorizedWhenInUse) {
// User has granted autorization to location, get location
locationManager.startUpdatingLocation()
}
}
Can anyone inform me what could be causing this to occur?
- locationManager:didChangeAuthorizationStatus: is called shortly after the CLLocationManager is initialised.
You can request authorization inside the delegate method if you want:
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager.requestAlwaysAuthorization()
break
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
break
case .authorizedAlways:
locationManager.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
Be aware that you need to assign the delegate in a 'timely' matter if you want this to work.
If you would somehow delay the delegate assignment, e.g. by setting it asynchronously, you might miss the initial call to - locationManager:didChangeAuthorizationStatus:.
Swift 3
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
manager.requestAlwaysAuthorization()
break
case .authorizedWhenInUse:
manager.startUpdatingLocation()
break
case .authorizedAlways:
manager.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
}
}
The other answers could introduce new undesirable behaviors.
You can just add a boolean and a guard to prevent the first call, with some comments explaining the bug :
var firstTimeCalled = true
// ...
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard !firstTimeCalled else {
firstTimeCalled = false
return
}
// ... send status to listeners
}
Related
I have implemented a location authorization callback function and it works, but looking over the code I seem to have got the function to trigger but never actually use the function as intended. Please can you comment on how bad I am being and if it is acceptable to do what I have done.
import UIKit
import CoreLocation
class ViewController___Main_menu: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
print("services are enabled")
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
print("Not determined")
case .restricted:
print("Resticted")
case .denied:
print("Denied")
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
#unknown default:
print("unknown error")
}
} else {
print("not enabled")
}
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus){
print("Status: \(status)")
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
print("Not determined")
case .restricted:
print("Resticted")
case .denied:
print("Denied")
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
#unknown default:
print("unknown error")
}
}
}
It's looks like you're requesting access to location services and straight after that checking what the authorization status is, expecting it to be available immediately. This isn't the way it works. When you request location with locationManager.requestWhenInUseAuthorization the request happens asynchronously.
When the location status changes then it will call to the function you're included locationManager(_:didChangeAuthorization) with the new status. This will happen when the user taps "Allow" or "Deny" on the prompt that appears. It can also happen when they go into the settings and change the permissions there
So to make it more clear, check and log the authorization status before requesting location. Use the following methods to know if the location request succeeded or failed:
func locationManager(CLLocationManager, didUpdateLocations: [CLLocation])
func locationManager(CLLocationManager, didFailWithError: Error)
You would use the CLLocationManager.locationServicesEnabled() check to wrap the request for location services. If location is turned off for the device then requesting location will just cause an error.
When user is on MainViewController
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}
viewDidLoad() will run and the alert will appear
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Accessing user location to determine the nearest pickup location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Accessing user location to determine the nearest pickup location</string>
The main question is, if user clicks Don't allow, how do I check programmtically? for example
// Example code snippet, not from apple officials
if NSlocation is nil then I want to do some alert
If the user chooses to deny your app access to location services, CLLocationManager.authorizationStatus() will be .denied (though you should also watch out for .restricted. If you'd like to be notified when the user chooses whether or not to allow or deny your app, you can make yourself your location manager's delegate and implement the proper method:
class MainViewController: CLLocationManagerDelegate{
let locationManager = CLLocationManager()
override func viewDidLoad() -> Void{
super.viewDidLoad()
locationManager.delegate = self
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) -> Void{
switch status{
case .authorizedAlways, .authorizedWhenInUse:
// You're allowed to use location services
case .denied, .restricted:
// You can't use location services
default:
// This case shouldn't be hit, but is necessary to prevent a compiler error
}
}
}
Use below delegate method of CLLocationManager to check user click on Don't allow button in permission alert.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .denied:
print("Show you own alert here")
break
}
}
To redirect user in location settings follow below:
Add URL Types "prefs" like below image
Write this code on button action:
UIApplication.sharedApplication().openURL(NSURL(string:"prefs:root=LOCATION_SERVICES")!)
I'm trying to animate where the user is when they accepts location tracking. The Code works when they already have accepted location tracking and then loads the view but I would like to have the view reload when they press accept on location tracking.
override func viewDidLoad() {
super.viewDidLoad()
//Prepare to get User
if CLLocationManager.authorizationStatus() != .authorizedAlways {
// May need to change to support location based notifications
locationManager.requestAlwaysAuthorization()
print("hello")
mapView.setVisibleMapRect(mapView.visibleMapRect, animated: true)
}else {
locationManager.startUpdatingLocation()
}
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
mapView.delegate = self
}
Animated to user location Code
//Get user location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//Show the map at the users current location
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.02,0.02 )
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
mapView.setRegion(region, animated: true)
self.mapView.showsUserLocation = true
locationManager.stopUpdatingLocation()
}
You can use:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager.requestAlwaysAuthorization()
break
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
break
case .authorizedAlways:
locationManager.startUpdatingLocation()
break
default:
break
}
}
All methods used to update locations in CLLocationManager are asynchronous, so there might be a delay from the user allowing location access to the location update actually happening regardless of the implementation of your code.
However, by calling CLLocationManager().requestLocation() inside locationManager(didChangeAuthorizationStatus) you can ensure that you receive a location update as close as possible to the acceptance of location usage.
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse:
locationManager.requestLocation()
case .authorizedAlways:
locationManager.requestLocation()
default:
break
}
}
This will automatically call your CLLocationManagerDelegate method as soon as the asynchronous requestLocation function finishes execution.
I'm working on an app that requires getting the user's current coordinates. I was planning on doing this through CLLocationManager's didUpdateLocations method. For some reason, didUpdateLocations is not being executed. However, it appears that locationManager.startUpdatingLocation() is being called successfully. None of the other possible solutions I've seen on this site have worked for me. I already added NSLocationAlwaysUsage to info.plist. Here is the entirety of my code:
import UIKit
import MapKit
import CoreLocation
var region: MKCoordinateRegion!
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var map: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse, .authorizedAlways:
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
print("Updating location now")
}
case .notDetermined:
locationManager.requestAlwaysAuthorization()
case .restricted, .denied:
print("User must enable access in settings")
break
}
if (region == nil){
}
else {
map.setRegion(region!, animated: true)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("Got location")
let userLocation:CLLocation = locations[0]
let lat:CLLocationDegrees = userLocation.coordinate.latitude
let long:CLLocationDegrees = userLocation.coordinate.longitude
let currentPos:CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, long)
didUpdateRegion(position: currentPos)
print(lat)
print(long)
}
func didUpdateRegion(position: CLLocationCoordinate2D) {
let span = MKCoordinateSpanMake(0.075, 0.075)
region = MKCoordinateRegion(center: position, span: span)
}
func locationManager(_manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
// If status has not yet been determied, ask for authorization
manager.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
// If authorized when in use
manager.startUpdatingLocation()
break
case .authorizedAlways:
// If always authorized
manager.startUpdatingLocation()
break
case .restricted:
print("User must activate location services in settings")
break
case .denied:
print("User must activate location services in settings")
break
default:
break
}
}
When I run this code on both the simulator and an actual device, I get the notification to allow location tracking. After accepting that, the console displays "Updating location now," but never gets to printing "Got location." Thank you for any light you can shed on this issue, I'm new to app development in general.
EDIT: I added in the entirety of my code instead of just the parts I thought were relevant. Basically, I'm trying to get the region shown on the map to follow the user. I attempt to do this by updating the variable "region" every time the didUpdateLocations function fires.
Am I getting it right and you only added one key - NSLocationAlwaysUsage?
Try to add both keys to the Info.plist:
Privacy - Location When In Use Usage Description
Privacy - Location Always Usage Description
Also, what happens if you implement this method of protocol?
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("\(error.localizedDescription)")
}
Does it print anything? Sorry, I was going to leave a comment, but I don't have enough reputation.
The problem I am facing is that when I press a UIButton - location services are required to initiate the action. However if the user was to deny Location Services at the initial launch of the app - the app will crash.
I have tried finding a way to implement CLAuthorizationStatus .Denied but I can't seem to find a way to do so. The only code I can seem to implement is the didChangeAuthorizationStatus which only initiates the request at First Launch of the application.
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus)
{
if status == .AuthorizedAlways || status == .AuthorizedWhenInUse
{
manager.startUpdatingLocation()
}
else
{
manager.requestWhenInUseAuthorization()
}
}
If I press the UIButton to send the API Request the app will crash if location services have been denied.
My question is how can I implement a method, within the button's IBAction, that will direct the user to go to their settings and enable location services. :)
CLLocationManager has a static function authorizationStatus() that you can use to get the current authorization status without even initializing a CLLocationManager object.
So in the function that you call when the user presses the button you can check the authorization status and act accordingly:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
lazy var locationManager = CLLocationManager()
...
func didPressButton(sender: UIButton) {
switch CLLocationManager.authorizationStatus() {
case .AuthorizedAlways, .AuthorizedWhenInUse:
locationManager.delegate = self
locationManager.startUpdatingLocation()
case .NotDetermined:
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
case .Denied:
print("Show Alert with link to settings")
case .Restricted:
// Nothing you can do, app cannot use location services
break
}
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse {
manager.startUpdatingLocation()
}
}
}