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...
}
Related
I am trying to update location from app to firebase after every X minutes ( any value less than 15mins is good).
I have enabled background mode for location and in following method I try to upload to Firestore document.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
problem is in log I can see this method was called but in background no data is uploaded to firestore. In fact I works just after the app has entered in background sometimes but not at all after that.
Is there any workaround for this ?
Use this code with Timer inside it to call update in Firestore:
write this code when starting your map call:
locationManager.startUpdatingLocation()
locationManager.delegate = self
Avoid calling untill you don't want to stop location updating:
locationManager.stopUpdatingLocation()
Conform Delegate method:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = manager.location else {
return
}
self.clLocation = location
//Firestore condition here
}
Check if you have set to true for allowsBackgroundLocationUpdates in your location Manager
locationManager?.allowsBackgroundLocationUpdates = true
Only if there is any update in location you will get the location updated in background. Also check if you have mentioned any distanceFilter in your location manager. If so, based on that only you will get the location update every X meters
locationManager?.distanceFilter = CLLocationDistance(X)
Use this code
locationManager.startUpdatingLocation()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.delegate = self
Don't stop location updates in following delegate method.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
I am building a feature related to region monitoring while starting region monitoring I am requesting the state as shown below in code. On some of the devices, I am getting region state Unknown all the time. If I switch Wifi On or Off or plug the charger into it. It starts working fine.
How can I make it more reliable on a cellular network?
Please, note I took all location permissions from the user before making any region monitoring or state request calls.
private func initiateLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.distanceFilter = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
}
func startMonitoring(alarm: StationAlarm) {
if LocationManager.sharedInstance.isRegionMonitoringAvailable() {
let coordinate = CLLocationCoordinate2D(latitude: stationLatitude, longitude: stationLongitude)
// 1
let region = CLCircularRegion(center: coordinate, radius: CLLocationDistance(radius * 1000), identifier: alarm.alarmId)
// 2
region.notifyOnEntry = true
region.notifyOnExit = false
// 4
locationManager.startMonitoring(for: region)
Utility.delay(0.1) { [weak self] in
self?.locationManager.requestState(for: region)
}
}
}
func locationManager(_: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
Log.event("Region State is \(state.rawValue)")
}
The issue is, you are calling the requestState using a hard-coded delay - (0.1). How do you make sure the Location Manager started monitoring your region within 0.1 seconds? You will get the exact region state of a region, only if started monitoring it.
The better method for overcoming this problem is, implement the didStartMonitoringForRegion delegate and call requestStateForRegion
locationManager.startMonitoring(for: region)
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
manager.requestState(for: region)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if (region is CLBeaconRegion) && state == .inside {
locationManager(manager, didEnterRegion: region)
}
}
From the CLLocationManager requestState(for:) docs:
region: The region whose state you want to know. This object must be an instance of one of the standard region subclasses provided by Map Kit. You cannot use this method to determine the state of custom regions you define yourself.
You defined the region yourself so you can't use requestState(for:) to get its state. You use that function with regions that you get back from Core Location (via the delegate methods).
If you want to know whether the device is currently inside a region, start a standard location update request (startUpdatingLocation() etc) and when you get back a recent and accurate coordinate, use the CLCircularRegion contains() function to check the coordinate.
// In the locationManager(_:didUpdateLocations:) delegate method
if myCircularRegion.contains(myCoordinate) {
// ...
}
I am developing a screen that will need to update location every 10 minutes using a timer. Other than that It only needs to update location at first load and when the view appears to the user again. It should stop monitoring once the the user goes to another view.
I have a code that is supposed to do this, but the issue is that the didUpdateLocations method is not called at any point. Also the map does not show the current location (I use simulated locations).
I have correctly set up the permissions and the app worked fine when it was setup to just show the location. I need to do this to reduce battery consumption.
Here is my related code:
In viewDidLoad:
if #available(iOS 8.0, *) {
self.locationManager.requestAlwaysAuthorization()
}
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.distanceFilter = 1000
self.locationManager.activityType = CLActivityType.automotiveNavigation
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.startUpdatingLocation()
self.map.showsUserLocation = true
In viewWillAppear:
self.map.showsUserLocation = true
self.locationManager.startUpdatingLocation()
In viewWillDisappear:
self.map.showsUserLocation = false
self.locationManager.stopUpdatingLocation()
In didUpdateLocations: (at last line)
self.locationManager.stopUpdatingLocation()
Timer Function: (this gets called fine)
Timer.scheduledTimer(timeInterval: 600.0, target: self, selector: #selector(HomePageViewController.updateLocationFromTimer), userInfo: nil, repeats: true)
#objc func updateLocationFromTimer()
{
self.locationManager.startUpdatingLocation()
}
I also tried to catch any error with the following code:
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
but it did not get called.
I would love to know why the location is not being updated and why the map is not showing the location. Please help.
Make sure you assign the delegate:
self.locationManager.delegate = self
I did not work for me either and discovered that the place at which you set the delegate impacts this.
E.g. this did not work:
var locationManager = CLLocationManager() {
didSet {
locationManager.delegate = self
}
}
Setting it at a later moment did work as expected. Not sure why to be honest, but maybe this helps someone.
I am working on watch heart beat app and I want when user heart rate critical then we will get his current location in(foreground, background and terminate) and send it to our server. Is there any way through which I get user location only at that position. I don't want to update his location every time.
To get user location you have to declare :
let locationManager = CLLocationManager()
in your controller.
Then, in viewDidLoad you have to request for location and initialize the CLLocationManager get process :
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
You will get location in CLLocationManagerDelegate :
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var location:CLLocationCoordinate2D = manager.location.coordinate
print("locations = \(location.latitude) \(location.longitude)")
}
In your info.plist you have to add NSLocationAlwaysUsageDescription and custom alert message to show while requesting for location.
cheers...
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.