handling location permissions instantaneously in swift - ios

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.

Related

My iOS application does not update the location in background

I have an iOS application developed in Swift. My app is currently in Swift 2 but I am using Xcode 8 with Swift 3. The app is configured to use the legacy swift language version.
Until recently the app was working correctly.
The app asks for the correct rights for always use the location and the autorisation is correctly set to always.
I renewed the signing identity for the production app and the app stopped to be notified on a location update but was still working in development mode (launched from xcode).
Now I revoked and renew the production and development certificate and the app does not update the location while in background whereas the autorisation is set to always.
The app is correctly installed so I guess that the certificates are okay but I don't understand why the location is not updated in background.
I run the app on an iPhone 7 with IOS 10.2 and xcode automatically manage signing.
Here is my location manager configuration:
public class LocationManager : NSObject, ModuleManager, CLLocationManagerDelegate {
/// The core location manager
let coreLocationManager: CLLocationManager
public var datas: JSONable? {
get {
return LocationDatas(locations: self.locations)
}
set {
self.locations = newValue == nil ? [Location]() : newValue as? [Location]
}
}
/// The list of locations to send
private var locations: [Location]?
/// The last location
public var lastLocation: Location? {
return self.locations?.last
}
public override init() {
self.coreLocationManager = CLLocationManager()
if #available(iOS 9.0, *) {
self.coreLocationManager.allowsBackgroundLocationUpdates = true
}
// The accuracy of the location data.
self.coreLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
// The minimum distance (measured in meters) a device must move horizontally before an update event is generated.
self.coreLocationManager.distanceFilter = 500; // meters
self.locations = [Location]()
super.init()
self.coreLocationManager.delegate = self
self.locationManager(self.coreLocationManager, didChangeAuthorizationStatus: CLLocationManager.authorizationStatus())
}
// MARK: - CLLocationManager Delegate
public func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
NSLog("location update")
guard locations.count > 0 else {
NSLog("Module Location -- no location available")
return
}
// Add all location waiting in the list to send
self.locations?.appendContentsOf(locations.map { Location(cllocation: $0) })
SDKManager.manager?.sendHeartbeat()
}
public func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch CLLocationManager.authorizationStatus() {
case .NotDetermined:
if #available(iOS 8.0, *) {
self.coreLocationManager.requestAlwaysAuthorization()
} else {
self.coreLocationManager.startUpdatingLocation()
}
case .Denied, .Restricted:
NSLog("Module Location -- access denied to use the location")
case .AuthorizedAlways:
NSLog("AuthorizedAlways")
self.coreLocationManager.startUpdatingLocation()
//self.coreLocationManager.startMonitoringSignificantLocationChanges()
default:
break
}
}
public func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
NSLog("Module Location -- error : \(error)")
}
}
The locationManager function is not called in background.
Here is my info.plist:
Here is the authorization on the phone:
The little location arrow is always there but no location update is logged.
I checked your code and it seems to be fine, revise if you have done these required settings
Enable location updates in Background mode
Add NSLocationAlwaysUsageDescription in your info.plist
If you did not do 1st point you app would have crashed but if did not do 2nd point your code will go through but you will never get updates.
Update:
It seems your LocationManager object is released in ARC. Can you try changing your LocationManager class to Singleton by added
static let sharedInstance = LocationManager()
And accessing LocationManager in your code like this
LocationManager.sharedInstance
You don't need to use App background Refresh just for Location update in Background. (It can be used for other maintenance work like DB cleaning, uploading, downloading, etc. while charging)
While initializing coreLocationManager, set the following properties as well
// It will allow app running location updates in background state
coreLocationManager.allowsBackgroundLocationUpdates = true
// It will not pause location automatically, you can set it true if you require it.
coreLocationManager.pausesLocationUpdatesAutomatically = false

CLLocationManager starts update before user authorization Swift

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

App not asking for permission to access location, popup stays in background

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.

Cannot keep GPS active with app in background, or read location with app in foreground or background

The app pertinent to this question is meant to track user location with a high degree of precision, including GPS coordinates and accelerometer readings, for a time period of 30 minutes, even if the user has pressed the sleep button.
To this ends, the plist file and app capabilities settings have been changed to reflect the reason for always on navigation access and to enable background processes for the provision of location based services.
The app does ask the user for GPS permissions when it is run, and if they are granted, the activation of this view controller (the view controller that contains the following code) does cause the GPS/Navigation icon to display on an iPhone.
The problem is, so far none of the four "print" commands seen below result in any printed messages, and so the "newLocation" and "myLocation" variables both do not yield any data. If this code is remotely close to being able to serve the purpose outlined in the first sentence, then the question is "How can it be fixed?". If this is a bad way to accomplish the goal, then a better answer would explain how this should be done.
import UIKit
import CoreMotion
import MapKit
import CoreLocation
class ActiveSession: UIViewController, CLLocationManagerDelegate {
lazy var locationManager: CLLocationManager! = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
return manager
}()
func locationManager(_manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
let myLocation:CLLocationCoordinate2D=CLLocationCoordinate2DMake(oldLocation.coordinate.latitude, oldLocation.coordinate.longitude)
print(myLocation)
if UIApplication.shared.applicationState == .active {
print("at least it's active at all")
} else {
print(newLocation)
print("it's active when the app isn't")
}
}
func getPermission () {
locationManager = CLLocationManager()
switch CLLocationManager.authorizationStatus() {
case .denied, .restricted:
return
case .notDetermined:
locationManager!.requestAlwaysAuthorization()
break
case .authorizedAlways, .authorizedWhenInUse:
break
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.getPermission()
locationManager = CLLocationManager()
locationManager!.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.startUpdatingLocation()
}
}
You’re creating new CLLocationManager instances in three places—the lazy locationManager initializer, getPermission, and viewDidLoad—and in the latter two of those, you’re neither setting the desired accuracy nor the delegate. Delete the locationManager = CLLocationManager() lines and you should have better results.
As ohr pointed out, an additional permission was required. The following line was added to the lazy locationManager initializer to rectify this omission:
manager.allowsBackgroundLocationUpdates = true
In accordance with Noah and Paulw11's advice, the getPermission function was deleted, and the line:
locationManager = CLLocationManager()
was removed from viewDidLoad, as there was a great deal of redundancy present. This alone did not fix the problems, but adding logic to the locationManager func so that recent map data was stored to an array, like so:
let annotation = MKPointAnnotation()
annotation.coordinate = newLocation.coordinate
locations.append(annotation)
while locations.count > 100 {
let annotationToRemove = locations.first!
locations.remove(at: 0)
}
resulted in working code. This solution was derived from code found here: https://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial
(Thanks, Ray)
Ultimately the code functioned in the foreground and background without removing anything from the class or implementing anything in appdelegate.

How to execute a task after location permission granted ios

I'm trying to set up an on boarding where I'm asking the user for a few permissions including: Location, Notifications and Camera. I have 3 different View Controllers set up, each asking for one of the permissions and explaining why. On each of the view controllers I have a button at the bottom that says "Grant Permission".
When the user clicks the button I want the permission dialogue to pop up, and once the user clicks allow I want to transition to the next view controller.
Here is what I have right now:
class OnboardingStep2:UIViewController{
override func viewDidLoad() {
self.view.backgroundColor = StyleKit.orangeWhite()
}
#IBAction func getPermission(sender: AnyObject) {
dispatch_sync(dispatch_get_main_queue()) {
let locManager = CLLocationManager()
locManager.requestAlwaysAuthorization()
}
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.Authorized) {
self.performSegueWithIdentifier("goToStep3", sender: self)
}
}
}
I've tried using dispatch to queue up the tasks, but when using async the permission dialogue pops up and then immediately it closes because the authorization check is run (I'm assuming). Using dispatch_sync, the dialogue is never shown.
What is the best way to do this, I want the permission dialogue to pop up first and once the user clicks allow i want to segue.
Conform to the CLLocationManagerDelegate
Then call this:
Swift 3.0
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
manager.requestLocation()
case .authorizedAlways, .authorizedWhenInUse:
// Do your thing here
default:
// Permission denied, do something else
}
}
Swift 2.2
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
manager.requestLocation()
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Do your thing here
default:
// Permission denied, do something else
}
}
Swift 5
Implement CLLocationManagerDelegate
and this function:
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// User has not yet made a choice
case .denied:
// User has explicitly denied authorization
case .restricted:
// This application is not authorized to use location services.
case .authorized, .authorizedAlways, .authorizedWhenInUse:
// User has granted authorization
default:
// Other
}
}

Resources