Ahad Sheriff Ahad Sheriff - 5 months ago 15
Swift Question

Error: 'Delegate must respond to locationManager:didUpdateLocations:' when collecting user location

So I want to collect user location when a user tap's a button, and use that location later on in my app. With my current implementation, when the button is tapped, the user should be prompted to allow location to be shared and then they should be logged in to the app (anonymously via Firebase in case you are wondering), and the users location information should print to the console. The prompt works, however after the allow location button is hit, my application terminates

due to uncaught exception 'NSInternalInconsistencyException'


and the reason is that the
'Delegate must respond to locationManager:didUpdateLocations:'


Which is strange because I do have a
didUpdateLocations:
function in my code.

I have no idea what is going on, and any help is appreciated. My ViewController code is below, thanks!

/*
* Copyright (c) 2016 Ahad Sheriff
*
*/

import UIKit
import Firebase
import CoreLocation

class LoginViewController: UIViewController {

// MARK: Properties
var ref: FIRDatabaseReference! // 1
var userID: String = ""

var locationManager = CLLocationManager()

override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference() // 2
}

@IBAction func loginDidTouch(sender: AnyObject) {

//ask user to enable location services
locationManager.requestWhenInUseAuthorization()

if CLLocationManager.locationServicesEnabled() {
//collect user's location
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.requestLocation()
locationManager.startUpdatingLocation()

let location = self.locationManager.location

var latitude: Double = location!.coordinate.latitude
var longitude: Double = location!.coordinate.longitude

print("current latitude :: \(latitude)")
print("current longitude :: \(longitude)")

//Log in to application anonymously using Firebase
FIRAuth.auth()?.signInAnonymouslyWithCompletion() { (user, error) in
if let user = user {
print("User is signed in with uid: ", user.uid)
self.userID = user.uid
} else {
print("No user is signed in.")
}

self.performSegueWithIdentifier("LoginToChat", sender: nil)

}

}

else {
print("Unable to determine location")
}

}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
let navVc = segue.destinationViewController as! UINavigationController // 1
let chatVc = navVc.viewControllers.first as! ChatViewController // 2
chatVc.senderId = userID // 3
chatVc.senderDisplayName = "" // 4
}

func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
//--- CLGeocode to get address of current location ---//
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in

if (error != nil)
{
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}

if placemarks!.count > 0
{
let pm = placemarks![0] as CLPlacemark
self.displayLocationInfo(pm)
}
else
{
print("Problem with the data received from geocoder")
}
})

}


func displayLocationInfo(placemark: CLPlacemark?)
{
if let containsPlacemark = placemark
{
//stop updating location
locationManager.stopUpdatingLocation()

let locality = (containsPlacemark.locality != nil) ? containsPlacemark.locality : ""
let postalCode = (containsPlacemark.postalCode != nil) ? containsPlacemark.postalCode : ""
let administrativeArea = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea : ""
let country = (containsPlacemark.country != nil) ? containsPlacemark.country : ""

print(locality)
print(postalCode)
print(administrativeArea)
print(country)
}

}


func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
print("Error while updating location " + error.localizedDescription)
}


}

Answer

First, add explicitly that you confirm CLLocationManagerDelegate:

class LoginViewController: UIViewController, CLLocationManagerDelegate

Second, set up delegate property for CLLocationManager:

override func viewDidLoad() {
    super.viewDidLoad()
    locationManager.delegate = self
    ref = FIRDatabase.database().reference()
}

Third, in CLLocationManagerDelegate doc I see different from your declaration of didUpdateLocations and didFailWithError method:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
func locationManager(_ manager: CLLocationManager, didFailWithError error: NSError)

Also, you have few other issues in code. They are all about unsafe unwrapping. You should not use unsafe unwrapping ! everywhere. Doing this you are killing optionals philosophy. Doing right thing, on other hand, can drastically increase stability of your app. In loginDidTouch function make following corrections:

var latitude: Double? = location?.coordinate.latitude
var longitude: Double? = location?.coordinate.longitude

When you call this function first time, your location not determined yet (it will be determined asynchronously, you will get location in delegate method), thats why you have fatal error when used force unwrapp. In didUpdateLocations function:

if let pm = placemarks?.first
{
    self.displayLocationInfo(pm)  
}

correction of this part of code is shorter then your original code, and prevents you from two possible crashes at one time - when placemarks is nil and when placemarks is not nil, but empty array