Attempting to share location services across various controllres can lead to much duplicate code. If you're simply interested in receiving updates about the user's current location, building a central location for retrieving this information can be quite useful.
In my app, I settled on building a Singleton that wraps the CLLocationManager. This ends up being fairly simple and looks like this...
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
@interface LocationService : NSObject <CLLocationManagerDelegate>
+(LocationService *) sharedInstance;
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) CLLocation *currentLocation;
- (void)startUpdatingLocation;
@end
In the above interface we create a class method called sharedInstance. I discuss the singleton pattern in a previous article.
But more importantly, we have implement the CLLocationManagerDelegate directly on this class. Therefore, this class will receieve the updates from the LocationManager and will update the currentLocation
property.
The implementation code looks something like this:
#import "LocationService.h"
@implementation LocationService
+(LocationService *) sharedInstance
{
static LocationService *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
- (id)init {
self = [super init];
if(self != nil) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 100; // meters
self.locationManager.delegate = self;
}
return self;
}
- (void)startUpdatingLocation
{
NSLog(@"Starting location updates");
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
{
NSLog(@"Location service failed with error %@", error);
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray*)locations
{
CLLocation *location = [locations lastObject];
NSLog(@"Latitude %+.6f, Longitude %+.6f\n",
location.coordinate.latitude,
location.coordinate.longitude);
self.currentLocation = location;
}
@end
In this service, we implement the singleton in the class sharedInstance
method.
In the initialization method we create an initialize the CLLocationManager instance.
We also implement any delegate methods in this class. Of importance is that on the locationManager:didUpdateLocations method, we set the currentLocation
property that can be used by other services.
So that begs the question, how do we use the service? Typically, we would access the single method by using the sharedInstance static method. This method would then provide us with the current location...
[LocationService sharedInstance].currentLocation
However, because CLLocationManager is async, we can't just assume that we have a value in currentLocation
. We need a way to observe this property.
A nice article describes the techniques we can use for observing objects. Enter Key Value Observing. A simple technique for doing example what we want, monitoring a property.
In our case, the consumer of our service, a controller for instance, simply adds a listener for changes to the currentLocation property.
[[LocationService sharedInstance] addObserver:self forKeyPath:@"currentLocation" options:NSKeyValueObservingOptionNew context:nil];
This will monitor the Singleton object and watch the currentLocation property for changes. Activity will be fired in the observeValueForKeyPath method...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"currentLocation"]) {
// do some stuff
}
}
We can filter what "key" and what "object" did the firing in conditional statements and execute our code accordingly.
That's all there is too it!