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?
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.
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;
}
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];