What's the easiest way to get the current location of an iPhone?

I already know how to use the CLLocationManager, so I could do it the hard way, with delegates and all that.

But I'd like to have a convenience method that just gets the current location, once, and blocks until it gets the result.


Solution 1:

What I do is implement a singleton class to manage updates from core location. To access my current location, I do a CLLocation *myLocation = [[LocationManager sharedInstance] currentLocation]; If you wanted to block the main thread you could do something like this:

while ([[LocationManager sharedInstance] locationKnown] == NO){
   //blocking here
   //do stuff here, dont forget to have some kind of timeout to get out of this blocked    //state
}

However, as it has been already pointed out, blocking the main thread is probably not a good idea, but this can be a good jumping off point as you are building something. You will also notice that the class I wrote checks the timestamp on location updates and ignores any that are old, to prevent the problem of getting stale data from core location.

This is the singleton class I wrote. Please note that it is a little rough around the edges:

#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>

@interface  LocationController : NSObject <CLLocationManagerDelegate> {
    CLLocationManager *locationManager;
    CLLocation *currentLocation;
}

+ (LocationController *)sharedInstance;

-(void) start;
-(void) stop;
-(BOOL) locationKnown;

@property (nonatomic, retain) CLLocation *currentLocation;

@end
@implementation LocationController

@synthesize currentLocation;

static LocationController *sharedInstance;

+ (LocationController *)sharedInstance {
    @synchronized(self) {
        if (!sharedInstance)
            sharedInstance=[[LocationController alloc] init];       
    }
    return sharedInstance;
}

+(id)alloc {
    @synchronized(self) {
        NSAssert(sharedInstance == nil, @"Attempted to allocate a second instance of a singleton LocationController.");
        sharedInstance = [super alloc];
    }
    return sharedInstance;
}

-(id) init {
    if (self = [super init]) {
        self.currentLocation = [[CLLocation alloc] init];
        locationManager = [[CLLocationManager alloc] init];
        locationManager.delegate = self;
        [self start];
    }
    return self;
}

-(void) start {
    [locationManager startUpdatingLocation];
}

-(void) stop {
    [locationManager stopUpdatingLocation];
}

-(BOOL) locationKnown { 
     if (round(currentLocation.speed) == -1) return NO; else return YES; 
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    //if the time interval returned from core location is more than two minutes we ignore it because it might be from an old session
    if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 120) {     
        self.currentLocation = newLocation;
    }
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    UIAlertView *alert;
    alert = [[UIAlertView alloc] initWithTitle:@"Error" message:[error description] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
    [alert release];
}

-(void) dealloc {
    [locationManager release];
    [currentLocation release];
    [super dealloc];
}

@end

Solution 2:

There is no such convenience and you shouldn't create your own. "Blocks until it gets the result" is extremely bad programming practice on a device like the iPhone. It can take seconds to retrieve a location; you should never make your users wait like that, and delegates ensure they don't.

Solution 3:

There are no "convenience methods" unless you code them yourself, but you'd still need to implement the delegate methods in whatever custom code you use to make things "convenient."

The delegate pattern is there for a reason, and as delegates are a big part of Objective-C, I recommend you get comfortable with them.