0

I am trying to get the user's current location on multiple controller, they are part of navigation controller, tabbarcontroller, presentmodel controller, so basically I really cannot do something like self.navigationcontroller or self.tabBarController.

What i did was I created a LocationHelper and a LocationHelperDelegate

@protocol LocationHelperDelegate 
@required
- (void)locationUpdate:(CLLocation *)location; // Our location updates are sent here
- (void)locationError:(NSError *)error; // Any errors are sent here
@end

@interface LocationHelper : NSObject <CLLocationManagerDelegate>{
CLLocationManager *locationManager;
CLLocation *currentLocation;
id delegate;
}

@property (nonatomic, retain) CLLocation *currentLocation;
@property (nonatomic, retain) CLLocationManager *locationManager;
@property (nonatomic, assign) id delegate;
@end

And then in my .m file I do the following

-(id) init {
    self = [super init];
    if(self != nil){
        self.locationManager = [[[CLLocationManager alloc] init] autorelease];
        self.locationManager.delegate = self;
    }
    return self;
 }

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation    
   *)newLocation fromLocation:(CLLocation *)oldLocation {
    if([self.delegate conformsToProtocol:@protocol(LocationHelperDelegate)]) { 
        [self.delegate locationUpdate:newLocation];
    }
   }

And then now this is what I am doing in all my controller where I need a location update. In the init method

    LocationHelper* locationHelper = [[LocationHelper alloc]init];
locationHelper.delegate = self;
[locationHelper.locationManager startUpdatingLocation];

and then I implement

 - (void)locationUpdate:(CLLocation *)location {
     latitude = location.coordinate.latitude;
     longitude = location.coordinate.longitude;
     [locationHelper.locationManager stopUpdatingLocation];
 }

I am 100% sure this is not the right way for doing it, but I have no clue how I should be doing this. Basically all I need is to do startUpdatingLocation once and then all my controller get a notification for locationUpdate, and when every one has received the notification stopUpdatingLocation.

I was thinking about making the LocationHelper class has a singleton but then when all the controller gets the instance of it can they set them self as a delegate, that doesn't seems right because delegate is one of the instance variable in the LocationHelper and it can hold only one value.

So as you can see I am confused, please help.

Yogesh
  • 1,333
  • 3
  • 16
  • 35

3 Answers3

4

The approach I took to catching responses from a multi-threaded network request was using NSNotificationCenter. It was unbelievable easy to implement and doesn't require you to keep a variable in a specific place, such as AppDelegate. This obviously makes things more flexible if you decide to refactor the code later on.

To dispatch a notification, you can use code like this. Note that we pass along custom data using the userInfo: paramater.

[[NSNotificationCenter defaultCenter] postNotificationName:@"requestFinished"
                                                    object:self
                                                  userInfo:resultDictionary];

To subscribe to be notified of a notification as above, use code like below. Not that the name: matches the postNotificationName: above. This is how you can dispatch and subscribe to multiple notifications, depending on your needs.

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(requestFinishedHandler:)
                                             name:@"requestFinished"
                                           object:nil];

And here is a prototype of how to access the custom user data that is passed through to your selector: defined above. Because I may want to pass around multiple pieces of data, I sent through an NSDictionary.

- (void)requestFinishedHandler:(NSNotification *)notification
{
    resultDictionary = [notification userInfo];
}
Taylor Gerring
  • 1,825
  • 1
  • 12
  • 17
  • Thanks a lot, just to clarify a few thing, the dispatch notification code will go to my - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation method in the LocationHelper, but where should I put the code [[NSNotificationCenter defaultCenter] addObserver:self. Does it goes in the init method of the each controller which wants to subscribe to the notification, and I am assuming each controller has to implement - (void)requestFinishedHandler:(NSNotification *)notification method right? – Yogesh Apr 13 '11 at 21:21
  • To me, it makes more sense to call addObserver: in the controller's viewDidAppear: (or viewDidLoad:), because you also want to call removeObserver: in the viewDidDisappear: (or viewDidUnload:). I'm not 100% sure what happens if you don't unregister the observer, but I can guess that's not exactly a "best practice". – Taylor Gerring Apr 13 '11 at 22:27
  • Thanks Cadaeic it makes sense. – Yogesh Apr 14 '11 at 00:42
2
  1. Your location helper should be a singleton (here's a good example: http://www.galloway.me.uk/tutorials/singleton-classes/)

  2. You should use notifications rather than delegates. The location helper singleton should send notifications when it has a new location, any interested controller should subscribe to those notifications.

mattjgalloway
  • 34,792
  • 12
  • 100
  • 110
Michael Behan
  • 3,433
  • 2
  • 28
  • 38
  • I am not asking for an example of singleton, I am asking for if it is a singleton how do I set the different view controller as a delegate of this singleton class, singleton class has only one instance variable for delegate. – Yogesh Apr 13 '11 at 16:09
  • You should use notifications not delegates. Your location helper singleton should send the notifications and everything that is interested in them should subscribe to them. - updated answer. – Michael Behan Apr 13 '11 at 16:13
  • http://stackoverflow.com/questions/2191594/how-to-send-and-receive-message-through-nsnotificationcenter-in-objective-c – Michael Behan Apr 13 '11 at 16:21
2

A piece of data that should only be represented once (like "my current location", of which you can have only one) should be someplace that can only exist once, like a singleton.

You already have one perfectly good singleton that you're already using. There are those who will disagree with me on this (and God knows they've got good reasons to), but for this sort of data I don't have the first problem keeping it all in my App Delegate.

When the app launches, in the App Delegate, I'll fire up a CLLocationManager with the App Delegate itself as the location manager's delegate. I'll keep the returned CLLocation as a named property of the delegate (say "currentLocation"), and overwrite it as often as I get updates from the locationManager.

Then in my various view controllers I can say:

MyAppDelegate *del = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
CLLocation *current = del.currentLocation;

This way all your location getting/management/storage happens in one place that you can get to from anywhere in your app, and it keeps everything nice and clean

EDIT: In answer to the question "By key-value observing, do you mean notifications", the answer is NO. They're completely different things.

In any of your view controllers--probably in viewDidLoad--you can say:

MyAppDelegate *del = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
[del addObserver:self forKeyPath:@"currentLocation" options:0 context:nil];

And then implement:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{
    MyAppDelegate *del = (MyAppDelegate *)object; 
    CLLocation *hereIAm = del.currentLocation;
    // and then do whatever with that
}

You got a bunch of details about the change that just happened on that key in the parameters of that method, but it's probably simpler just to reach back up to the delegate and snatch out the value you're looking for.

Probably want to remove that observer in viewDidUnload:

MyAppDelegate *del = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
[del removeObserver:self forKeyPath:@"currentLocation"];
Dan Ray
  • 21,623
  • 6
  • 63
  • 87
  • @Dan, but the problem with this approach is how does all my controller gets notified when there is an updated location in the app delegate class. – Yogesh Apr 13 '11 at 16:08
  • @Yogesh -- It doesn't need to be. It uses the delegate's `.currentLocation` property every time it goes to DO anything with locations, and it's the delegate's job to keep things current. Or, if you need to trigger activity in the viewController on location update, this situation is just about perfect for key-value observing. – Dan Ray Apr 13 '11 at 17:23
  • @Dan so when you say key-value observing do you means notification as mbehan and Cadaeic are suggesting – Yogesh Apr 13 '11 at 21:22
  • @Yogesh - See edited answer. Using a NotificationCenter would work, I just think in this particular case KVO is tidier. – Dan Ray Apr 14 '11 at 12:11
  • @Dan Thanks, I am getting the following warning: passing argument 3 of addObserver:forKeyPath:options:context: makes integer from pointer without a cast on this line of code [del addObserver:self forKeyPath:@"currentLocation" options:nil context:nil]; – Yogesh Apr 14 '11 at 15:04
  • I found it, it should be 0 for options instead of nil, please correct in your response. – Yogesh Apr 14 '11 at 15:21
  • one more things the observerValueForKeyPath is called only when the delegate currentLocation is being set by doing self.currentLocation=newValue, but if you just do currentLocation=newValue it doesn't get called. – Yogesh Apr 14 '11 at 15:54
  • @Dan one more thing, so basically i have a tabBarController, and with all my tabs observing the changeLocation from the delegate, it seems like if i switch from default tab to some other tab before i changeLocation happened in the delegate, my observeValueForKeyPath methods doesn't get called in the default tab any idea what's going worng – Yogesh Apr 14 '11 at 16:37
  • @Yogesh - Key value coding relies on setting a named `@parameter`, not an ivar. So yes, you have to do self.currentLocation. If your observeValue method isn't getting called, probably your addObserver didn't get set up properly. Try putting some logging around it and make sure it's actually getting called. – Dan Ray Apr 14 '11 at 17:12
  • @Dan, probably you didn't read the comment fully, observeValue is getting called properly only on the view that is visible currently, but not on the views which are not visible, and because the value got updated when I was on view1 i see the value in view1 only and then now when i go to view2 or view3, i don't see the updated value in those views. – Yogesh Apr 14 '11 at 19:42