Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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


}
like image 201
Ahad Sheriff Avatar asked Dec 25 '22 04:12

Ahad Sheriff


1 Answers

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

like image 131
Shadow Of Avatar answered May 04 '23 17:05

Shadow Of