Fatal error when unwrapping CLLocation Location Coordinate - ios

I currently have a test application to teach myself Core Location. It is an app with a button which will either show your location when you press a button, or an alert when location services are off.
I get a fatal error (unexpectedly found nil when unwrapping option value) When location services are on and I attempt to println the string. I cannot find anywhere in the documentation where the CLLocationManager.location.coordinate returns an optional value. I will include the code snippet. Thanks!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
}
}
#IBAction func testLocation(sender: AnyObject) {
switch CLLocationManager.authorizationStatus() {
case .AuthorizedAlways, .AuthorizedWhenInUse:
printCurrentCoordinates()
case .Restricted, .Denied:
disabledLocationSeriveAlert()
default:
println("Failed")
}
func printCurrentCoordinates() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
println("\(locationManager.location.coordinate.latitude)")
println("\(locationManager.location.coordinate.longitude)")
}
func disabledLocationSeriveAlert() {
let alert = UIAlertController(title: "Unable to retrieve location", message: "Enable location services in system settings", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}

It looks like you aren't giving the CLLocationManager enough time to update the location.
I looked at the documentation, and in the statement locationManager.location.coordinate.latitude, location is the only one that's an optional. It's implicitly unwrapped, so that's why you don't need an ! after it.
In the printCurrentCoordinates method, you attempt to print the coordinates immediately after you call startUpdatingLocation. The documentation for location says:
The value of this property is nil if no location data has ever been retrieved.
And the documentation for startUpdatingLocation says:
This method returns immediately. Calling this method causes the location manager to obtain an initial location fix (which may take several seconds) and notify your delegate by calling its locationManager:didUpdateLocations: method.
So, you aren't giving the location manager enough time to get a location fix before you attempt to print the location.
You have a couple of choices - you can call startUpdatingLocation earlier and then print the coordinates in printCurrentCoordinates, or you could have printCurrentCoordinates start updating the location and then print the coordinates in locationManager:didUpdateLocations:.

The issue is location property of locationManager will be nil at that time.
That property contains the most recently retrieved location data. You just allocated location manager, at that time there won't be any data in that property.
According to CLLocationManager Class Reference:
location
Property The most recently retrieved user location.
(read-only)
Declaration
#NSCopying var location: CLLocation! { get }
Discussion
The value of this property is nil if no location data has
ever been retrieved.

location is nil when not set (from doc)
The value of this property is nil if no location data has ever been retrieved.
You have to use delegate to get the location. This is asynchronous operation, so value not available right after call startUpdatingLocation()
You must implement CLLocationManagerDelegate (and set your object as delegate) and wait for locationManager:didUpdateLocations: method - only then location will have some data.

Obtaining the location is an asynchronous operation - in fact the method you invoke to start the op is startUpdatingLocation() and not getLocation(). That is an indication that the process is started, and it's not completed at the time the invoked method returns.
The location is provided asynchronously via the func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) method, part of the CLLocationManagerDelegate protocol that you must adopt in your class. And of course you have to set the delegate property of the CLLocationManager instance. If you have ever worked with tables, you should know how it works.
However, I recommend reading the official docs, or a good tutorial like this one

Related

CLLocationManager not updating while in background, and get messy location coordinates Swift

I have a tracking function but it doesn't update location while in background.
1st case: Tracking while app is in the foreground -> the tracking is actually happening but doesn't get precise coordinates. I will change to locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation to see if improves accuracy of the tracking.
2nd case: Tracking while screen is off -> the tracking is a straight line from a to b, tracking doesn't update coordinates.
3rd case: Tracking while app is in back ground(pressed home button) -> tracking is happening as case 1.
I found a post that explains that if authorisation is set to always you have to specify you want to keep updating location while in background, but nothing has changed. This is the code and info.plist :
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
// locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
// locationManager.allowsBackgroundLocationUpdates = true //for getting user location in background mode as well
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow //map following user
configureLocationServices()
addDoubleTap() // enabling duble tap gesture recognizer
// mapView.isUserInteractionEnabled = true
let location = locationManager.location?.coordinate
let region = MKCoordinateRegionMakeWithDistance(location!, 1000, 1000) // set mapView based on user location coordinates
mapView.setRegion(region, animated: true)
centerMapOnLocation()
// alerts coordinates to post to Firebase
let alertDrawLatitude = alertDrawCoordinates?.latitude // not used ?
let alertDrawLomgitude = alertDrawCoordinates?.longitude
let title: String? = alertNotificationType
var subtitle: String? = alertNotificationType
// user alert notification. takes coordinates from alertNotificationArray( populated with firebase returning coordinate for all alerts
displayAlerts()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else { return }
self.actualRouteInUseCoordinatesArray.append(mostRecentLocation.coordinate)
}
func configureLocationServices() {
if authorizationStatus == .notDetermined{
locationManager.requestAlwaysAuthorization()
} else if authorizationStatus == .authorizedAlways {
locationManager.showsBackgroundLocationIndicator = true //set update location even if in background. very imposrtant!!
}
}
UPDATE:
changing the accuracy only made things worse.
with AccuracyBest:
and with AccuracyBestForNAvigation
second tracking is actually worse.. how can navigation apps rely on this kind of tracking? is there anything wrong with my code for LocationManager?
SECOND UPDATE:
it now get updated location when in background, but is way off..I never passed the yellow street and it shows like I waked for 10 minutes after it..
THIRD EDIT:
I found out that I should filter out GPS raw data, so I'm using a Kalman filter, and it really smooths out the resulting tracking.
So I'm fine tuning two parameters, and in order to be able to change those parameters I added two textfields #IBOutlet weak var filterValueTextField: UITextField! and #IBOutlet weak var horizontalAccuracyTextField: UITextField!and connected those to the parameters
hcKalmanFilter?.rValue = Double(String( describing:filterValueTextField?.text!))! and guard mostRecentLocation.horizontalAccuracy < Double(String( describing: horizontalAccuracyTextField?.text!))! else { return }.
My problem is now that it finds nil while unwrapping value in the horizontalAccuracy parameter.
If in horizontalAccuracy I just put a value it accepts an integer, but when I take it from the texField converting the textfield.text to Int, compiler throws an error Binary operator '<' cannot be applied to operands of type 'CLLocationAccuracy' (aka 'Double') and 'Int', while if I convert it to Double doesn't, but it finds nil.
Why the filterValue finds a value from it's textField, and the horizontal Accuracy doesn't? they're declared, and use the same way.
Any idea?
First of all you have limited time while your app goes to background, and that time is depend upon load on your device's OS, but most probably it is approx. 30 seconds. So this is the reason your are not getting location updates while your screen is off or while your app goes to background.
But Apple allows app to run in background for some tasks and location update is one of them, so you can fetch location updates even if your app goes to background by enabling Background Fetch capability for your app.
For more details please follow below official doc. of Apple:
https://developer.apple.com/documentation/corelocation/getting_the_user_s_location/handling_location_events_in_the_background
And secondly try to maintain your locationmanager object in global scope of your app like you can place it in AppDelegate or in Singleton class if you are maintaining any for your app, so it will always be available.
Sometimes location that you receive does not have desired accuracy, especially when you've just started tracking, first couple of locations are going to be well off. You can use location's horizontal accuracy property to filter location witch have, for example, less then 50m accuracy

How to get CLLocationManager result before all viewDidLoad stuff loads?

Please check the code:
let manager = CLLocationManager()
//Location manager to determine the current location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
lat = location.coordinate.latitude
lon = location.coordinate.longitude
let currentLocation = CLLocation(latitude: lat!, longitude: lon!)
}
And i have few more functions in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
//Here i want to get the result immediately, but...
manager.startUpdatingLocation()
parseJSONfunction()
anotherFunction()
anotherFunction2()
...}
So, i can't get the result of manager.startUpdatingLocation() function until all viewDidLoad() stuff loads.
The question: Is it possible to get coordinates before all other functions runs? If yes, please describe how?
The question: Is it possible to get coordinates before all other functions runs?
In a word, no. The location manager is asynchronous. You ask it to start updating your location and it fires up the GPS and tries to get a fix (it also uses cell towers, WiFi base stations, etc.) It can take several seconds (or longer) in order to get a reasonably accurate reading.
When I'm writing location-aware apps, I usually start the location manager, and in my locationManager(_:didUpdateLocations:) method I check the horizontal accuracy of the result and only take it if it's at least reasonably accurate. This can make it take even longer.
If you load a viewController it's synchronous. The system does the setup, which causes various framework calls to fire, and then viewDidLoad gets called, synchronously once the view loads. If you start the location manager when the user asks to display a new view controller, you can't possibly have a location fix by the time viewDidLoad is called.
If you load your app, have the app delegate invoke a singleton to begin location updates, and then wait for the user to switch to your other screen, where you then ask the singleton for the location during viewDidLoad you have a good chance of getting a good location reading, but even then it isn't certain.

Getting current location using CLLocationManager in Swift

How do I get the current location of my iOS device?
It seems that Apple has made changes that to how get location and I could not find an up-to-date post. I answered this question myself below:
I couldn't find an up-to-date solution to getting the current location so here's how I did it.
My source is apple's sample code on location tracking and smart watch called PotLocCoreLocationwithiPhoneandAppleWatch: https://github.com/robovm/apple-ios-samples/tree/master/PotLocCoreLocationwithiPhoneandAppleWatch
Here's what you have to do in your app.
Note that everything from steps 2-6 are in this gist: https://gist.github.com/JonMercer/75b3f45cda16ee5d1e0955b2492f66dd
In the project settings in xcode, click on the capabilities tab (it's the tab beside the general tab where you put in your bundle identifier). Then turn on Background Mode and enable Location updates. EDIT thanks to #Rob: And also add NSLocationWhenInUseUsageDescription to your plist. The value can be anything you like
Inside your view controller, extend the CLLocationManagerDelegate
class YourClass: UIViewController, CLLocationManagerDelegate {
Implement these two functions
/**
Increases that location count by the number of locations received by the
manager. Updates the batch count with the added locations.
*/
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//use the "locations" variable here
}
/// Log any errors to the console.
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error occured: \(error.localizedDescription).")
}
Create a let manager = CLLocationManager() variable
On viewDidLoad() set the following: manager.requestWhenInUseAuthorization(), manager.allowsBackgroundLocationUpdates = true, and manager.startUpdatingLocation()
In order to stop tracking do the following: manager.stopUpdatingLocation() and manager.allowsBackgroundLocationUpdates = false

didUpdateLocations delegate method is repeated a random number of times

I get latitude and longitude of the user in my method didUpdateLocations. If location is allowed I call a method that takes in parameters latitude, longitude, and calls a webservice, else I display an UIAlertView.
Problem is: iOS calls my locationManager delegate method a random number of times. So my webservice is called several times... How can I fix it please?
When I call the location, verify if is allowed... I make the request in the previous screen:
// GET LOCATION
self.initializeWaitingScreen()
if( CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
} else {
let loginFailAlert: UIAlertView = UIAlertView(title: "Localisation refusée", message: "Vos fonctionnalités sont restreintes, pour accéder à l'application complète, veuillez activer la localisation", delegate: self, cancelButtonTitle: "OK")
loginFailAlert.show()
self.initializeUIComponent()
self.initializeDataWithWebServiceWithoutLocation()
}
My locationManager method:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var lon = manager.location.coordinate.longitude.description
var lat = manager.location.coordinate.latitude.description
self.locationManager.stopUpdatingLocation()
self.initializeDataWithWebServiceWithLocation(lon, _lat: lat)
self.initializeUIComponent()
}
self.initializeDataWithWebServiceWithLocation(lon, _lat: lat)
take longitude and latitude, and give it to my method who call webservices.
This is expected behavior. As CLLocationManager determines the user's location, updates will be sent (I don't just mean the obvious). See this excerpt from Apple's docs:
Regardless of which location service you use, location data is reported to your app via the location manager’s associated delegate object. Because it can take several seconds to return an initial location, the location manager typically delivers the previously cached location data immediately and then delivers more up-to-date location data as it becomes available. Therefore it is always a good idea to check the timestamp of any location object before taking any actions. If both location services are enabled simultaneously, they deliver events using the same set of delegate methods.
If you require some filtering of events, you'll need to (1) ensure you've set your desiredAccuracy properly to help minimize the number of events then (2) perform any particular app-specific filtering. Be cautious though, since the reason you get multiple updates is that the determined location has changed. If you second-guess the system, you may wind up with inaccurate data.
Finally, evaluate whether you need location changes or significant location changes. If you don't need the high granularity, go with "significant".

Unable to get the permission prompt from CLLocationManager

Here is my code from a ViewController implementing CLLocationManagerDelegate:
func startLocationManager() {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
println("I'm called")
locationManager.requestAlwaysAuthorization()
// locationManager.startMonitoringSignificantLocationChanges()
locationManager.startUpdatingLocation()
let status = CLLocationManager.authorizationStatus()
println(status.rawValue) // This print 0 which stands for kCLAuthorizationStatusNotDetermined
println(CLLocationManager.locationServicesEnabled()) // true
}
func locationManager(manager: CLLocationManager!,
didUpdateLocations locations: [AnyObject]!) {
println("nobody call me ever, and I'm sad")
}
For some reason, I never get the prompt / alter to autorise location updates. I have tried on my device iOS 8.1 and the simulartor. I followed the advices found here: requestAlwaysAuthorization not showing permission alert :
"Add Core Location framework to Project Settings / Targets / Capabilities / Background Modes set "Location Updates" and "Uses Bluetooth LE Accessories" Add key at Info.plist NSLocationAlwaysUsageDescription".
I have also tried to clean up and rebuild, nothing change. I feel clueless.
EDIT: This question seems related: iOS: App is not asking user's permission while installing the app. getting kCLAuthorizationStatusNotDetermined every time - Objective-c & Swift but the selected answer and its article doesn't expose anything new
Your CLLocationManager object is local object and thus will be deallocated immediately after it falls out of scope. Make it a class property and then asynchronous processes like requesting authorization and determining the location will have a chance to run.

Resources