I have an application were I want to get the users location in viewDidLoad, I then store the lat and long in variables and use them in a function to get data based on the position from the user. I have a timer that calls a function every x minute that gets data (let´s call it getData()), the first time getData() is called lat and long is 0, but the second, third time etc.. they have values.
When I update the coords (during runtime) in the simulator I do get the updated values for both lat and long, but it´s the first run that lat and long always are 0. My code looks like this:
let locationManager = CLLocationManager()
var getDataGroup = dispatch_group_create()
override func viewDidLoad() {
super.viewDidLoad()
dispatch_group_enter(getDataGroup)
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
dispatch_group_leave(getDataGroup)
dispatch_group_wait(getDataGroup, DISPATCH_TIME_FOREVER)
dispatch_async(dispatch_get_main_queue()) {
self.getData()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
if (error != nil){
println("Error: " + error.localizedDescription)
return
}
if (placemarks.count > 0){
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm)
}
else{
println("Error with location data")
}
})
}
func displayLocationInfo (placemark : CLPlacemark){
self.locationManager.stopUpdatingLocation()
lat = String(stringInterpolationSegment: placemark.location.coordinate.latitude)
long = String(stringInterpolationSegment: placemark.location.coordinate.longitude)
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Error: " + error.localizedDescription)
}
func getData(){
// Do stuff with lat and long...
}
For some reason the first run lat and long are both 0, the rest of the runs they have a valid value. The problem is that getData is called before the locationManager and displayLocationInfo methods even though I have added self.locationManager.startUpdatingLocation() before the call to getData() for some reason.
I don't really understand what your code is supposed to do.
There's no reason for the GCD code in your viewDidLoad. simply create an instance of the location manager and tell it to start updating your location.
Then your didUpdateLocations method will be called once a location is available. Use the last location in the array of locations you get in that method - it will be the most recent.
I would advise checking the horizontalAccuracy of the locations before using them. Typically the first few location readings are really bad, and then slowly the readings settle down. The Horizontal accuracy reading is actually a radius giving the possible area of the reading. If you get 1 km, it means your location could be anywhere in a circle 1 km in radius. You probably want to discard readings that bad and wait for one that's within a few hundred meters (or better.)
Your viewDidLoad method is calling a method getDeparturesAtStop, and that method will likely be called before the first call to didUpdateLocations fires. (You don't show what that method does.)
Where is the code that is getting a zero lat/long?
Related
Yeah ! My CoreLocation working now ! So i have a problem to store latitude and longitude and stopping locating.
I have a Sign In page (my MainVC)
before user sign in i need to get and store current device location and stop locating (now it's working with help of Alexander see under).
in my MainVC.swift i have this 2 global variables
var locationManager:CLLocationManager!
var myLocations = [CLLocation]()
in my viewDidLoad() i have this :
//Setup our Location Manager
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
For operate locationManager i have this 2 functions :
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locations = \(locations)")
errorMsgIfInternetNotAvailable.text = "GPS success"
myLocations.append(locations.last!)
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
errorMsgIfInternetNotAvailable.text = "Error while updating location " + error.localizedDescription
}
When i build my app, all working, if not autorise my device to get GPS informations i have the locationManager function error working.
If i autorise it
in console, the location update every second and don't stop it with the .stopUpdatingLocation() command
First, it is always good to provide the build error output. It's hard enough to guess which error you're getting.
It seems like you defined, but not initialized myLocations property. You have to do it like this:
var myLocations = [CLLocation]()
Notice the brackets () in initialization.
And then you have to add object to your array:
myLocations.append(locations.last)
or, if you want to store only one object in your array, do it like:
myLocations = [locations.last]
I have a locationManager function to grab the users current location and posting the name of the city and state. I have a print statement so I can check in my console if everything is working properly...and it is. However, it prints the city location 3 times. This actually causes an issue in my actual app but thats beyond the point of this question.
My function is as follows:
var usersLocation: String!
var locationManager: CLLocationManager!
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) -> Void in
if error != nil {
print(error)
} else {
let p = placemarks?.first // ".first" returns the first element in the collection, or nil if its empty
// this code above will equal the first element in the placemarks array
let city = p?.locality != nil ? p?.locality : ""
let state = p?.administrativeArea != nil ? p?.administrativeArea : ""
self.navigationBar.title = ("\(city!), \(state!)")
self.usersLocation = ("\(city!), \(state!)")
self.locationManager.stopUpdatingLocation()
print(self.usersLocation)
self.refreshPosts()
}
}
}
So in my print(self.usersLocation) it will print in my console three times. Is this normal?
UPDATE TO SHOW VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 250.0
}
I'd first suggest a few things:
Call stopUpdatingLocation before you perform reverseGeocodeLocation.
You are calling stopUpdatingLocation inside the reverseGeocodeLocation completion handler closure. The problem is that this runs asynchronously, and thus didUpdateLocations may receive additional location updates in the intervening period. And often, when you first start location services, you'll get a number of updates, often with increasing accuracy (e.g. horizontalAccuracy values that are smaller and smaller). If you turn off location services before initiating asynchronous geocode request, you'll minimize this issue.
You can also add add a distanceFilter in viewDidLoad, which will minimize redundant calls to the delegate method:
locationManager.distanceFilter = 1000
You can use your own state variable that checks to see if the reverse geocode process has been initiated. For example:
private var didPerformGeocode = false
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// if we don't have a valid location, exit
guard let location = locations.first where location.horizontalAccuracy >= 0 else { return }
// or if we have already searched, return
guard !didPerformGeocode else { return }
// otherwise, update state variable, stop location services and start geocode
didPerformGeocode = true
locationManager.stopUpdatingLocation()
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
let placemark = placemarks?.first
// if there's an error or no placemark, then exit
guard error == nil && placemark != nil else {
print(error)
return
}
let city = placemark?.locality ?? ""
let state = placemark?.administrativeArea ?? ""
self.navigationBar.title = ("\(city), \(state)")
self.usersLocation = ("\(city), \(state)")
print(self.usersLocation)
self.refreshPosts()
}
}
I had the same problem and Rob's answer didn't do it for me.
When the location service first starts, the location is updated multiple times regardless of the distanceFilter.
You might still want the location to be updated and you don't want to lose the location accuracy(which is the whole point of updating location multiple times on start-up), so calling stopUpdatingLocation(or using a local variable) after the first geolocating call isn't the way to go either.
The most intuitive way is to wrap your geocode call in an #objc function and call the the function with a delay:
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(myGeocodeFunction(_:)), with: location, afterDelay: 0.5)
Same code, I'm assuming that the device is actually updating the location twice for some reason, even though I only call startUpdatingLocation() once and I run some stopUpdatingLocations() inside of didUpdateLocations
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
let loc: CLLocation = locations[locations.count - 1]
let id = 0
let type = 0
let number = 0
createNewDataPoint(id, loc: loc, type: type, number: number)
}
In this case, createNewDataPoint gets called twice, creating 2 new datapoints. It only happens once in the simulator, so I'm assuming it has something to do with the actual device and the GPS since the simulator fakes its location.
startUpdatingLocation() is only in my code one time, on a button. Basically, you click the button, go go manager.startUpdatingLocations(), didUpdateLocations hits once on simulator, twice on device (identical coordinates) and it creates 2 new data points.
The only other code that mentions anything related is setting the accuracy, filter, authorization requests, and the previously mentioned startUpdatingLocation(). Is there something I can do to make sure I'm not creating twice as many data points as necessary?
Location Manager delegate methods can be called very frequently and at any time.
You may however, apply following algorithm to safeguard yourself:
Create a global bool say didFindLocation.
Set didFindLocation to false when you call startUpdatingLocation.
Inside delegate call back didUpdateLocations:, if didFindLocation was false, set didFindLocation to true and then call stopUpdatingLocation.
Hope this helps.
The best way is do as following:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
manager.delegate = nil
}
Best solution for iOS 10.0+
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
[locationManager stopUpdatingLocation]; // stop location manager
locationManager.delegate = nil;
//Your logics...
//This will be called only one time now.
}
But don't forget to set the delegate again.
After getting the desired latitude and longitude just call stopUpdatingLocation()and set the delegate to nil.
In Swift 3:
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
In Objective-C:
[locationManager stopUpdatingLocation]
locationManager.delegate = nil
Here locationManager is the object of CLLocationManager.
You will not get frequently on simulator. and on device when you will move far away then only you get didUpdateLocations. just move in a open space so GPS can identify you device location so it get best accuracy.
Instead of starting / ending the location update and setting delegate to nil, there is a method called requestLocation which is ideal when your application need quick fix on the user's location:
From the docs:
override func viewDidLoad() {
// Create a location manager object
self.locationManager = CLLocationManager()
// Set the delegate
self.locationManager.delegate = self
}
func getQuickLocationUpdate() {
// Request location authorization
self.locationManager.requestWhenInUseAuthorization()
// Request a location update
self.locationManager.requestLocation()
// Note: requestLocation may timeout and produce an error if authorization has not yet been granted by the user
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Process the received location update
}
Use this method when you want the user’s current location but do not need to leave location services running.
#Zumry Mohamed 's solution is right
i try the code like this:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationManager stopUpdatingLocation];
self.locationManager.delegate = nil;
self.locationManager = nil;
}
finally this delegate is called only once, i understand now why the problem is occurred, just because manager call the stopUpdatingLocationmethod but system doesn't help us to make the delegate invalid, so we can receive the callback every time location updates due to your desiredAccuracy and distanceFilter property settings of your CLLocationManager, so the final solution is just like what #Zumry Mohamed said, we can manually set the delegate to nil when we stopUpdateLocation. hope it will help you understand what happens why this could solve the problem.
locationManager.startUpdatingLocation() fetch location continuously and didUpdateLocations method calls several times,
Just set the value for locationManager.distanceFilter value before calling locationManager.startUpdatingLocation().
As I set 200 meters(you can change as your requirement) working fine
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 200
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
Another way is to set a time interval to turn on and off the delegate and so the location manager. A sorta of this
var locationManagerUpdate:Bool = false //Global
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 10 seconds
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.updateLocationManager), userInfo: nil, repeats: true)
}
#objc func updateLocationManager() {
if locationManagerUpdate == false {
locationManager.delegate = self
locationManagerUpdate = true
}
}
extension lm_gest: CLLocationManagerDelegate {
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locationManagerUpdate == true {
manager.stopUpdatingLocation()
manager.delegate = nil
}
//your code here...
}
I've been attempting to retrieve a iPhone's latitude and longitude for purposes of passing to an external function in order to geotag something. However, I've been rather unsuccessful. Here is the code I'm working with right now.
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func findMyLocation (sender:AnyObject)
{
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
if error == nil
{
println("Reverse geocoder failed with error " + error.localizedDescription)
return
}
if placemarks.count > 0
{
let pm = placemarks[0] as CLPlacemark
self.displayLocationInfo(pm)
}
else
{
println("Problem with the data received from geocoder")
}
})
}
func displayLocationInfo(placemark: CLPlacemark) {
//stop updating location to save battery life
locationManager.stopUpdatingLocation()
if (placemark.postalCode != nil)
{
println(placemark.postalCode)
println("just tried to print placemark's postal code")
}
//println(placemark.locality ? placemark.locality : "")
//println(placemark.postalCode ? placemark.postalCode : "")
//println(placemark.administrativeArea ? placemark.administrativeArea : "")
//println(placemark.country ? placemark.country : "")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!)
{
println("Error while updating location " + error.localizedDescription)
}
As of now, I get pretty much nothing when I run the program. I have a little button that says "Find Me", and upon clicking, nothing pops up in the output box. I have the iOS Simulator's Debug location set to "Apple", and I've previously tried always enabling location services in settings on the simulator when trying to run the program (although currently it's not showing my app and the option to do so).
I really just want to get a double value for latitude and longitude, and if there's a simple something that'd be great.
You should only request one type of authorization. Pick the one that makes the most sense for the user experience of your app.
You are probably missing your purpose string, which is now required to activate location services. Add one of the following keys to your Info.plist:
NSLocationAlwaysUsageDescription - if requesting Always permission
NSLocationWhenInUseUsageDescription - if requesting WhenInUse permission
The value of the key should be a string that explains why you want location permissions.
Problem: I need the location coordinates to be acquired before getReports() is executed in viewDidLoad(). As you can see, I'm calling startLocationUpdates first in viewDidLoad(). Unfortunately, there is a delay between calling startLocationUpdates() and didUpdateLocations getting called. This delay is causing the latitude and longitude coordinates to be zero every time prior to executing getReports().
How can I be sure that didUpdateLocations gets called BEFORE getReports() so that the latitude and longitude values are set properly?
override func viewDidLoad() {
super.viewDidLoad()
self.startLocationUpdates()
// Do any additional setup after loading the view.
self.mapView.delegate = self
if mblnLocationAcquired {
getReports()
}
}
}
func startLocationUpdates() {
var currentLocation = CLLocation()
mLocationManager.desiredAccuracy = kCLLocationAccuracyBest
mLocationManager.delegate = self
//mLocationManager.requestWhenInUseAuthorization()
mLocationManager.requestWhenInUseAuthorization()
mLocationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let location = locations.last as CLLocation
self.mdblLat = location.coordinate.latitude
self.mdblLong = location.coordinate.longitude
println("didUpdateLocation \(location.coordinate.latitude), \(location.coordinate.longitude)")
mLocationManager.stopUpdatingLocation()
mblnLocationAcquired = true
}
How can I be sure that didUpdateLocations gets called BEFORE getReports() so that the latitude and longitude values are set properly?
Simple. Don't call getReports() until after the latitude and longitude are set properly! You are making a huge mistake by turning off updates as soon as the first update arrives. Keep in mind that it takes significant time for the GPS to "warm up" after you start location updates. You will receive numerous initial updates in which the location is meaningless or miles off. You need to use the accuracy reading of the location in order to wait until you have a good value before you display anything.