Can't Determine When User Doesn't Allow Location Services - ios

I'm requesting the user to turn on location services. I want to know when a user clicks Don't Allow so I can handle some notifications. However, the didFailWithError or didChangeAuthorizationStatus methods are not being called when I click Don't Allow. I know this nothing is printed in the logger. I've attached a code sample. What am I doing wrong and how do I fix this. Thanks.
import UIKit
import CoreLocation
class AwesomeViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
let authorizationStatus = CLLocationManager.authorizationStatus()
if(authorizationStatus == .AuthorizedWhenInUse || authorizationStatus == .AuthorizedAlways) {
// authorization is good
} else {
locationManager.requestWhenInUseAuthorization()
}
}
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
print(status)
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
print(error.localizedDescription)
}
}

the didFailWithError or didChangeAuthorizationStatus methods are not being called
Those are delegate methods. Your location manager does not have any delegate - certainly it does not have you (the AwesomeViewController instance) as a delegate. So it is not going to call those methods, ever. You need to set the location manager's delegate (in this case you would set it to self).

Related

Location Service as a Singleton in Swift, get stuck on "When In Use"

I'm programming an app that needs "Always location" and I decided to use a Singleton to keep tracking active since I need most of the time the location services even in the background.
When I run the application on my iPhone, the console says that the location service is in "When In Use" mode and my protocol don't get the location updates from the LocationManager.
What's wrong with my Singleton (I'm a Swift newbie please be clear in your answers.
Is it a good idea to use a Singleton for Location Services ?
LocationService.swift (UPDATED)
import Foundation
import CoreLocation
protocol LocationServiceDelegate {
func onLocationUpdate(location: CLLocation)
func onLocationDidFailWithError(error: Error)
}
class LocationService: NSObject, CLLocationManagerDelegate {
public static let shared = LocationService()
var delegate: LocationServiceDelegate?
var locationManager: CLLocationManager!
var currentLocation: CLLocation!
private override init() {
super.init()
self.initializeLocationServices()
}
func initializeLocationServices() {
self.locationManager = CLLocationManager()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestAlwaysAuthorization()
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
case .notDetermined:
self.locationManager.requestAlwaysAuthorization()
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("User choosed locatiom when app is in use.")
default:
print("Unhandled error occured.")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.currentLocation = locations.last!
locationChanged(location: currentLocation)
}
private func locationChanged(location: CLLocation) {
guard let delegate = self.delegate else {
return
}
delegate.onLocationUpdate(location: location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
self.locationManager.stopUpdatingLocation()
locationFailed(error: error)
}
private func locationFailed(error: Error) {
guard let delegate = self.delegate else {
return
}
delegate.onLocationDidFailWithError(error: error)
}
}
Then I initialize the singleton :
AppDelegate.swift
let locationService = LocationService.shared
Then my View conforms to my protocol :
ViewController.swift
extension ViewController: LocationServiceDelegate {
func onLocationUpdate(location: CLLocation) {
print("Current Location : \(location)")
}
func onLocationDidFailWithError(error: Error) {
print("Error while trying to update device location : \(error)")
}
}
Yes, You can use singleton for your purpose. Few things you can check with your implementation:
locationManager.pausesLocationUpdatesAutomatically = false.
enable background modes for location updates.
Switch to significant location updates when the app moves to background.
Is it a better way to send notifications to all viewControllers to pass the CLLocation object or its better to conform to my protocol in every controllers ?

how to execute an action after allow button is pressed in the location access permission?

I am making an app that uses coordinate from GPS, before implementing it, we have to ask permission to the user like the picture above.
I want to make if the user tap "allow" at that alert, then activateGPSToSearchCoordinate() is trigerred, but if 'don't allow' is tapped then I don't want to do anything.
this is my code at the moment, and it doesn't work properly
class LocationManager: NSObject {
let manager = CLLocationManager()
var didGetLocation: ((Coordinate?) -> Void)?
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestLocation()
}
func getPermission() -> CLAuthorizationStatus {
// to ask permission to the user by showing an alert (the alert message is available on info.plist)
if CLLocationManager.authorizationStatus() == .notDetermined {
manager.requestWhenInUseAuthorization()
return .notDetermined
} else if CLLocationManager.authorizationStatus() == .denied {
return .denied
} else if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
return .authorizedWhenInUse
} else {
return .notDetermined
}
}
}
I will use that class in the view controller method like below, especially that getPermission()
func getCoordinate() {
let coordinateAuthorizationStatus = locationManager.getPermission()
if coordinateAuthorizationStatus == .authorizedWhenInUse {
activateGPSToSearchCoordinate()
} else if coordinateAuthorizationStatus == .denied {
showAlertSetting()
}
}
at the moment, if that permission is triggered for the very first time...
either the user tap 'Allow' or 'don't Allow' the CLAuthorizationStatus will always be .notDetermined
so the activateGPSToSearchCoordinate() , will never be triggered.
so I need to to activate activateGPSToSearchCoordinate() only after the 'Allow' at that alert is pressed
how to solve this problem?
Read more about CLLocationManagerDelegate, there is delegate methods for Success and failure.
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
GlobalObjects.shared.latitude = locations[0].coordinate.latitude
GlobalObjects.shared.longtitude = locations[0].coordinate.longitude
GlobalObjects.shared.locationOBJ = locations[0]
print(GlobalObjects.shared.latitude, GlobalObjects.shared.longtitude)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
What about CLLocationManagerDelegate? Did you try this?
func locationManager(CLLocationManager, didUpdateLocations: [CLLocation])
//Tells the delegate that new location data is available.
func locationManager(CLLocationManager, didFailWithError: Error)
//Tells the delegate that the location manager was unable to retrieve a location value.
func locationManager(CLLocationManager, didFinishDeferredUpdatesWithError: Error?)
//Tells the delegate that updates will no longer be deferred.
func locationManager(CLLocationManager, didUpdateTo: CLLocation, from: CLLocation)
//Tells the delegate that a new location value is available.
Implement CLLocationManagerDelegate, on a class/view controller from where you are calling func getCoordinate().
class TestViewController: CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
}
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// get location coordinate
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// handle error, if any kind of error occurs
}
Here are nice tutorial with example, may help you, in implementing CLLocationManagerDelegate :
https://developer.apple.com/documentation/corelocation/cllocationmanager
https://www.raywenderlich.com/160517/mapkit-tutorial-getting-started
https://www.appcoda.com/tag/mapkit/
http://www.techotopia.com/index.php/A_Swift_Example_iOS_8_Location_Application

Delegate must respond to locationManager:didFailWithError: even though implemented didFailWithError method

For some reason Xcode thinks I'm not implementing didFailWithError method of the CLLocationManagerDelegate protocol
I fail to see what I'm doing wrong, as I literally copied from another SO post that said this didFailWithErrormethod was updated for Swift 3. So I don't understand why Xcode thinks I'm not implementing didFailWithError
Any insight would be greatly appreciated!
Code
class OptionsViewController: UIViewController,
CLLocationManagerDelegate {
var locationManager: CLLocationManager = CLLocationManager()
override func viewDidLoad() {
//Ask user for location
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
//Use users current location if no starting point set
if CLLocationManager.locationServicesEnabled() {
if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse
|| CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways {
locationManager.requestLocation()
}
else{
locationManager.requestWhenInUseAuthorization()
}
}
else{
//Alert user to open location service, bra bra bra here...
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error)")
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("didChangeAuthorization")
if status == CLAuthorizationStatus.authorizedWhenInUse
|| status == CLAuthorizationStatus.authorizedAlways {
locationManager.requestLocation()
}
else{
//other procedures when location service is not permitted.
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("Did update location called")
// let locValue:CLLocationCoordinate2D = manager.location!.coordinate
// print("locations = \(locValue.latitude) \(locValue.longitude)")
if locations.first != nil {
print("location:: (location)")
}
}
Aaaandd I realized it's b/c I needed to use an Error of a specific type (specifically Swift.Error)
This is the right method declaration for didFailWithError:
func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {

Swift Not Reaching LocationManager Function

I am trying to get my location in order to check how far I am from a certain longitude and latitude.
For some reason the program never reaches the LocationManager Function where I want to grab my coordinates.
I have set up the correct properties in info.plist and the app does ask for permission.
I added a function to check for errors and got:
didFailWithError Error Domain=kCLErrorDomain Code=0 "(null)"
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager:CLLocationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
print("location on")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("REACHED FUNCTION")
}
}
If you are using iOS 8.0 and later you need to ask for permission before starting to update location. You can use following code for that
if #available(iOS 8.0, *) {
locationManager.requestWhenInUseAuthorization()
}
Above code will go in viewDidLoad. And implement following delegate method
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .AuthorizedWhenInUse:
locationManager.startUpdatingLocation()
break
case .AuthorizedAlways:
locationManager.startUpdatingLocation()
break
default:
break
}
}

CLLocationManagerDelegate methods not being called in Swift code

I'm trying to create a simple app that finds out the region someone is in, but I'm stuck because none of the CLLocationManagerDelegate methods are called when the app runs and finds locations.
In case it's relevant I'm also not seeing the dialog asking that I give the app permission to use location.
Here's what I have so far -
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var locationLabel : UILabel!
var locationManager = CLLocationManager()
let geocoder = CLGeocoder ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
override func viewDidDisappear(animated: Bool) {
locationManager.stopUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
geocoder.reverseGeocodeLocation(locations.last as CLLocation, completionHandler: {(placemark, error) in
if (error != nil) {
println("Error")
} else {
let pm = placemark.first as CLPlacemark
println(pm)
}
})
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Epic Fail")
}
}
I've put in breakpoints so I know the code is never called. I have gone in and manually turned on location services for the app while it's been running too.
try calling
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!)
instead of the delegate call you are making now...
I had problems with:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
aswell. But using the above delegate call fixed my issue.
Edit (didn't read the question properly...)
You never ask for permission, which is why you don't get a popup. Call the following:
locationManager.requestWhenInUseAuthorization()
This is quite an important step, and your app won't work if you haven't asked the user for authorization of your app
It is also important that you add NSLocationWhenInUseUsageDescription to your .plist file if running iOS8.
Screenshot:
The following answer question and answer gave me the solution I needed.
CLLocationManager authorization issue iOS 8
Ultimately if you're running on iOS then you must add the key
NSLocationAlwaysUsageDescription (when requesting location data to be always available)
OR
NSLocationWhenInUseUsageDescription (when requesting to use location data only when your app is running)
If you do not do this the location services access will be denied by default for your app and the user will not be prompted to allow it.
private let loctionManager = CLLocationManager() // Here
class YourViewController: ViewController {
loctionManager.delegate = self
loctionManager.requestWhenInUseAuthorization()
}
extension YourViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if (status == .authorizedWhenInUse) {
// do something
}
}
}
.plist
Property List
Key: Privacy - Location When In Use Usage Description
Type: String
Value: Use to add a location to your rap.
OR
Source code
<key>NSLocationWhenInUseUsageDescription</key>
<string>Use to add a location to your rap.</string>
If you tried all other answers and still having issues, just make sure CLLocationManager.init() and startUpdatingLocation are called on the same thread, preferably main thread.
I personally had a project and took me a while to figure out!

Resources