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.
The code for my CLLocationManagerDelegate was working fine before the last iOS update, however now it is crashing with the error 'NSInternalInconsistencyException', reason: 'Delegate must respond to locationManager:didUpdateLocations:'
This is the code for my delegate (note: start() is called from my ViewController):
class Location: NSObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
func start() {
locationManager.delegate = self
if locationManager.responds(to: #selector(CLLocationManager.requestAlwaysAuthorization)) {
locationManager.requestAlwaysAuthorization()
} else {
startLocationUpdates()
}
}
func startLocationUpdates() {
locationManager.allowsBackgroundLocationUpdates = true
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.activityType = .automotiveNavigation
locationManager.startUpdatingLocation()
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
startLocationUpdates()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// snip
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// snip
}
}
Not sure how you're initializing this class, setting delegates, etc., but this is the UserLocation class that I use. Compare it to yours.
import Foundation
import CoreLocation
var userLocation: UserLocation?
class UserLocation: NSObject, CLLocationManagerDelegate {
var launchLocationSet = false
override init() {
super.init()
setupLocationManager()
}
public var currentLocation: CLLocation!
private var locationManager = CLLocationManager()
private func setupLocationManager() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
currentLocation = locations.last! as CLLocation!
if !launchLocationSet {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "updateLocation"), object: nil, userInfo: nil)
launchLocationSet = true
}
}
func currentLatitude() -> CLLocationDegrees {
return currentLocation.coordinate.latitude
}
func currentLongitude() -> CLLocationDegrees {
return currentLocation.coordinate.longitude
}
}
Hi I'm making a program that gets the users location and puts an according annotation on the map. I started by writing all of the code in the View Controller and it gets the location perfectly. Below is the working code in the view controller.
class MapViewController: UIViewController, CLLocationManagerDelegate {
var annotation = MKPointAnnotation()
var userLocation: CLLocation?
#IBOutlet weak var mapView: MKMapView!
var locationManager:CLLocationManager!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineCurrentLocation()
}
func determineCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
userLocation = locations[0] as CLLocation
print("user latitude = \(userLocation?.coordinate.latitude)")
print("user longitude = \(userLocation?.coordinate.longitude)")
annotation.coordinate = CLLocationCoordinate2D(latitude: (userLocation?.coordinate.latitude)!, longitude: (userLocation?.coordinate.longitude)!)
annotation.title = "You"
mapView.addAnnotation(annotation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
}
However now when I try and recreate almost the exact same code in another swift file. didUpdateLocations never gets called. locationManager.startUpdatingLocation() does get called.
Below is my new swift file which I call from the View Controller. Is there any simple concept I'm missing here because I really don't see why this doesn't work.
import Foundation
import CoreLocation
class SendLocation: NSObject, CLLocationManagerDelegate {
var userLocation: CLLocation?
var locationManager:CLLocationManager!
func sendLocationPost() {
determineCurrentLocation()
}
func determineCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
print("WHY")
if CLLocationManager.locationServicesEnabled(){
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
userLocation = locations[0] as CLLocation
print("user latitude = \(userLocation?.coordinate.latitude)")
print("user longitude = \(userLocation?.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
}
}
I call it using :
let location = SendLocation()
location.sendLocationPost()`
in my View Controller
This happens because you are not keeping a reference to your SendLocation object.
Make SendLocation a property of your UIViewController.
For example, calling it from a static scope will not keep a reference.
WONT WORK:
static func sendLocation() {
let location = SendLocation()
location.sendLocationPost()
}
WILL WORK
let location = SendLocation()
func sendLocation() {
location.sendLocationPost()
}
I am developing an app using swift I want to fetch current location of the user and want to show in google map so I wrote my code in my class I have included all the functions and methods (i.e) added and imported frameworks core location and also updated plist but I can't able to fetch current location rotationally some time I get the location but some time it crashed.
Here is the code what I am tried.
import CoreLocation
class HomeViewController: UIViewController, CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
let location: CLLocation? = locationManager.location
let coordinate: CLLocationCoordinate2D? = location?.coordinate ----> App crossed this line then it will crashed
print(coordinate!)
print(coordinate!.latitude)
print(coordinate!.longitude)
strForCurLatitude = "\(coordinate!.latitude)"
strForCurLongitude = "\(coordinate!.longitude)"}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
print("User allowed us to access location")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while get location \(error)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.startUpdatingLocation()
let locationNew = locations.last
let coordinateNew: CLLocationCoordinate2D? = locationNew?.coordinate
strForCurLatitude = "\(coordinateNew!.latitude)"
strForCurLongitude = "\(coordinateNew!.longitude)"
strForLat=strForCurLatitude;
strForLong=strForCurLongitude;
print("User's Latitude is: \(Double(strForLat!)!)")
print("User's Longitude is: \(Double(strForLong!)!)")
self.locationManager.startUpdatingLocation()
}
Remove this from ViewDidLoad because you can not get location in viewDidLoad.
let location: CLLocation? = locationManager.location
let coordinate: CLLocationCoordinate2D? = location?.coordinate
print(coordinate!)
print(coordinate!.latitude)
print(coordinate!.longitude)
strForCurLatitude = "\(coordinate!.latitude)"
strForCurLongitude = "\(coordinate!.longitude)"
In CLLocationManager's following method best for getting location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
make a reference to CLLocationManager as variable, like this:
class HomeViewController {
var locationManager = CLLocationManager()
viewDidLoad() {
super.viewDidLoad()
}
// rest of code
}
in this case Location Manager should works properly.
I'm trying to get the user's location. To do so I have set the following property in the info.plist :
I have also added the following code in my viewDidLoad method as well as the function below. The problem is that the locationManager(manager, didUpdate....) function never gets called, I also never get prompted for permission to access location, even though I have removed and installed the app again. I am testing this on my iPad, not on the simulator. The didFailWithError function never gets called either.
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("UPDATING")
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
let latitude = locValue.latitude
let longitude = locValue.longitude
latitudeText = "\(latitude)"
longitudeText = "\(longitude)"
if let a = latitudeText, b = longitudeText {
print(a)
print(b)
self.locationManager.stopUpdatingLocation()
if (userAlreadyExist()) {
dispatch_async(dispatch_get_main_queue(), {
//self.performSegueWithIdentifier("segueWhenLoggedIn", sender: self)
self.performSegueWithIdentifier("showCamera", sender: self)
// self.performSegueWithIdentifier("showTabBarController", sender: self)
})
}
else {
dispatch_async(dispatch_get_main_queue(), {
self.performSegueWithIdentifier("segueWhenLoggedOut", sender: self)
})
}
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print(error.localizedDescription)
}
EDIT :
I have added the following snippet of code :
if CLLocationManager.locationServicesEnabled() {
print("yes")
}
else {
print("no")
}
it returns yes. I have also checked on my device, locationServices are enabled, the app is listed there, however all the other apps have "While Using", "Never" or "Always" written next to them, mine doesn't have anything written.
where do you start the location update ? for example:
//location manager
lazy var locationManager: CLLocationManager = {
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
_locationManager.activityType = . automotiveNavigation
_locationManager.distanceFilter = 10.0 // Movement threshold for new events
_locationManager.allowsBackgroundLocationUpdates = true // allow in background
return _locationManager
}()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation() // start location manager
}
here is a working conroller code:
also important to to set up Custom iOS Target Properties.
Add these two lines to the Info.plist:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
//
// ViewController.swift
// LocationTest2
import UIKit
import CoreLocation
class ViewController: UIViewController {
//location manager
lazy var locationManager: CLLocationManager = {
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
_locationManager.activityType = . automotiveNavigation
_locationManager.distanceFilter = 10.0 // Movement threshold for new events
// _locationManager.allowsBackgroundLocationUpdates = true // allow in background
return _locationManager
}()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//allow location use
locationManager.requestAlwaysAuthorization()
print("did load")
print(locationManager)
//get current user location for startup
// if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
// }
}
}
// MARK: - CLLocationManagerDelegate
extension ViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
print("**********************")
print("Long \(location.coordinate.longitude)")
print("Lati \(location.coordinate.latitude)")
print("Alt \(location.altitude)")
print("Sped \(location.speed)")
print("Accu \(location.horizontalAccuracy)")
print("**********************")
}
}
}
for me worked:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locationManager update")
}
instead of this
private func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locationManager update")
}
Also make sure you have set the custom location to simulator as by default it will be None ...
In the simulator go to Debug -> Location-> .
It should also be noted that locationManager:didFailWithError: will run if the location is not set in the simulator, as you'd expect.
A very subtle cause for this bug in Swift code. Don't define the delegate call didUpdateLocations as private or fileprivate. The location manager won't be able to find or call it if you do.
Good:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
Bad:
fileprivate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
I looked for answers high and low. This is the only thing that worked for me:
Change the func for didUpdateToLocation to:
func locationManager(_: CLLocationManager, didUpdateToLocation newLocation: CLLocation!,fromLocation oldLocation: CLLocation!) {
}
Note the subtle change of "as _: CLLocationManager", instead of "manager: CLLocationManager".
You should call startUpdatingLocation() inside the didDetermineState delegate method
if CLLocationManager.authorizationStatus() != .authorizedWhenInUse {
locationManager.requestWhenInUseAuthorization()
}else{
locationManager.startUpdatingLocation()
}
//later
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .authorizedWhenInUse:
manager.startUpdatingLocation()
break
case .authorizedAlways:
manager.startUpdatingLocation()
break
case .denied:
//handle denied
break
case .notDetermined:
manager.requestWhenInUseAuthorization()
break
default:
break
}
}
I was using AppDelegate as CLLocationManagerDelegate instead of ViewController like in most examples, this was the code I had
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var locationManager: CLLocationManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
return true
}
}
and I was trying to get updates when exit event was called
// AppDelegate.swift
extension AppDelegate: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
if !CLLocationManager.locationServicesEnabled() {
return
}
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
return
}
}
But this didn't work. Instead I had to move the configuration of locationManager to AppDelegate class, and only start the updates in AppDelegate extension.
Like this
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var locationManager: CLLocationManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
/* This was the catch, it needs to be here instead of inside extension */
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager?.distanceFilter = kCLDistanceFilterNone
self.locationManager?.allowsBackgroundLocationUpdates = true
/* */
return true
}
}
extension AppDelegate: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
if !CLLocationManager.locationServicesEnabled() {
return
}
/* Only do this here */
manager.startUpdatingLocation()
return
}
}
Then it started working, and sending continuous location updates. Also I tested on simulator.
Make sure the startUpdatingLocation method gets called on the main thread, like so:
DispatchQueue.main.async { [weak self] in
self?.locationManager.startUpdatingLocation()
}