Having some issues using location manager.
It seems like locationManager.requestWhenInUseAuth does not stop app flow, and startUpdatingLocation is called before user can dismiss auth alert.
How to avoid this?
My app loads default values for non-available GPS, so I always get default (because this func is called even if "want to auth this app...?" alert is still showing).
My code:
if ask{
locationManager.requestWhenInUseAuthorization()
self.manageLocation()
}
func manageLocation(){
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
// load default deck
self.loadBlink(useDefault: true)
case .authorizedAlways, .authorizedWhenInUse:
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.distanceFilter = 10.0
locationManager.startUpdatingLocation()
}
} else {
// load default deck
self.loadBlink(useDefault: true)
}
}
Requesting authorization in asynchronous. It returns immediately, and you have to take that into account.
Add this into your info.plist
<key>NSLocationAlwaysUsageDescription</key>
<string>$(PRODUCT_NAME) would like to use your location.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME) would like to use your location.</string>
set var locationManager:
var locationManager:CLLocationManager!
on viewDidLoad call this:
locationManager = [[CLLocationManager alloc] init]; // objective-c
locationManager = CLLocationManager() // swift (if i'm not wrong)
call this requestWhenInUseAuth before you call startUpdatingLocation
Related
Fist time when I install my iOS app on a device via xcode and location service is off, requestAlwaysAuthorization() shows "Turn on location services" that push user to device Settings to turn on location service, then user comes back to the app and would see "permission" alert(with three options always, while using and never). If user taps on always option then closes the app completely and turns location service off then opens app again, "Turn on location services" alert is not displayed. This is my code:
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
NotificationCenter.default.addObserver(self, selector: #selector(checkLocationService), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
#objc func checkLocationService() {
if CLLocationManager.locationServicesEnabled() {
switch CLLocationManager.authorizationStatus() {
case .denied, .notDetermined, .restricted:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
...
}
} else {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
}
I've added all three location keys in the Info.plist:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>My app need your location to work</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>My app need your location to work</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>My app need your location to work</string>
I am testing on iOS 11 and 12 and I have no idea what is wrong.
You only request auth once... if the user grants permission then you dont need to ask again and if they refuse you can't prompt again. you need to handle it differently if denied. you push the user to the app settings in the settings menu and the user has to enable from there
switch CLLocationManager.authorizationStatus() {
case .notDetermined, .restricted:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
...
case .denied:
// present an alert advising the user that they need to go to the settings menu to enable the permissions as they have previously denied it.
// if the user presses Open Settings on the alert....
if let url = URL(string: UIApplicationOpenSettingsURLString) {
UIApplication.shared.open(url: url)
}
}
I'm using the MapKit in my Swift iOS app.
The thing is that I'm requesting the permission to access the user location when the app is in use, but the first time I run the app in my iPhone, it stays frozen in the splash screen, because the permission request don't popup, but then, if I press the home button, the popup appears to ask for permission. And if I accept then, the next run the app works properly, but it shouldn't work like this.
So in the code, the debugger crashes here because he cannot get the permission:
let initialLocation:CLLocation = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
Indicating the next issue: Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1000b5d00)
So, I'm already asking the permissions in the viewWillAppear method:
let locationManager = CLLocationManager()
// Ask for Authorisation from the User.
// locationManager.requestAlwaysAuthorization()
// For use in foreground
locationManager.requestWhenInUseAuthorization()
//locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
//locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
mapView.showsUserLocation = true
}
And I also have the entry in the Info.plist: Privacy - Location When In Use Usage Description.
Why is the popup not showing in the foreground but in the background?
Thanks in advance for your help.
Cheers
EDIT:
I have an splash screen with the logo before the map view. Can this be the problem?
EDIT 2 in answer to #Dan Clark
Ok, I've added this check in the viewDidLoad as below:
EDIT 3
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
print("viewdidload")
if CLLocationManager.authorizationStatus() != .AuthorizedWhenInUse // Check authorization for location tracking
{
print("requestingautorization")
locationManager.requestWhenInUseAuthorization()
print("afterrequestingauthorization")
// LocationManager will callbackdidChange... once user responds
} else {
print("startupdatinglocation")
addPins(locationManager)
}
}
But the popup requesting the authorization is not appearing :( I've got both prints before and after but the popup is not showing.
I also added the function you wrote me, in the same class.
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
print("instatuscheck")
switch status
{
case .AuthorizedWhenInUse:
print("statusauthorized")
addPins(manager)
default:
print("statusdefault")
manager.requestWhenInUseAuthorization()
// User denied access, handle as appropriate
}
}
But I don't have it clear... this function will be called automatically when the authorization status changes?
Thanks again for your help :)
The problem is that it can take a while for you to get authorized by LocationManager after you make the request. Therefore, on your first try you don't have authorization before reaching the closure after your request. I've addressed this by testing for authorization and, if I don't have it, putting in the request and then waiting for the callback to didChangeAuthorizationStatus before starting location updates. If I already do have authorization, I immediately start location updates.
By the second time you run the app, you have the authorization so the delay doesn't occur and you're OK to go.
To try this approach, include this section in your ViewDidLoad (I'm assuming that you don't need to run this whenever your view appears, but only when it first starts):
if CLLocationManager.authorizationStatus() != .authorizedAlways // Check authorization for location tracking
{
locationManager.requestAlwaysAuthorization() // LocationManager will callbackdidChange... once user responds
} else {
locationManager.startUpdatingLocation()
}
And add this delegate function to your class to be called by LocationManager once you're authorized:
// If we've been authorized to use location, start the processes, otherwise abort the operation
// since we can't proceed without locations
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status
{
case .authorizedAlways:
locationManager.startUpdatingLocation()
default:
// User denied access, handle as appropriate
}
}
Here's the code I use to instantiate / configure the locationManager:
lazy var locationManager: CLLocationManager = {
[unowned self] in
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = [a user setting in my app]
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.pausesLocationUpdatesAutomatically = false // So doesn't shut off if user stops to rest
_locationManager.activityType = .fitness
_locationManager.distanceFilter = Double([a user setting in my app])
return _locationManager
}()
This has been working for me so hopefully it will help.
I am working on watch heart beat app and I want when user heart rate critical then we will get his current location in(foreground, background and terminate) and send it to our server. Is there any way through which I get user location only at that position. I don't want to update his location every time.
To get user location you have to declare :
let locationManager = CLLocationManager()
in your controller.
Then, in viewDidLoad you have to request for location and initialize the CLLocationManager get process :
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
You will get location in CLLocationManagerDelegate :
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var location:CLLocationCoordinate2D = manager.location.coordinate
print("locations = \(location.latitude) \(location.longitude)")
}
In your info.plist you have to add NSLocationAlwaysUsageDescription and custom alert message to show while requesting for location.
cheers...
Hello i am using CLLocation to my app and i have initialise my CLLocationManager like this:
func initLocationManager(){
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
let authstate = CLLocationManager.authorizationStatus()
if(authstate == CLAuthorizationStatus.NotDetermined || authstate == CLAuthorizationStatus.Denied){
println("Not Authorised")
locationManager.requestWhenInUseAuthorization()
}
locationManager.startUpdatingLocation()
}
And i have also added the NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription key to my plist.
The first time i open my app i get the prompt message that my app want to access location and it has 2 buttons allow and dont allow. if i click on the dont allow button and close the app, when i opened it again i dont get the prompt message again.
How can i make this prompt message to appear each time the user opens the app? Thank you
Prompting alert each time is not a valid approach.
For an alternative you can show alert only in that case when Location Service is
disabled or "Dont Allow" initially.
Following code promt alert at first and a custom alert when Location Service is Disabled
import UIKit
import CoreLocation
class ViewController: UIViewController,CLLocationManagerDelegate {
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
initLocationManager()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func initLocationManager(){
let status = CLLocationManager.authorizationStatus()
if(status == CLAuthorizationStatus.NotDetermined) {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
let iosVersion = NSString(string: UIDevice.currentDevice().systemVersion).doubleValue
if iosVersion >= 8.0 {
//For Foreground
locationManager.requestWhenInUseAuthorization()
}
locationManager.startUpdatingLocation()
} else {
if(status != CLAuthorizationStatus.AuthorizedWhenInUse) {
var alert = UIAlertView(title: "Location", message: "Please turn on Location Services", delegate: nil, cancelButtonTitle: "Cancel")
alert.addButtonWithTitle("Open Setting")
alert.show()
/*Add Action on Open Setting alertbutton to directly open settings in iOS 8 and later
-> UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)*/
}
}
}
}
There is no way of doing it once the user declines, you have to show a dialog explaining to the user that he/she has to go to Settings and manually allow the functionality.
This message is asking for the permission to use GPS of your device and try to getting location of the device that's why GPS is much needed on. For second you don't want to show it you can make a condition with NSUseDefaults and store a key then don't call the method locationmanager startupdatinglocation. This is the only way to don't show it again otherwise it will show everytime.
I am trying to implement a basic map view and add a user's current location to the map as an annotation. I have added the requestwheninuse key to my info.plist and imported coreLocation.
In my view controller's did load method, I have the following:
locManager.requestWhenInUseAuthorization()
var currentLocation : CLLocation
if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse){
currentLocation = locManager.location
println("currentLocation is \(currentLocation)")
}
else{
println("not getting location")
// a default pin
}
I am getting the prompt re. permission to retrieve location. As this is happening I am getting my print saying not getting location, obviously because this runs before the user gets a chance to tap OK. If I elave the app and come back in I can retrieve the location and add it to the map. However, I want when the user taps OK the first time to be able to then grab the current location and add it to the map there and then. How can I achieve this? I have the following method for adding a pin:
func addPin(location2D: CLLocationCoordinate2D){
self.mapView.delegate = self
var newPoint = MKPointAnnotation()
newPoint.coordinate = location2D
self.mapView.addAnnotation(newPoint)
}
In order to do that, you need to implement the methoddidChangeAuthorizationStatus for your location manager delegate which is called shortly after CLLocationManager is initialized.
First, at the top of the file don't forget to add : import CoreLocation
To do that, in your class where you are using the location, add the delegate protocol. Then in the viewDidLoad method (or applicationDidFinishLaunching if you are in the AppDelegate) initialize your location manager and set its delegate property to self:
class myCoolClass: CLLocationManagerDelegate {
var locManager: CLLocationManager!
override func viewDidLoad() {
locManager = CLLocationManager()
locManager.delegate = self
}
}
Finally, implement the locationManager(_ didChangeAuthorizationStatus _) method in the body of your class that you declared previously, this method will be called when the status of the authorization is changed, so as soon as your user clicked the button. You can implement it like this:
private 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:
// If restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// If user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
Swift 4 - New enum syntax
For Swift 4, just switch the first letter of each enum case to lowercase (.notDetermined, .authorizedWhenInUse, .authorizedAlways, .restricted and .denied)
That way you can handle each and every case, wether the user just gave its permission or revoked it.