0

I want to call a method from elsewhere in the app to get the user's location that was obtained in the app delegate. When calling CLLocation *getCurLoc = [AppDelegate getCurrentLocation]; from another view controller, nothing is returned.

The App Delegate is,

@synthesize locationManager;

CLLocation *location;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    locationManager = [[CLLocationManager alloc]init];
    locationManager.delegate = self;
    locationManager.desiredAccuracy=kCLLocationAccuracyKilometer;
    [locationManager startUpdatingLocation];

    return YES;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    [locations lastObject];
    [manager stopUpdatingLocation];

    CLLocation *location =  [locations lastObject];
}

+(CLLocation *)getCurrentLocation{
    return location;
}

Changing it to an instance method with a "-" didn't work. Should "location" be made into an instance? Should the delegate be made into an instance, or is there a better way to access it from elsewhere?

Bob Green
  • 19
  • 5
  • 1
    Your `CLLocation *location` should be implemented as an ivar or as a property of some class that holds the data (model). – Rok Jarc Nov 02 '13 at 19:48

3 Answers3

2

In didUpdateLocations, you define and set a local variable location, not the global variable location defined at the top of the file. Changing the line

CLLocation *location =  [locations lastObject];

to

location =  [locations lastObject];

would fix the problem. But the better solution is to use a property in the AppDelegate class. You can define it in a class extension:

@interface AppDelegate ()
@property(strong, nonatomic) CLLocation *location;
@end

Then you access it in instance methods like

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    [locations lastObject];
    [manager stopUpdatingLocation];
    self.location =  [locations lastObject];
}

and in a class method like

+(CLLocation *)getCurrentLocation{
    // Get the instance:
    AppDelegate *app = [[UIApplication sharedApplication] delegate];

    return app.location;
}

Update: If the AppDelegate is declared to conform so some protocol (in the public interface or in the class extension), for example:

@interface AppDelegate () <CLLocationManagerDelegate>
@property(strong, nonatomic) CLLocation *location;
@end

then the above code creates a warning

initializing 'AppDelegate *__strong' with an expression of incompatible type 'id<UIApplicationDelegate>'

and an explicit cast is necessary:

+(CLLocation *)getCurrentLocation{
    // Get the instance:
    AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    return app.location;
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • When using 'AppDelegate *app = [[UIApplication sharedApplication] delegate];' I get a warning __strong' with an expression incompatible with type' id. I found that if I combine this answer with the gimenetes's answer I can get it to work using '+ (AppDelegate *)sharedDelegate { return (AppDelegate*)[UIApplication sharedApplication].delegate; }' – Bob Green Nov 04 '13 at 03:26
  • @BobGreen: You are right. I did not get a warning when I tested this and wrote the answer, but I figured out why the cast is necessary in your case, and have updated the answer. – Martin R Nov 04 '13 at 06:13
1

Martin's answer is an effective quick&dirty way of solving this problem in a way that is consistent with your approach - storing the location in appDelegate.

If you want to take a step further you might want to consider implementing a special object that would hold the data - the data model. It is considered a bad practice to store data in application delegate - it is not what it is there for (though it works perfectly fine in sample or small applications).

You could do something like this:

DataModel.h

#import <Foundation/Foundation.h>

@interface DataModel : NSObject

@property (strong) CLLocation *location;

+ (DataModel *)sharedModel;

@end

DataModel.m

#import "DataModel.h"

@class CLLocation;

@implementation DataModel

- (id) init
{
    self = [super init];
    if (self)
    {
        _location = nil;
    }
    return self;
}

+ (DataModel *)sharedModel
{
    static DataModel *_sharedModel = nil;
    static dispatch_once_t onceSecurePredicate;
    dispatch_once(&onceSecurePredicate,^
                  {
                      _sharedModel = [[self alloc] init];
                  });

    return _sharedModel;
}

@end

You would then need to #import "DataModel.h" wherever you need it. You would change your didUpdateLocations: to:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    [locations lastObject];
    [manager stopUpdatingLocation];

    [DataModel sharedInstance].location = [locations lastObject];
}

And from anywhere in the code you could get this location simply by [DataModel sharedInstance].location.

EDIT:

For a very simple app this approach might look as an overkill. But as soon as your app grows it surely pays off to use it.

This kind of class/object/singleton is ment to hold all the data your app needs (fixed and temporary). So all the data sources can make a good use of it. In short: it enables you to easily follow the model-view-controller guidelines.

You cold of course use another class/object/singleton to hold the temporary data - it depends on the complexity of your data-structure.

You don't have to specifically initialize this kind of object. It is initialized the first time you reference it. That is why dispatch_once is there for. It makes sure that there is one and only one instance of this shared object present: https://stackoverflow.com/a/9119089/653513

And this one single instance of [DataModel sharedInstance] will remain there until your app is terminated.

Apple uses similar approach for [NSUserDefaults standardDefaults], [UIApplication sharedApplicaton] for example.

I tend to put the #import "DataModel.h" into my projects Prefix.pch so I don't have to import it every single time I use it (it is used trough all the app).

PROS: - data accesible throughout the app - code reusability - MVC structure - code is more readable

CONS: - I couldn't really find one. Except that the dispatch_once(&onceSecurePredicate,^... might confuse one for the first couple of seconds.

Community
  • 1
  • 1
Rok Jarc
  • 18,765
  • 9
  • 69
  • 124
  • I think this is the way to go. Could I also make this class store data from other classes, by adding more properties, and make it a central place to store some temporary data? Also I'm not sure what the dispatch or asynchronous is doing. Can you explain more? What are the pros and cons of using this approach? – Bob Green Nov 05 '13 at 04:35
  • Actually yes: a class like this is ment to hold all the data your application needs. It enables you to really work in Model-View-Controller manner. I'll add some more explanations to the answer. – Rok Jarc Nov 05 '13 at 08:24
0

You can access the AppDelegate from any point in your application using [UIApplication sharedApplication].delegate so you could do:

CLLocation *location = [(YourAppDelegateClassName*)[UIApplication sharedApplication].delegate getCurrentLocation];

The method getCurrentLocation needs to be an instance method (-(CLLocation *)getCurrentLocation). You will need also to import #import "YourAppDelegateClassName.h" in those files you need to use that method.

To avoid the casting and accessing [UIApplication sharedApplication].delegate everytime I prefer to implement a static method in my AppDelegates:

+ (YourAppDelegateClassName*)sharedDelegate {
    return (YourAppDelegateClassName*)[UIApplication sharedApplication].delegate;
}

So you can use any method like this:

CLLocation *location = [[YourAppDelegateClassName sharedDelegate] getCurrentLocation];
gimenete
  • 2,649
  • 1
  • 20
  • 16
  • Whoever downvoted this answer should leave an explaining comment. It seems to me that all three current answers are possible solutions to the problem. – Martin R Nov 02 '13 at 20:03
  • Agree with Martin R and gimenete: why would this answer be downvoted? I know we shouldn't be upvoting for compensation but here it goes... – Rok Jarc Nov 02 '13 at 20:08
  • Thank you rokjarc, and Martin for your support – gimenete Nov 02 '13 at 20:30