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

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

Related

Open SwiftUI view only if location permission granted

I have a view (say V) in which a user answers a few questions and their location is recorded. However, the answers only make sense with the user's location.
So what I want is that when the user clicks on a button on the parent view, it takes them to V and immediately asks them for the location permission. If they accept, they can continue on to answer the questions, but if they deny, they navigate back to the parent screen.
I know I can navigate back to the parent screen with self.presentation.wrappedValue.dismiss().
But how do I know when the user has accepted or denied the permission since requestWhenInUseAuthorization() is an asynchronous function?
I'm following this tutorial on getting a user's location on iOS with Swift.
Code for my LocationService:
import CoreLocation
protocol LocationServiceDelegate {
func didFetchCurrentLocation(_ location: GeoLocation)
func fetchCurrentLocationFailed(error: Error)
}
class LocationService: NSObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var delegate: LocationServiceDelegate
init(delegate: LocationServiceDelegate) {
self.delegate = delegate
super.init()
self.setupLocationManager()
}
private func setupLocationManager() {
if canUseLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
}
func requestLocation() {
if canUseLocationManager() {
print(CLAuthorizationStatus.self)
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
}
func requestPermission() {
locationManager.requestWhenInUseAuthorization()
}
private func canUseLocationManager() -> Bool {
return CLLocationManager.locationServicesEnabled()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
if let location = locations.last {
let geoLocation = GeoLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
delegate.didFetchCurrentLocation(geoLocation)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
delegate.fetchCurrentLocationFailed(error: error)
}
deinit {
locationManager.stopUpdatingLocation()
}
}
struct GeoLocation {
var latitude: Double
var longitude: Double
}
CLLocationManagerDelegate has also the following method:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
}
This method is called every time the authorization status changed. I would also like to recommend you implementing your LocationService as an ObservableObject instead of using delegate approach.

Getting location on real device not working

I'm trying to get the user location, running on the simulator, I get the default address, but atleast I know it is working.
I tried to run it on my device but it didn't work.
I try to look for a solution before writing this question but couldn't find something that work for me.
This is my code:
LocationManager:
class LocationManager: NSObject, CLLocationManagerDelegate {
static let shared = LocationManager()
var locationManager: CLLocationManager!
var geoCoder = CLGeocoder()
var callBack:((String)->())?
override init() {
super.init()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .other
locationManager.requestWhenInUseAuthorization()
}
func checkIfLocationIsEnabled() -> Bool{
return CLLocationManager.locationServicesEnabled()
}
func getUserLocation(){
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let userLocation: CLLocation = locations[0] as CLLocation
geoCoder.reverseGeocodeLocation(userLocation) { (placemarks, err) in
if let place = placemarks?.last{
self.callBack?(place.name!)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
This is my getLocation (just calling the getUserLocation and setting the address I get from the callback):
func getLocation(_ label: UILabel) -> String{
guard let comment = self.mView.addCommentTextField.text else { return ""}
LocationManager.shared.getUserLocation()
var addressString = ""
LocationManager.shared.callBack = { address in
DispatchQueue.main.async {
label.text = "\(address), \(comment)"
addressString = address
}
}
return addressString
}
This is how I call getLocation:
self.mView.inLabel.isHidden = false
self.getLocation(self.mView.inLabel)
Actually looking closer at your code, I see that you are asking permissions like this:
locationManager.requestWhenInUseAuthorization()
But requestWhenInUseAuthorization() is asynchronous call, you need to wait for user response before you can use any location services:
When the current authorization status is CLAuthorizationStatus.notDetermined, this method runs asynchronously and prompts the user to grant permission to the app to use location services.
(source)
Also notice that it will only work if status is notDetermined. Any other status would not trigger it. So firstly:
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
// already authorized, can use location services right away
}
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
// wait, don't call any location-related functions until you get a response
}
If location permissions are set to anything else, no point to ask for them.
And then your class is already CLLocationManagerDelegate, so:
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
// do something with new status, e.g.
if status == .authorizedWhenInUse {
// good, now you can start accessing location data
}
// otherwise, you can't

Double segue behavior when user authorizes location use

In my iOS application I’m requesting the user’s permission to obtain the device’s location. On my main ViewController I have a navigation bar button that when tapped, it will ask the user for permission for when in use. If the user taps OK, it will then be send to the view controller that displays the local data. If the user taps Cancel then nothing happens. I also have a pop up for when and if the user taps again on the location button to be redirected to the settings to authorize location use if previously cancelled.
The app works as I intended in the simulator but when used on a device, when the user taps OK to allow location use, it segues to the local View Controller but it does so 2 or 3 times consecutively.
The segue goes from the main view controller to the local view controller and it requests permissions from the button tap using an IBAction.
The location information is obtained in the main view controller and passed to the local controller. The local controller displays everything as it is intended.
How can I prevent this double or triple segue to the same View Controller?
Below is my code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toLocal" {
let destination = segue.destination as! LocalViewController
destination.latitude = latitude
destination.longitude = longitude
}
}
//MARK: - Location Manager Methods
#IBAction func LocationNavBarItemWasTapped(sender: AnyObject) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
let status = CLLocationManager.authorizationStatus()
switch status {
case .restricted, .denied:
showLocationDisabledPopUp()
return
case .notDetermined:
// Request Access
locationManager.requestWhenInUseAuthorization()
case .authorizedAlways:
print("Do Nothing: authorizedAlways")
case .authorizedWhenInUse:
self.performSegue(withIdentifier: "toLocal", sender: nil)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("LocationManager failed with error \(error)")
}
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if (status == CLAuthorizationStatus.denied) {
showLocationDisabledPopUp()
}
}
As superpuccio already stated the main issue is that the didUpdateLocations delegate function is called multiple times. I also do not know why you are checking the authorizationStatus in the didUpdateLocations function since at that point it is already clear that the user allowed location access. In my opinion the function should look something like this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// check for a location that suits your needs
guard let location = locations.last, location.horizontalAccuracy > 0 else { return }
// prevent the manager from updating the location and sending more location events
manager.delegate = nil
manager.stopUpdatingLocation()
// update the local variables
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
// perform the segue
performSegue(withIdentifier: "toLocal", sender: nil)
}
Since there are some more issues like starting location updates before knowing the actual authorization status I'll provide a full solution like I'd do it. Feel free to ask if anything is unclear:
class ViewController: UIViewController {
var latitude: CLLocationDegrees?
var longitude: CLLocationDegrees?
lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
return locationManager
}()
#IBAction func getLocation(_ sender: UIBarButtonItem) {
locationManager.delegate = self
checkAuthorizationStatus()
}
private func checkAuthorizationStatus(_ status: CLAuthorizationStatus? = nil) {
switch status ?? CLLocationManager.authorizationStatus() {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
default:
showLocationDisabledPopUp()
}
}
func showLocationDisabledPopUp() {
// your popup code
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// your segue code
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkAuthorizationStatus(status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last, location.horizontalAccuracy > 0 else { return }
manager.delegate = nil
manager.stopUpdatingLocation()
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
performSegue(withIdentifier: "toLocal", sender: nil)
}
}
The issue here is that you are performing a segue in
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
that can be fired multiple times (each time a new location comes from the CLLocationManager). There are several ways to solve this, but in order to make you change as little as possible I suggest this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
let status = CLLocationManager.authorizationStatus()
switch status {
case .restricted, .denied:
showLocationDisabledPopUp()
return
case .notDetermined:
// Request Access
locationManager.requestWhenInUseAuthorization()
case .authorizedAlways:
print("Do Nothing: authorizedAlways")
case .authorizedWhenInUse:
//-- MODIFIED HERE --
locationManager.stopUpdatingLocation()
locationManager = nil
Dispatch.main.async {
self.performSegue(withIdentifier: "toLocal", sender: nil)
}
}
}
Let me know if it helps, otherwise we can change a little more your code to solve this simple issue.
EDIT: just so you know: it would be better to perform the segue in
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus)
when the status is kCLAuthorizationStatusAuthorized or kCLAuthorizationStatusAuthorizedAlways or kCLAuthorizationStatusAuthorizedWhenInUse depending on your needs.

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) {

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

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).

Resources