I have a CoreLocation manager in VC, when user pressed "get direction" button, I initalize location manager, and app opens google map direction with current location and some pre defined destination location.
Here is my problem, if app is not in background state, current location nearly always true, bu if app calling from background in same VC and user pressed again "get direction" button , current location generally shows old locations. In short, I'm troubling with multitasking and timestamp of retrieved locations did not solved my problem.
IBAction:
if ( self.locationManager ) {
[_locationManager release];
self.locationManager = nil;
}
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationTimer = [NSTimer scheduledTimerWithTimeInterval:LOCATION_TIMER target:self selector:#selector(stopUpdatingLocationTimer) userInfo:nil repeats:NO];
HUD = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
[self.locationManager startUpdatingLocation];
Core Location Delegate:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
NSLog(#"%f",locationAge);
if (locationAge > 3.0)
return;
if (newLocation.horizontalAccuracy < 0)
return;
if ( self.currentLocation == nil || self.currentLocation.horizontalAccuracy > newLocation.horizontalAccuracy ) {
self.currentLocation = newLocation;
if (self.currentLocation.horizontalAccuracy <= self.locationManager.desiredAccuracy) {
[self stopUpdatingLocations:YES];
}
}
}
In your example locationAge is a negative representation of the number of seconds since the timestamp of newLocation. This means that locationAge will never be greater than 3 and you're effectively letting every update through the sieve.
Set locationAge like this:
NSTimeInterval locationAge = [newLocation.timestamp timeIntervalSinceNow];
For those who encounter same problem,
Also, some tutorials related with core location on web, lead me this problem.
Of course, I keep CLLocation ivar in my VC, whenever CLLocation ivar
sets and google maps called, my app goes to background.
Then, my app calls from background by user, and start updating locations,
and old CLLocation ivar is not nil and probably best horizantal accuracy then
newly comes. Therefore;
if ( self.currentLocation == nil || self.currentLocation.horizontalAccuracy > newLocation.horizontalAccuracy )
this line cause problems, and old location in CLLocation ivar
never replaced.
So I changed viewDidDisappear like this and I assigned nil value to CLLocation variable and works perfectly.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.locationManager stopUpdatingLocation]; // CLLocationManager
self.locationManager = nil;
[self.locationTimer invalidate]; // NSTimer
self.locationTimer = nil;
self.currentLocation = nil; // CLLocation
}
p.s : thank you Mark Adams
Related
In my current project.
I need user's location at every 50 meter user move.
So Basically After open application every 50 meter change I need user location for call web service in Objective c. Also i want same process run when application is in background state.
Thanks in advance
You have to make object of CLLocationManager when application starts and set it's delegate
Add the below code to get user's current location
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
Now add the delegate of CLLocationManagaer that is didUpdateToLocation and add the following code in that.
CLLocationDistance meters = [newLocation distanceFromLocation:oldLocation];
if(meters==50)
{
// CALL YOU WEBSERVICE
}
set your location track in
//create location manager object
locationManager = [[CLLocationManager alloc] init];
//there will be a warning from this line of code
[locationManager setDelegate:self];
//and we want it to be as accurate as possible
//regardless of how much time/power it takes
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
//set the amount of metres travelled before location update is made
[locationManager setDistanceFilter:50];
and add
if ([CLLocationManager locationServicesEnabled]) {
[self.locationManager startUpdatingLocation];
}
Update
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *location = locations.lastObject;
NSLog(#"%#", location.description);
//In here you get all details like
NSLog(#"latitude = %#",location.coordinate.latitude);
NSLog(#"longitude = %#",location.coordinate.longitude);
NSLog(#"altitude = %#",location.altitude);
NSLog(#"horizontalAccuracy = %#",location.horizontalAccuracy);
NSLog(#"verticalAccuracy = %#",location.verticalAccuracy);
NSLog(#"timestamp = %#",location.timestamp);
NSLog(#"speed = %#",location.speed);
NSLog(#"course = %#",location.course);
}
I'm using Parse and with geoPointForCurrentLocationInBackground I can stop updating once a location is received without having to manually stop it.
How do I stop updating location immediately right after I receive location using CLLocationManager?
Edit
I know [self.locationManager stopUpdatingLocation]; stops it.
What I'm really asking is, how do I know I've received location for the first time then stop it immediately?
After getting your location, use this method:
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
Call stopUpdatingLocation as soon as your didUpdateLocations method is called.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[manager stopUpdatingLocation];
//store your location
self.location = [locations lastObject];
}
BOOL first_time = YES; // public
Every time you start updating location set first_time to YES:
first_time = YES;
[self.locationManager startUpdatingLocation];
in your didUpdateUserLocation method:
if (userLocation == nil) {
NSLog(#"User location is nil. maybe wating for permission");
} else if (!CLLocationCoordinate2DIsValid(userLocation.coordinate)) {
NSLog(#"User location is not valid 2d coordinates. maybe called in background");
} else {
NSLog(#"Did update user location: %f %f", userLocation.location.coordinate.latitude, userLocation.location.coordinate.longitude);
// here is the first time you receive user location
if (first_time)
{
first_time = NO;
[self.locationManager stopUpdatingLocation];
}
}
call below method to save and stop location after you get it once
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
self.location = [locations lastObject]
self.locationManager.delegate = nil;
[self.locationManager stopUpdatingLocation];
}
The opposite of stopMonitoringSignificantLocationChanges is not stopUpdatingLocation, it is startMonitoringSignificantLocationChanges.
Check out the CLLocation documentation for further detail.
Finally found the answer in another question...reprinting it here because it took me a while to stumble on it.
I had to call this:
[_locationManager stopMonitoringSignificantLocationChanges];
Even though I never called startMonitoringSignificantLocationChanges in the first place, seems I had to "un-call" it...strange that it works this way, but as soon as I added that line, location services shut down promptly. Hope this helps someone else.
Once current location update stop location manager using stopUpdatingLocation.
region.center = self.mapView.userLocation.coordinate;
if (region.center.longitude != 0 && region.center.latitude != 0) {
[self.locationManager stopUpdatingLocation];
}
self.yourLocationManager.stopUpdatingLocation()
I am trying to make an app that can be trigged by an iBeacon to wake up (from being killed/suspended/terminated) to record second-by-second GPS information. The GPS recording should then stop when the phone gets out of range of the beacon. I have successfully gotten my app to recognize the didEnterRegion and didExitRegion methods when it comes in and out of range of the iBeacon. In the didEnterRegion method I want to basically say something like [locationManager startUpdatingLocation] so that I can start tracking the user's location. However, when I try to add this line of code, the location updates stop after about 10 seconds.
Later I found an article about background location updates that came with this Github project. I added the BackgroundTaskManager, LocationShareModel, and LocationTracker files to my project. Basically, the idea behind this solution is to continually restart the location manager so it doesn't have the chance for the background task to expire and stop sending updates. However, even with this solution, I only get location updates for a little over 3 minutes.
I have the "Location Updates" and "Use Bluetooth LE accessories" background modes enables. The "Background Fetch" (Background App Refresh) is not enabled, in accordance with this quote from Apple: "In iOS 8 and later, disabling the Background App Refresh setting for the current app or for all apps does not prevent the delivery of location events in the background." My app requests "Always" authorization for location updates.
I cannot figure out how to solve this issue, despite reviewing seemingly endless StackOverflow articles and tutorials. I am testing it on an iPhone 5S running iOS 8.3.0. Any insight would be appreciated. See code excerpts below.
In AppDelegate.m :
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]]) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"Start recording trip";
notification.soundName = #"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
self.recording = YES;
[self startAutoTrip];
}
}
- (void) startAutoTrip {
self.locationTracker = [[LocationTracker alloc]init];
[self.locationTracker startLocationTracking];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]]) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"Stop recording trip";
notification.soundName = #"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
[self stopAutoTrip];
self.recording = NO;
}
}
- (void)stopAutoTrip {
// stop recording the locations
CLSLog(#"Trying to stop location updates");
[self.locationTracker stopLocationTracking:self.managedObjectContext];
CLSLog(#"Stop location updates");
}
In LocationTracker.m (from tutorial cited above, change 60 sec and 10 sec time intervals to 5 sec and 2 sec). Basically these are the startLocationTracking, didUpdateLocations, and stopLocationTracking methods.
- (void)startLocationTracking {
NSLog(#"startLocationTracking");
if ([CLLocationManager locationServicesEnabled] == NO) {
NSLog(#"locationServicesEnabled false");
UIAlertView *servicesDisabledAlert = [[UIAlertView alloc] initWithTitle:#"Location Services Disabled" message:#"You currently have all location services for this device disabled" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[servicesDisabledAlert show];
} else {
CLAuthorizationStatus authorizationStatus= [CLLocationManager authorizationStatus];
if(authorizationStatus == kCLAuthorizationStatusDenied || authorizationStatus == kCLAuthorizationStatusRestricted){
NSLog(#"authorizationStatus failed");
} else {
NSLog(#"authorizationStatus authorized");
CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = 10; //meters
locationManager.activityType = CLActivityTypeAutomotiveNavigation;
locationManager.pausesLocationUpdatesAutomatically = NO;
if(IS_OS_8_OR_LATER) {
[locationManager requestAlwaysAuthorization];
}
[locationManager startUpdatingLocation];
}
}
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"locationManager didUpdateLocations");
for(int i=0;i<locations.count;i++){
CLLocation * newLocation = [locations objectAtIndex:i];
NSDate *eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (fabs(howRecent) < 10.0 && newLocation.horizontalAccuracy < 20 && locations.count > 0) {
CLLocationCoordinate2D theLocation = newLocation.coordinate;
CLLocationAccuracy theAccuracy = newLocation.horizontalAccuracy;
self.myLastLocation = theLocation;
self.myLastLocationAccuracy= theAccuracy;
CLLocationCoordinate2D coords[2];
coords[0] = ((CLLocation *)locations.lastObject).coordinate;
coords[1] = newLocation.coordinate;
[self.shareModel.myLocationArray addObject:newLocation];
}
}
//If the timer still valid, return it (Will not run the code below)
if (self.shareModel.timer) {
return;
}
self.shareModel.bgTask = [BackgroundTaskManager sharedBackgroundTaskManager];
[self.shareModel.bgTask beginNewBackgroundTask];
//Restart the locationMaanger after 1 minute (5 sec)
self.shareModel.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self
selector:#selector(restartLocationUpdates)
userInfo:nil
repeats:NO];
//Will only stop the locationManager after 10 seconds, so that we can get some accurate locations
//The location manager will only operate for 10 seconds to save battery
// 2 sec
if (self.shareModel.delay10Seconds) {
[self.shareModel.delay10Seconds invalidate];
self.shareModel.delay10Seconds = nil;
}
self.shareModel.delay10Seconds = [NSTimer scheduledTimerWithTimeInterval:2 target:self
selector:#selector(stopLocationDelayBy10Seconds)
userInfo:nil
repeats:NO];
}
- (void) restartLocationUpdates
{
NSLog(#"restartLocationUpdates");
if (self.shareModel.timer) {
[self.shareModel.timer invalidate];
self.shareModel.timer = nil;
}
CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = 10; //meters
locationManager.activityType = CLActivityTypeAutomotiveNavigation;
locationManager.pausesLocationUpdatesAutomatically = NO;
if(IS_OS_8_OR_LATER) {
[locationManager requestAlwaysAuthorization];
}
[locationManager startUpdatingLocation];
}
- (void)stopLocationTracking:(NSManagedObjectContext *)managedObjectContext {
NSLog(#"stopLocationTracking");
CLSLog(#"stopLocationTracking");
CLSLog(#"set managedObjectContext %#", managedObjectContext);
self.managedObjectContext = managedObjectContext;
if (self.shareModel.timer) {
[self.shareModel.timer invalidate];
self.shareModel.timer = nil;
}
CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
[locationManager stopUpdatingLocation];
[self saveRun];
[self sendRun];
}
Thank you all for your responses. It is possible to wake your app up from being killed/suspended/terminated using iBeacons, contrary to what Øyvind Hauge said. And unfortunately, adding the background location mode to your plist does not enable indefinite location updates, as others suggested; I was only ever able to get 3 minutes of execution using that method.
I actually found the solution to my question in this StackOverflow article. The solution is to add just a few lines of code to your app delegate - you need to start another location manager that is monitoring for significant location updates. Here are the lines of code that I added to my didFinishLaunchingWithOptions method in my AppDelegate.m file after declaring anotherLocationManager as a property...
self.anotherLocationManager = [[CLLocationManager alloc] init];
self.anotherLocationManager.delegate = self;
[self.anotherLocationManager startMonitoringSignificantLocationChanges];
I never do anything else using this location manager, I just leave it perpetually running in the background, and for some reason this enables indefinite location updates from a regular call to [locationManager startUpdatingLocation]. I am no longer having the location updates mysteriously stop after 3 minutes. It seems very strange that this was the solution, but it was pretty simple to implement, and hopefully this will help others who are dealing with the same problem.
If you set the location background mode in your plist, you can range beacons and get GPS location updates indefinitely. The key to getting this to work is starting a background thread.
You can see an example of how to do this in this blog post about extending beacon ranging on the background. While the blog post mentions this is limited to 3 minutes, when you add the location background mode to your plist, that time limit goes away.
Understand that you may not get AppStore approval for using this background mode unless Apple appreciates your justification for doing so.
So in iOS, location updates will work in background indefinitely ONLY if -
1. You have started location updates in foreground AND
2. You have added Background Location in your plist.
In your case, the OS is waking you up in background and as you've said correctly, you only get 10 seconds of execution time before the OS suspends your app. The workaround for this is basically starting a background task, as you have done to get additional 180 seconds of execution time (this number can change based on OS version).
To understand your issue in depth, you need to know that there are only certain events(like geofence/ibeacon/significant location update) which will wake your app in background, let us call them "wakeup" events. Once any of these event occurs, you have a maximum of 180 seconds of background execution time (using background task) after which your app WILL be suspended, unless any of these events is triggered again, after which you need to restart your background task. I'm not sure how your application works exactly, but if you can ensure that you keep getting these "wakeup" events from the OS for the duration for which you need location updates, you can pretty much keep your app awake in background.
Just to add, I've seen a lot of blog posts that claim that keeping a timer and restarting location updates periodically using that timer works, but I have never been able to use it successfully.
I wrote a simple iOS application that retrieves location information and then uses the location to request Yahoo Weather.
The problem is that even when I call the Core Location in the viewDidLoad, it won't give me the result immediately.
So why can't I get the location information?
How can I get the location information in viewDidLoad?
The pseudocode currently is something like:
- (void)viewDidLoad
{
[super viewDidLoad];
self.locManager = [[CLLocationManager alloc] init];
self.locManager.delegate = self;
self.locManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locManager.distanceFilter = 100;
[self.locManager startUpdatingLocation];
//won't get the current location right now, so the output will be null
NSLog(#"Current Location Longitude: %#", self.longitudeString);
NSLog(#"Current Location Latitude: %#", self.latitudeString);
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = [locations lastObject];
self.longitudeString = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
self.latitudeString = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
}
Location updates are not provided as instantly as you are expecting, you need to wait few seconds (2-3 or may be more) to get precise location update. If you want to have location data in viewDidLoad then you should init your location manager (and call startUpdatingLocation) before invoking the ViewController (since then it is not guaranteed that you will have location-data in viewDidLoad).
My service needs gps. I actually implemented a service, which turns the gps on, checks if the location is valid, and then goes to sleep for some time. (Starting with 5 seconds, and if no movement is detected, it can sleep up to a minute)
After that, I start the gps again, and get a new location.
But the battery drain is still high! Ive used locMgr.distanceFilter = 10.0f and desiredAccurady = NearestTenMeters.
How do I minimize battery drain more?
This is how I handle it. After I grab the location I store it in a NSDictionary. Then if I need to location again I return the NSDictionary instead of turning the GPS back on. After 2 minutes I reset the NSDictionary (you can adjust the time to what best suites you app). Then the next time I need the location after the NSDictionary was reset I grab a new location from the GPS.
- (NSDictionary *) getCurrentLocation {
if (self.currentLocationDict == nil) {
self.currentLocationDict = [[NSMutableDictionary alloc] init];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
locationManager.distanceFilter = kCLDistanceFilterNone;
[locationManager startUpdatingLocation];
CLLocation *myLocation = [locationManager location];
[self.currentLocationDict setObject:[NSString stringWithFormat:#"%f", myLocation.coordinate.latitude] forKey:#"lat"];
[self.currentLocationDict setObject:[NSString stringWithFormat:#"%f", myLocation.coordinate.longitude] forKey:#"lng"];
[locationManager stopUpdatingLocation];
[locationManager release];
//Below timer is to help save battery by only getting new location only after 2 min has passed from the last time a new position was taken. Last location is saved in currentLocationDict
[NSTimer scheduledTimerWithTimeInterval:120 target:self selector:#selector(resetCurrentLocation) userInfo:nil repeats:NO];
}
return self.currentLocationDict;
}
- (void) resetCurrentLocation {
NSLog(#"reset");
[currentLocationDict release];
self.currentLocationDict = nil;
}