I am using CoreLocation to grab the users location. I need to grab the users location then send that to the server. I have it all working, but! I am implementing locationManager:(CCLLocationManager *)manager...
My url request is hitting before the location is found. Whats the best way to:
Request location
Store in string
Send string to request
I want to ensure the location is found before its sent. Do I check against the instance of the class with a conditional and inside the block hit the server. I dont need to keep updating, just grab once and stopUpdatingLocation
Was going through this post previously: Getting Longitude/Latitude of the User When the viewDidLoad
What I have so far:
- (void)viewDidLoad {
[super viewDidLoad];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[locationManager startUpdatingLocation];
...more below... server request below here
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
NSLog(#"found!");
[locationManager stopUpdatingLocation];
}
}
Thoughts on how to do that? I think the conditional may work, but not sure if there was a proper way to handle that using the methods provided from CoreLocation.
Create a new method that will perform your location-dependent code. Then once you have the location (I.e. After the NSLog(#"found!"); line), call this method.
Note that the method may get called multiple times as the location accuracy improves - you might want to handle this.
For example:
- (void)viewDidLoad {
[super viewDidLoad];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil)
{
NSLog(#"found!");
[self handleLocation:newLocation];
[locationManager stopUpdatingLocation];
}
}
- (void)handleLocation:(CLLocation*)location
{
// handling code here
}
The location manager is asynchronous. You have to ask for location updates, then wait for it to call you back.
Worse, the first several updates are usually garbage that needs to be thrown away.
The first location update frequently has a timestamp that is hours, days, or even weeks old. You first have to check to make sure the timestamp is in the last few seconds.
Once you've done that, you need to check the horizontal accuracy, and make sure the reading is accurate enough. Often when you first start the location manager the first few readings have an accuracy value of more than a kilometer, which is awful. (Accuracy is really a radius value. You can only be sure that the location you get is somewhere inside a circle with the specified radius.) You need to come up with an app-specific accuracy reading that is "good enough", and throw away accuracy readings until you get one that is good enough. You also need to check for negative accuracy readings, which mean that the GPS is returning invalid values.
Next, you have to allow for the case where the GPS doesn't settle down in a reasonable time. (Sometimes it can take multiple minutes, or simply fail to get a good reading.) In that case you need to handle it as a failure.
So, to handle all that you need to write your locationManager:didUpdateLocation: method to check the time stamp and the horizontal accuracy to make sure the reading is actually good. You also need to make sure you time out and report a failure if you can't get a decent location reading after a reasonable wait. One way to do this is to start a "give up" timer when you first begin location updates, and after you get a good reading, kill it. If the timer fires, stop location updates and report a "can't get a good location reading" error to your user. Understand that it's not uncommon to take 10 or 15 seconds to get a decent accuracy reading.
Related
I have a location app that needs to get accurate location periodically. Currently I am getting constantly getting location in didUpdateLocation but I only ever log the location every 5 seconds. I am interested in a solution that gets accurate location periodically or on signification change. I would like either or both of these scenarios:
(by very accurate, I need 10m of desired accuracy)
Get a very accurate location every 5 seconds
Notify/callback if user moves a threshold ( eg moves 5 - 10 meters)
The app needs to work when backgrounded as well and location must still be logged if user switches to another app.
I was considering turning on/off location every 5 seconds but was not sure if that is the best practice. I also know there is also allowDeferredLocationUpdatesUntilTraveled but I believe that only applied to backgrounded mode. I would appreciate a solution that saves battery when the app is in use and in background mode. Please share your solutions and best practices for my use case.
I did write an app using Location services, app must send location every 10s. And it worked very well.
Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.
Steps are as follows:
Required: Register background mode for update Location.
Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:
-(void) initLocationManager
{
// Create the manager object
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
_locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
_locationManager.desiredAccuracy = 45;
_locationManager.distanceFilter = 100;
// Once configured, the location manager must be "started".
[_locationManager startUpdatingLocation];
}
To keep app run forever using "allowDeferredLocationUpdatesUntilTraveled:timeout" method in background, you must restart updatingLocation with new parameter when app moves to background, like this:
- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;
[_locationManager stopUpdatingLocation];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
}
App gets updatedLocations as normal with "locationManager:didUpdateLocations:" callback:
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// store data
CLLocation *newLocation = [locations lastObject];
self.userLocation = newLocation;
//tell the centralManager that you want to deferred this updatedLocation
if (_isBackgroundMode && !_deferringUpdates)
{
_deferringUpdates = YES;
[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
}
}
But you should handle the data in then "locationManager:didFinishDeferredUpdatesWithError:" callback for your purpose
- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
_deferringUpdates = NO;
//do something
}
NOTE: I think we should reset parameters of LocationManager each time app switches between background/forgeround mode.
Hopefully this should help
I created location manager with
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
but location updates received once per second.
Is it possible to get updates more often?
I tried to get current location in timer, but it's still updated once per second.
Here's what I did:
[self pingLocation];//viewDidLoad
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[manager stopUpdatingLocation];
manager.delegate = nil;
self.currentLocation = (CLLocation *)[locations lastObject];
}
- (void) pingLocation {
[locationManager startUpdatingLocation];
locationManager.delegate = self;
[self performSelector:#selector(pingLocation) withObject:nil afterDelay:0.5];//change to whatever you want
}
This seemed to give me fairly good results and could make it update more than once per second. However, it drains battery very drastically and is not the best practice. I would ask yourself: why do you want this? Do you really need more than one update per second?
Edit: I've just tested this again and I believe that although it appears to update every half second, it doesn't really. For example:
30.00000, 60.00000
30.00000, 60.00000
30.00032, 60.00056
30.00032, 60.00056
Thus, you really can't update faster than a second, sorry to say.
According to the CLLocation.startUpdatingLocation documentation:
Calling this method several times in succession does not automatically
result in new events being generated. Calling stopUpdatingLocation in
between, however, does cause a new initial event to be sent the next
time you call this method.
You will probably have to also set distanceFilter to none. However, you can imagine these two will help churn through a battery.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[locationManager startUpdatingLocation];
}
By this, location manage get update more often. Because every time when it get update it will request for another location frequently.
{
...
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(#"myLocation1: %#",[locations lastObject]);
myLocation = [locations lastObject];
NSLog(#"myLocation2: %#",[locations lastObject]);
[manager stopUpdatingLocation];
[self doSomethingWithLocation];
}
Currently I'm in the location 40.000,40.000.
I'm closing my app and change location to 10.000,10.000
When entering the app again and running [locationManager startUpdatingLocation]; my log will show:
myLocation1: <+40.00000000,+40.00000000>
myLocation2: <+40.00000000,+40.00000000>
If I'll trigger [locationManager startUpdatingLocation]; again my log will show:
myLocation1: <+10.00000000,+10.00000000>
myLocation2: <+10.00000000,+10.00000000>
How can I call didUpdateLocations once and still get the current location?
Should I use another delegate?
I guess I could place stopUpdatingLocation inside doSomethingWithLocation and run doSomethingWithLocation after some sort of delay in order for the right location to be updated but I'm sure that's not the way it's meant to be.
Thanks
Leave the location manager running for a while (e.g. 30 seconds), setting a timer to tell it to stop. The location manager updates are like pancakes, the first one you get isn't always the best.
The first update you are seeing is likely a "stale" location, which was determined many minutes ago when location services were last powered up. Or it may be a very inaccurate location determined using cell-tower positioning, for example. If you just need to get the device's current location, using Core Location directly requires a good deal of code because you must handle these cases. (The CLLocationManager API appears to be built for apps that need continuous location updates, like turn-by-turn GPS navigation apps.)
Instead of using CLLocationManager directly, I suggest you take a look at using an open source component such as INTULocationManager which will handle all of this work for you and make it trivially simple to request one or more discrete requests for the device's current location.
In this case you should check timestamp of location. User does not move on such distances so quickly.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *location = [locations lastObject];
if(fabs([location.timestamp timeIntervalSinceNow]) < 5)//seconds
{
myLocation = location;
manager.delegate = nil;
[manager stopUpdatingLocation];
[self doSomethingWithLocation];
}
}
I have three points, but really only the first one is the most important
In order to preserve battery life, I'm trying to have the locationManager turn on just long enough to get the user's location and then shut off. What is the best means to do so?
I don't always need to know the user's location, but having a relatively accurate location when the user hits search is important (within perhaps a couple dozen city blocks, 10,000 meters, maybe even less accurate).
I'm sending a request to a server and then getting the results, and after THAT delay, I need a more accurate version of the user location (within 100 meters is fine).
I don't know how much of this is too nitpicky, but if the last two points are possible/efficient battery-wise, then please let me know how to do it!
You can easily stop tracking location when your CLLocationManager's delegate receives a location update meeting your desired accuracy.
To start updating location, do something like:
CLLocationManager *locationManager = [CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[locationManager startUpdatingLocation];
Then implement the appropriate CLLocationManagerDelegate method:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *foundLocation = [locations lastObject];
if (foundLocation && foundLocation.horizontalAccuracy < kYourDesiredAccuracyInMetres)
{
[manager stopUpdatingLocation];
//Do whatever else with the location you've established
}
}
You should be able to tweak this meet your requirements, by checking the accuracy of the returned locations returned and either stopping the updates or letting them continue (if you need better accuracy).
It's also a good idea to set a timer when you start updating location, and stop updates if you haven't found a location within a set amount of time. You should also implement the locationManager:didFailWithError: delegate method to check whether you can access location services at all.
LocationManager could be stopped after location information of user has been retrieved .
CLLocationManager* locationManager = [ [ CLLocationManager alloc] init];
locationManager.delegate = self; //we must implement the protocol
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.headingFilter = kCLHeadingFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
//To turn on gps (if it isn't on already)
[locationManager startUpdatingLocation];
//To turn gps off (if no other apps are listening)
[locationManager stopUpdatingLocation];
I'm looking for an open source app or library to track user location in the background. Now I'm trying to do it with CLLocation and background tasks, but accuracy is not enough for my case. Could you explain, how apps, like "moves", "runkeeper", "endmondo", creates my route? Should I use Accelerometer or/and compass to create a route between CLLocation background points?
Some code:
//location manager init
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.delegate = self;
#pragma mark - CLLocationManager Delegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if ([self isInBackground]) {
if (self.locationUpdatedInBackground) {
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
self.locationUpdatedInBackground(newLocation);
[self endBackgroundTask];
}
} else {
if (self.locationUpdatedInForeground) {
self.locationUpdatedInForeground(newLocation);
}
}
}
UPD:
Justed tested my app with next properties
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.activityType = CLActivityTypeFitness;
self.locationManager.pausesLocationUpdatesAutomatically=NO;
In this case I have about 10 fired events during 1,5 hour trip
Use
kCLLocationAccuracyBestForNavigation
check this.
You need to add in your "Info.plist" file the key UIBackgroundModes (array) with the value "location" (app registers for location updates).
You can check all background modes here.
So your app uses location services. Then please read the Location Awareness Programming Guide.
You need to make some changes to your Info.plist:
If your app relies on location services to function properly, add location-services to UIRequiredDeviceCapabilities
if your app requires GPS hardware, add gps to UIRequiredDeviceCapabilities
if you need to run your app longer then 10 minutes in the background, add location to UIBackgroundModes. Then your location manager will deliver locations beyond the 10-minute-limit.
you should also set NSLocationUsageDescription (can also be localized)
Getting Location Events in the Background
If your app needs location updates delivered whether the app is in the foreground or background, there are multiple options for doing so. The preferred option is to use the significant location change service to wake your app at appropriate times to handle new events. However, if your app needs to use the standard location service, you can declare your app as needing background location services.
An app should request background location services only if the absence of those services would impair its ability to operate. In addition, any app that requests background location services should use those services to provide a tangible benefit to the user. For example, a turn-by-turn navigation app would be a likely candidate for background location services because of its need to track the user’s position and report when it is time to make the next turn.
Your problem is the background handler. Remove it and enable gps background mode in plist file. then you should get full power gps all the time.
Set property pausesLocationUpdatesAutomatically=NO
This is new in ios6.
From CLLocationManager:
Allowing the location manager to pause updates can improve battery
life on the target device without sacrificing location data. When this
property is set to YES, the location manager pauses updates (and
powers down the appropriate hardware) at times when the location data
is unlikely to change. For example, if the user stops for food while
using a navigation app, the location manager might pause updates for a
period of time. You can help the determination of when to pause
location updates by assigning a value to the activityType property.
The default value of this property is YES.
For analysis add these methods to your LocationManager delegate:
- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager {
NSLog(#"locMan: locationManagerDidPauseLocationUpdates");
}
- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager {
NSLog(#"locMan: locationManagerDidResumeLocationUpdates");
}
You can set up monitoring location stuff in you VC as below
in viewDidLoad method do as below
CLLocationManager locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;(Accuracy according to your need)
[locationManager startUpdatingLocation];
than you have to overrite below two optional delegate methods of CLLocationManagerDelegate protocol
for iOS6+
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{}
and for iOS 2 to 6
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
in these methods you will get updated location. use it as you want.
every time location updated these method get calls.
you don't ask for code. You ask for: "I'm looking for an open source app or library"
It may help you to visit this website.
hope it helps you,
Also a tutorial.
Here was my solution to this,
Declare the instance variable:
CLLocationManager *locationManager;
Be sure to include the delegate
<CLLocationManagerDelegate>
In viewDidLoad:
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move, location is updated
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // get best current locaton coords
locationManager.headingFilter = 1;
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
Implement the delegate method:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
int degrees = newLocation.coordinate.latitude;
double decimal = fabs(newLocation.coordinate.latitude - degrees);
int minutes = decimal * 60;
double seconds = decimal * 3600 - minutes * 60;
NSString *lat = [NSString stringWithFormat:#"%d° %d' %1.4f\"",
degrees, minutes, seconds];
NSLog(#" Current Latitude : %#",lat);
latitudeLocation.text = lat;
degrees = newLocation.coordinate.longitude;
decimal = fabs(newLocation.coordinate.longitude - degrees);
minutes = decimal * 60;
seconds = decimal * 3600 - minutes * 60;
NSString *longt = [NSString stringWithFormat:#"%d° %d' %1.4f\"",
degrees, minutes, seconds];
NSLog(#" Current Longitude : %#",longt);
longitudeLocation.text = longt;
}
Disclaimer: I work for Cintric
We also wanted to be able to accurately track a users location in background (even after the app had been killed). We spent a long time solving the problem, especially focusing on battery drain.
We posted a great blog post on how we solved it. And also are providing a drag and drop SDK solution. It's not open source but you can integrate it for free:
You can download the .framework file, drag it into your project and initialize with one line of code:
[CintricFind initWithApiKey:#"YOUR_API_KEY_HERE" andSecret:#"YOUR_SECRET_HERE"];
You can get an API key for free. There are docs explaining everything here.