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?
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'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.
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];
Related
I'm making an app for practice. This app shares with a simple model through AppDelegate. To manipulate the model, I got an NSDictionary object from the model and allocate it to a viewController property. but It seems too verbose.
// viewController.h
#property (nonatomic, strong) NSMutableDictionary *bookDetail;
#property (nonatomic, strong) bookModel *modelBook;
// viewController.m
- (void)setLabel {
self.label_name.text = self.bookDetail[#"name"];
self.label_author.text = self.bookDetail[#"author"];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
id appDelegate = [[UIApplication sharedApplication] delegate];
self.modelBook = [appDelegate modelBook];
self.bookDetail = self.modelBook.bookList[self.modelBook.selectedId];
[self setLabel];
self.editMod = NO;
}
- (IBAction)editSave:(id)sender {
if (self.editMod == NO) {
....
[self.nameField setText:self.bookDetail[#"name"]];
[self.authorField setText:self.bookDetail[#"author"]];
....
} else {
self.bookDetail = [#{#"name" : self.nameField.text,
#"author" : self.authorField.text} mutableCopy];
[self setLabel];
....
}
}
#end
*bookDetail work like a copy of self.modelBook.bookList[self.modelBook.selectedId] not a reference. Using self.modelBook.bookList[self.modelBook.selectedId] works well, but I don't want to. How Can I simplify this code?
*bookDetail work like a copy of self.modelBook.bookList[self.modelBook.selectedId] not a reference. Using self.modelBook.bookList[self.modelBook.selectedId] works well, but I don't want to.
Your question is not clear to me so this might be wrong, but hopefully it helps.
bookDetail is not a "copy" in the usual sense, rather it is a reference to the same dictionary that self.modelBook.bookList[self.modelBook.selectedId] references at the time the assignment to bookDetail is made.
Given that you say that using the latter "works well" is sounds as though self.modelBook.selectedId is changing and you expected bookDetail to automatically track that change and now refer to a different dictionary. That is not how assignment works.
How Can I simplify this code?
You could add a property to your modelBook class[1], say currentBook, which returns back bookList[selectedID] so each time it is called you get the current book. In your code above you then use self.modelBook.currentBook instead of self.bookDetail and can remove the property bookDetail as unused (and incorrect).
HTH
[1] Note: this should be called ModelBook to follow naming conventions. Have you noticed the syntax coloring is incorrect? That is because you haven't followed the convention.
Create the shared instance of BookModel then you can access it anywhere:
Write this in bookModel:
+ (instancetype)sharedInstance
{
static bookModel *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[bookModel alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
Then you can access this like bookModel.sharedInstance.bookList
In AppDelegate.h
#property(strong,nonatomic)NSString *str2;
In ViewController.m
AppDelegate *c3=[[AppDelegate alloc]init];
c3.str2= #"Hello";
NSLog(#"Output:-%#",c3.str2);
Output:-Hello
Navigation code in Tableview didselect method(view Controller):-
Class2 *c2=[self.storyboard instantiateViewControllerWithIdentifier:#"cl2"];
[self.navigationController pushViewController:c2 animated:YES];
In Class2.m:-
AppDelegate *c3=[[AppDelegate alloc]init];
NSLog(#"%#",c3.str2);
Output:-Null
First, let's fix your current approach: the reason some recommend using app delegate to store shared values is that there is one easily accessible instance of it. You never create a new app delegate, but access the shared one instead:
AppDelegate *c3 = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Once you replace [[AppDelegate alloc]init] with the above, your code will start working the way you expect.
However, this solution is not ideal. App delegate is not supposed to be a place for storing shared values; they should be stored in your model object (as "M" in MVC, Model-View-Controller). Create a singleton model object, place shared variable in it, and use that shared variable by accessing the singleton from different view controllers.
In AppDelegate.h
#property(strong,nonatomic) NSString *str2;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_str2 = "Hello"
}
In ViewController.m and any other view controller that wants to access str2:
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
NSLog(#"Output: %#", delegate.str2);
Never create AppDelegate object on your own. Instead, access its instance via sharedApplication.
I am having little bit confused regarding this way of asking question
Any how i am mentioning my query with support of image here. Can you please help me out regarding this issue
Step 1: I have an requirement like: By using CLLocationManger delegate methods i fetched speed value like:
- (void)startLocationUpdates{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.pausesLocationUpdatesAutomatically = YES;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
[locationManager requestWhenInUseAuthorization];
[locationManager startUpdatingLocation];
}
#pragma mark
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations{
float speed = locationManager.location.speed;
float speedInKmph = speed * 3.6; // to convert the speed into kmph.
NSString *speedValue =[NSString stringWithFormat:#"%.f Kmph ",speedInKmph];
self.currentSpeedLblRef.text = speedValue;
self.maxSpeedLblRef.text = speedValue;
}
Step 2: currentSpeedLblRef,maxSpeedLblRef --> these are my UILabels
Example: Now i am driving a car --> For the first time i opened the app and i got the current speed of car(like: 120 Kmph) and then i need to display same value in "maxSpeedLblRef"(120 Kmph) also
After some time my current speed of car if 50 Kmph. But i need to display value in "maxSpeedLblRef" is --> Max value --> means 120 kmph . Because already i was getting 120 kmph value for previos
After that If my current speed of car if 180 kmph --> I need to show "maxSpeedLblRef" valu like: 180 Kmph. Because it is the latest one compare to 120 Kmph
After Close the app and then
If i reopen the app i want to show the vale like "maxSpeedLblRef" --> 180 Kmph. Because this value is the previous saved value
HERE IS MY SOURCE CODE:Click here link
you are missing a few simple things here!
You need to store the max speed for comparison - all you're doing here is is updating the label with the current value every time. Set up a class-level attribute, initial value = 0, and update it whenever current speed > max speed.
There are a few ways you can store the max value so that it's there when you next open the app. Probably easiest to go with user defaults. There are many tutorials available - try this one http://code.tutsplus.com/tutorials/ios-sdk-working-with-nsuserdefaults--mobile-6039
OK - using your project code, here's what you need.
Update your ViewController.h to this
// ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *currentSpeedLblRef;
#property (weak, nonatomic) IBOutlet UILabel *maxSpeedLblRef;
// you need to store the max speed
#property float speedMax;
-(void)loadDefaults;
-(void)storeDefaults;
#end
and then in the VIewController.m, replace the viewDidLoad with this
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self loadDefaults];
[self startLocationUpdates];
}
update the locationManager function to this
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations{
float speed = locationManager.location.speed;
float speedInKmph = speed * 3.6; // to convert the speed into kmph.
NSString *speedValue =[NSString stringWithFormat:#"%.f Kmph ",speedInKmph];
self.currentSpeedLblRef.text = speedValue;
if (speedInKmph > self.speedMax)
{
self.speedMax = speedInKmph;
[self storeDefaults];
}
NSString *speedMaxValue =[NSString stringWithFormat:#"%.f Kmph ",self.speedMax];
self.maxSpeedLblRef.text = speedMaxValue;
}
and, finally add the load / store functions
- (void)loadDefaults
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.speedMax = [defaults floatForKey:#"SpeedMax"];
}
- (void)storeDefaults
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setFloat:self.speedMax forKey:#"SpeedMax"];
[defaults synchronize];
}
As my user changes location, I need to compare his location with an array of locations I pull from the web. However, in my appDelegate, I'm not sure where exactly to place my code as I'm not sure what methods are called or not called when the app is terminated, but the CLLocationManager still works.
Specifically, I need to input this code where it will actually be called when the app is still terminated:
// alloc and init the various (Mutable)Array properties
self.locations = [[NSMutableArray alloc] init];
// Create new HomeModel object and assign it to _homeModel variable
_homeModel = [[DealsModel alloc] init];
// Set this view controller object as the delegate for the home model object
_homeModel.delegate = self;
// Call the download items method of the home model object
[_homeModel downloadItems];
The _homeModel will then call this method:
-(void)itemsDownloaded:(NSArray *)items
{
// This delegate method will get called when the items are finished downloading
// Set the downloaded items to the array
_locations = [items copy];
}
Which I will further edit to compare the user's location to the array of locations.
The thing is, this array of locations only changes once a week. Does the app really have to pull it from the web every time the user's location changes? Or is there a way to cache this and only pull it when self.locations has been deallocated?
This is what I have now, but I feel there must be a better way:
#interface AppDelegate () <CLLocationManagerDelegate>
{
DealsModel *_homeModel;
}
#property BOOL didRunBefore;
#property CLLocationManager *locationManager;
#property NSMutableArray *deals;
#end
#implementation AppDelegate
-(void)itemsDownloaded:(NSArray *)items
{
// This delegate method will get called when the items are finished downloading
// Set the downloaded items to the array
_deals = [items copy];
[self compareSponsorLocations:_deals toUserLocation:[self.locationManager location]];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {
if (!self.deals) {
// alloc and init the various (Mutable)Array properties
self.deals = [[NSMutableArray alloc] init];
// Create new HomeModel object and assign it to _homeModel variable
_homeModel = [[DealsModel alloc] init];
// Set this view controller object as the delegate for the home model object
_homeModel.delegate = self;
// Call the download items method of the home model object
[_homeModel downloadItems];
} else {
[self compareSponsorLocations:self.deals toUserLocation:[locations lastObject]];
}
}
- (void) compareSponsorLocations: (NSArray *) array toUserLocation: (CLLocation *) location
{
for (Deal *deal in array) {
NSLog(#"%#", deal.name);
}
NSLog(#"%#", location.description);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
To handle location updates when you app is in background or even terminated, you can use "Significant Location Changes". It will trigger application to start in background mode when location has been changed significantly. Then you can start a background task to perform the operations on your need.
Documentation:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW8
Easy way to simulate it and debug:
XCode / iOS simulator: Trigger significant location change manually
I am working on an app and I got stuck at the point where I can't seem to retrieve the value of a BOOL set in a class.
I spent too much time already on it, been through all the questions I found that seem to cover the matter.
The bad thing here is that I get something, but not what I need (I get a 0, which means, I guess, that the value wasn't retrieved correctly as it should be 1).
The things I tried are :
pass a pointer to my first class and access to my BOOL like this:
//in some method
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
by declaring it (talking of the pointer) as a property in my second class (and importing the h. file from my first class, where my BOOL is declared as property too):
#property FirstClass *pointerFirstClass;
But I got 0 using this.
The other shot I gave was add my BOOL in the first class and create an instance of the class in my second class
//in some method
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
But I got 0 too.
As Booleans are primitive types, like in C, I get a bit confused since I am new to object-oriented programming, I don't know how I could like create a getter for this, for example.
I also tried to do a - (BOOL)getBOOLValue method in my first class, and call this method in my second class and assign it to a BOOL in that second class.
But the result wasn't better.
Am I missing something?
Is there a way to get my value that I didn't think of or didn't know about yet?
I am running low on thoughts on how to get around this, it shouldn't be that hard IMO so I hope it is something simple that I just left aside.
EDIT :
Some actual code. I am working between 2 files called AppDelegate (yes, the actual one) and WelcomeViewController (so a VC).
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
BOOL inRegion; //thought of this making my BOOL as a property of AppDelegate
}
#property (strong, nonatomic) UIWindow *window;
#property BOOL inRegion; //Declaring my BOOL here to make it accessible for another class
- (BOOL)getBOOLValue; //An attempt to pass my BOOL value
AppDelegate.m
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
self.inRegion = YES; //Set my BOOL to TRUE
}
else if (state == CLRegionStateOutside)
{
self.inRegion = NO; //Else set it to False
}
- (BOOL)getBOOLValue
{
return inRegion; //Tried to create a custome "getter"
}
WelcomeViewControler.m (I changed nothing in the .h file)
I said I tried many things, right now, this is the last version of my code.
//Simply trying to do a Segue on a condition...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
As said, I want to retrieve the BOOL value of the AppDelegate.
Thank you.
This code doesn't make sense:
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
The first line doesn't do anything. You're not assigning anything to the property, and you're not doing anything with the value. Furthermore, the second line doesn't relate to the first line in any way that we can see from the code you've provided. Try this instead:
self.pointerFirstClass = [[FirstClass alloc] init];
self.pointerFirstClass.myBOOL = YES;
NSLog(#"myBOOL = %d", self.pointerFirstClass.myBOOL);
In other words, you need to be sure that self.pointerFirstClass points to a valid object. And then you need to make sure that you've assigned the value you want to the myBOOL property of that object.
Update: This looks like a case where you're talking to the wrong object. Look at this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
This is surely not what you really want. The application object is a single object -- a real singleton, in fact, meaning that there is and can be only one application object. That object has a delegate object, and that's a specific instance of your AppDelegate class. In this code, though, you're creating a new instance of AppDelegate, one that's different from the one that the application is using. Any changes that are made to the actual application delegate in response to messages from the application will not be reflected in the new object that you've created.
What I think you want is to get the actual application delegate object, and you can do that using:
[[UIApplication sharedApplication] delegate];
So, change your code to look like this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
// note: you don't need an else clause if it doesn't do anything
}
That way, you'll be talking to the same object that the app uses, which is the one that has the inRegion property set in response to the location manager call.
UPDATE - Now we can see your code the problem is obvious, you are trying to access the appDelegate by creating a new one...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
Instead you should be doing this....
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]
--
Not sure if you are posting your actual code? but the first example you give...
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
Shouldn't the second line be
NSLog(#"%d", self.pointerFirstClass.myBOOL);
Also this property...
#property FirstClass *pointerFirstClass;
Won't retain it once you've set it, it needs to be
#property (nonatomic,strong) FirstClass *pointerFirstClass;
In the second example...
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
You allocate and initialise a new FirstClass object and then check the property straight away, if you are not setting this to YES in the init then it will be false
Like I say, maybe you're not posting your actual code?
I guess what you want is initializing myBOOL to 1.
If so, you need do something as following
#implement FirstClass
- (id)init
{
self = [super init];
if(self) {
_myBOOL = 1;
}
return self;
}
// Other methods
#end
EDIT:
The comments is why you get 0.
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init]; // this is the problem.
// you create a new appdelegate,
// and never call locationManager:didDetermineState:forRegion:
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
rewrite your code as following:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}