How can I make my application update location only when a button is pressed?
I have a button named "REFRESH". Everytime this button is pressed, I want to show my user their location. For example, 51 Bourke Street, Victoria.
However, I do not want to update my location regularly. I want to update its location only when the button is pressed, to save battery power.
What do you think? Am I doing it correctly?
I have these classes:
VoteViewController.h and VoteViewController.m
CoreLocationController.h and CoreLocationController.m
This is what I have:
VoteViewController.h class
#interface VoteViewController : UIViewController <CoreLocationControllerDelegate>
{
CoreLocationController *coreController;
}
- (void)locationUpdate:(CLLocation *)location;
- (void)locationError:(NSError *)error;
- (void)geoReverseAddress:(MKPlacemark *)placeMark;
- (IBAction)refreshButtonPressed;
VoteViewController.m class
- (void)viewDidLoad
{
[super viewDidLoad];
coreController = [[CoreLocationController alloc] init];
coreController.delegate = self;
}
- (IBAction)refreshButtonPressed
{
NSLog(#"Refresh Button pressed");
label.text = [NSString stringWithString:#""];
[coreController.locationManager startUpdatingLocation];
}
- (void)locationUpdate:(CLLocation *)location
{
comments.text = [location description];
[coreController.locationManager stopUpdatingLocation];
}
- (void)locationError:(NSError *)error
{
comments.text = [error description];
[coreController.locationManager stopUpdatingLocation];
}
- (void)geoReverseAddress:(MKPlacemark *)placeMark
{
label.text = [NSString stringWithFormat:#"%# %#, %#", [placeMark subThoroughfare],
[placeMark thoroughfare], [placeMark locality]];
}
CoreLocationController.h class
#protocol CoreLocationControllerDelegate <NSObject>
#required
- (void)locationUpdate:(CLLocation *)location;
- (void)locationError:(NSError *)error;
- (void)geoReverseAddress:(MKPlacemark *)placeMark;
#end
#interface CoreLocationController : NSObject <CLLocationManagerDelegate, MKReverseGeocoderDelegate>
{
CLLocationManager *locationManager;
id delegate;
MKReverseGeocoder *reverse;
}
#property(nonatomic, retain) CLLocationManager *locationManager;
#property(nonatomic, retain) id delegate;
#end
CoreLocationController.m class
-(id) init
{
self = [super init];
if (self != nil)
{
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self;
self.locationManager.distanceFilter = kCLHeadingFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"Update location");
[self.delegate locationUpdate:newLocation];
reverse = [[MKReverseGeocoder alloc] initWithCoordinate:[newLocation coordinate]];
reverse.delegate = self;
[reverse start];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
[self.delegate locationError:error];
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
[self.delegate locationError:error];
[reverse cancel];
[reverse release];
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
[self.delegate geoReverseAddress:placemark];
[reverse cancel];
[reverse release];
}
When you first fire up CLLocationManager, you're very likely to get one stale location from the last time it ran. Once that's out of the way, you're going to start getting very inaccurate locations while the device uses WiFi sniffing and cell triangulation, while the GPS looks for a fix.
So in your didUpdateToLocation method, you probably want to throw away the first hit, and then test the .horizontalAccuracy value of your newLocation object for a low enough value to trust.
Apart from that, I don't see anything bad about what you've sent here. I'm not sure I'd go to the trouble of wrapping the location fetching work in its own class, I'd probably just do that out in my viewController. But that's a style choice. If you're reusing this functionality elsewhere, what you've got here is obviously the way to go.
Related
My core location works but I receive a warning at this line of code. locationManager.delegate = self; The warning is Assigning to 'id' from incompatible type 'phoneLocationViewController *const __strong'. How do I get rid of this warning? Here is my code
.h
#interface phoneLocationViewController : UIViewController {
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation;
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
#property (nonatomic, retain) CLLocationManager *locationManager;
#property (nonatomic, retain) CLLocation *currentLocation;
.m
#synthesize locationManager, currentLocation;
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation {
self.currentLocation = newLocation;
if(newLocation.horizontalAccuracy <= 100.0f) { [locationManager stopUpdatingLocation]; }
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
if(error.code == kCLErrorDenied) {
[locationManager stopUpdatingLocation];
} else if(error.code == kCLErrorLocationUnknown) {
// retry
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error retrieving location"
message:[error description]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
}
- (void)viewDidLoad
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self; (I GET THE WARNING HERE)
[locationManager startUpdatingLocation];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[locationManager stopUpdatingLocation];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
Declare your class as implementing the protocol of the delegate of the location manager.
#interface phoneLocationViewController : UIViewController <CLLocationManagerDelegate> {
You should add the CLLocationManagerDelegate to your interface declaration.
#interface phoneLocationViewController : UIViewController <CLLocationManagerDelegate> {
....
}
in .h file
#import <MapKit/MapKit.h>
#interface FirstViewController : UIViewController <MKMapViewDelegate,MKReverseGeocoderDelegate,CLLocationManagerDelegate>
{
}
in .m file
-(void)viewDidLoad
{
CLLocationManager *locationManager = [[[CLLocationManager alloc] init] autorelease];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[super viewDidLoad];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
MKReverseGeocoder *geoCoder = [[MKReverseGeocoder alloc] initWithCoordinate:newLocation.coordinate];
geoCoder.delegate = self;
[geoCoder start];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"locationManager:%# didFailWithError:%#", manager, error);
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
MKPlacemark * myPlacemark = placemark;
NSString *kABPersonAddressCityKey;
NSString *city = [myPlacemark.addressDictionary objectForKey:(NSString*) kABPersonAddressCityKey];
lblAddress.text = city;
NSLog(#"city detail is:--> %#",city);
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
NSLog(#"reverseGeocoder:%# didFailWithError:%#", geocoder, error);
}
This is the code which I have done to get the current location of user & print it in the label.
But the CLLocationManager delegate method (which are written above) is not called & I am not able to get the current address.
Please help me out.
Where I am doing mistake...? guide me.
Thanks.
Instead of autoreleasing the CLLocationManager instance, assign it to an ivar in your class. Then release it in -dealloc as usual (or in one of the delegate methods if you don't need it any longer).
I suspect your location manager is getting deallocated on the next turn of the run loop before having an opportunity to fire off its delegate methods.
In your method viewDidLoad:
-(void)viewDidLoad
{
CLLocationManager *locationManager = [[[CLLocationManager alloc] init] autorelease];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
myMapView.showsUserLocation = YES; // add this line
[super viewDidLoad];
}
And you are done!!!
Is CLGeocoder just that coarse right now? I was expecting something relatively close to a street address. I'm testing on the 5.1 simulator and using ARC. I made a quick test project right now with the following if that helps:
- (IBAction)getLocationPressed {
if ([CLLocationManager locationServicesEnabled] &&
[CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
[self.geoCoder reverseGeocodeLocation:self.locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
// Note: there is NO guarantee that the CLGeocodeCompletionHandler will be invoked on the main thread
dispatch_async(dispatch_get_main_queue(),^ {
NSLog(#"placemarks count: %d", [placemarks count]);
CLPlacemark *placemark = [placemarks objectAtIndex:0];
// Note: if a poor location is specified, there may be multiple placemarks for the given location
NSString *currentAddress = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
NSLog(#"I am currently at %#", currentAddress);
self.locationLabel.text = currentAddress;
});
}];
}
}
#pragma mark - CLLocationManager Delegate Methods
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
// do something...
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error {
if (error.code == kCLErrorDenied) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error!"
message:#"this can not work without location services enabled"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
#pragma mark - Lifecycle Methods
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager.delegate = self;
self.locationManager.purpose = REASON_FOR_USING_LOCATION;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;//kCLLocationAccuracyNearestTenMeters;
[self.locationManager startUpdatingLocation];
}
Thanks
YESSSS! It turns out that I just wasn't enabling location in the simulator. It actually works if I test it on my device. Here's an example of with the full .m file if it helps anyone. I've set up a couple labels and a rounded rect button that you'll see from the code:
#define REASON_FOR_USING_LOCATION (#"to find the closest widget")
#interface ViewController () <CLLocationManagerDelegate>
#property (weak, nonatomic) IBOutlet UILabel *locationLabel;
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) CLGeocoder *geoCoder;
#end
#implementation ViewController
#pragma mark - Getters/Setters
#synthesize locationLabel = _locationLabel;
#synthesize locationManager = _locationManager;
#synthesize geoCoder = _geoCoder;
// lazily instantiate as required
- (CLGeocoder *)geoCoder {
if (!_geoCoder) _geoCoder = [CLGeocoder new];
return _geoCoder;
}
- (CLLocationManager *)locationManager {
if (!_locationManager) _locationManager = [CLLocationManager new];
return _locationManager;
}
#pragma mark - Target/Action Methods
- (IBAction)clearLocationPressed {
self.locationLabel.text = #"";
}
- (IBAction)getLocationPressed {
if ([CLLocationManager locationServicesEnabled] &&
[CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
[self.geoCoder reverseGeocodeLocation:self.locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
// Note: there is NO guarantee that the CLGeocodeCompletionHandler will be invoked on the main thread
dispatch_async(dispatch_get_main_queue(),^ {
NSLog(#"placemarks count: %d", [placemarks count]);
CLPlacemark *placemark = [placemarks objectAtIndex:0];
// Note: if a poor location is specified, there may be multiple placemarks for the given location
NSString *currentAddress = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
NSLog(#"I am currently at %#", currentAddress);
self.locationLabel.text = currentAddress;
});
}];
}
}
#pragma mark - CLLocationManager Delegate Methods
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
// do something...
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error {
if (error.code == kCLErrorDenied) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error!"
message:#"this can not work without location services enabled"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
#pragma mark - Lifecycle Methods
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager.delegate = self;
self.locationManager.purpose = REASON_FOR_USING_LOCATION;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;//kCLLocationAccuracyNearestTenMeters;
[self.locationManager startUpdatingLocation];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)viewDidUnload {
[self setLocationLabel:nil];
[self setLocationManager:nil];
[self setGeoCoder:nil];
[super viewDidUnload];
}
#end
Hi I have a requirement where I need to update user's location to the server while app is running in the background. I have used the code below to test it in the car and have found few discrepancies in the lat and long that are reported when the - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation: is fired.The coordinates are way far of to where I actually am at that moment.
I know the didupdatelocation uses cell tower readings hence not very accurate.I was wondering if there is anyway I can start [locationManager startUpdatingLocation] when the - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:; and then get the readings which are much more accurate.
Please any help or directions would be great and highly appreciated.
#import "LocationsAppDelegate.h"
#import "RootViewController.h"
#implementation LocationsAppDelegate
#synthesize window;
#synthesize navigationController;
-(void)initLocationManager {
if (locationManager == nil) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; // 100 m
// [locationManager startUpdatingLocation];
[locationManager startMonitoringSignificantLocationChanges];
}
}
- (void)saveCurrentData:(NSString *)newData {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *savedData = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:#"kLocationData"]];
[savedData addObject:newData];
[defaults setObject:savedData forKey:#"kLocationData"];
[savedData release];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
//check if the data is less than 15 sec ago
if (abs(howRecent) > 15.0)
{
NSLog(#"old data latitude %+.6f, longitude %+.6f\n",
newLocation.coordinate.latitude,newLocation.coordinate.longitude);
}else{
[locationManager startUpdatingLocation];
NSDateFormatter * formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
NSString *locationData = [NSString stringWithFormat:#"%.6f, %.6f, %#",newLocation.coordinate.latitude, newLocation.coordinate.longitude,[formatter stringFromDate:newLocation.timestamp]];
[self saveCurrentData:locationData];
[locationManager stopUpdatingLocation];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSString *errorData = [NSString stringWithFormat:#"%#",[error localizedDescription]];
NSLog(#"%#", errorData);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (![CLLocationManager significantLocationChangeMonitoringAvailable]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Sorry" message:#"Your device won't support the significant location change." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
return YES;
}
[self initLocationManager];
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
[locationManager release];
[navigationController release];
[window release];
[super dealloc];
}
#end
The behaviour of startMonitoringSignificantLocationChanges is not affected by the distanceFilter or desiredAccuracy properties.
So if you want to retrieve more accurated results, call startUpdatingLocation, and by playing with the distanceFilter and desiredAccuracy properties (kCLLocationAccuracyBestForNavigation, kCLLocationAccuracyBest) are the most accurated, but consumes more power, so play with them.
I didn't understand exactly the behaviour of your code. Why you call startMonitoringSignificantLocationChanges instead of startUpdatingLocation, but you call it inside the delegate method?
I want to calculate the maximum altitude, minimum altitude, and average altitude of the current location through CLLocationManager. I know how to calculate the altitude using the following code:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface test : UIViewController <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
CLLocation *startingPoint;
IBOutlet UILabel *altitudeLabel;
}
#property (retain, nonatomic) CLLocationManager *locationManager;
#property (retain, nonatomic) CLLocation *startingPoint;
#property (retain, nonatomic) UILabel *altitudeLabel;
#end
//this is my test.h class
#import "test.h"
#implementation test
#synthesize locationManager;
#synthesize startingPoint;
#synthesize altitudeLabel;
#pragma mark -
- (void)viewDidLoad {
self.locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)dealloc {
[locationManager release];
[startingPoint release];
[altitudeLabel release];
[super dealloc];
}
#pragma mark -
#pragma mark CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if (startingPoint == nil)
self.startingPoint = newLocation;
NSString *altitudeString = [[NSString alloc] initWithFormat:#"%gm", newLocation.altitude];
altitudeLabel.text = altitudeString;
[altitudeString release];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSString *errorType = (error.code == kCLErrorDenied) ? #"Access Denied" : #"Unknown Error";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error gettingg location from Core Location" message:errorType delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil];
[alert show];
[alert release];
}
#end
Through this I only get the altitude value, but I need to know how to calculate average altitude, minimum altitude, and maximum altitude. Does anyone know how to do this?
Instead of storing all the altitudes in an array as others have suggested, you could just store the current average/min/max and update it as you go.
int numUpdates = 0;
double averageAlt = 0.0;
double minAlt = DBL_MAX;
double maxAlt = DBL_MIN;
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if (newLocation.altitude < minAlt) {
minAlt = newLocation.altitude;
}
if (newLocation.altitude > maxAlt) {
maxAlt= newLocation.altitude;
}
double sum = numUpdates * averageAlt;
sum+=newLocation.altitude;
numUpdates++;
averageAlt = sum / numUpdates;
}
I describe how to get min in the minAltitude method. I'll leave it to you to find max and average.
in .h:
NSMutableArray *altitudes;
in .m:
- (void) viewDidLoad {
[super viewDidLoad];
altitudes = [[NSMutableArray alloc] init];
}
- (void) dealloc {
[altitudes release];
[super dealloc];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
[altitudes addObject:[NSNumber numberWithDouble:newLocation.altitude]];
}
- (double) minAltitude
{
double min = DBL_MAX;
double value;
NSNumber *altitude;
for (altitude in altitudes) {
value = [altitude doubleValue];
if (value < min) {
min = value;
}
}
return min;
}