I've a typical problem. In my application, I'm handling the application delegate method
- (void)applicationDidBecomeActive:(UIApplication *)application
to refresh the UI.
As my application won't be terminated and running in background, when ever the app comes to active state, this method is being called and working fine.
But in one of my view controller, i'm creating CLLocationManager object
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self; // send loc updates to myself
The problem is that, when this location manager object is being created, application delegate's "applicationDidBecomeActive" is also being called which is not necessary for me to handle. How should I neglect the call when location manager object is being initialized?
It's strange that such a call occures, but if there is no way to stop it, just make a flag in your defaults, smth lke callAfterSettingDelegate,and in your DidBcomeActive check if it was called after that, ignore this call and set that flag to nil.
Related
Below are some snippets of my code
BOOL checkingForLocation;
.
.
.
- (IBAction)LocationButtonTapped:(id)sender {
[locationManager startUpdatingLocation];
checkingForLocation = YES;
}
.
.
.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
[locationManager stopUpdatingLocation];
// locationManager = nil;
if (checkingForLocation) {
checkingForLocation = NO;
NSLog(#"here");
// fetch data from server using location and present view controllers
}
}
didUpdateLocations is called multiple times because locationManager is continually updating the location per startUpdatingLocation. My app has a bug where the code within if(checkingForLocation) runs multiple times and renders the view multiple times causing undesired results.
1) Why is it that stopUpdatingLocation doesn't stop further calls to didUpdateLocations?
2) Furthermore, if the checkingForLocation has already been set to NO why does the subsequent calls to didUpdateLocations still treat checkingForLocation as YES. Is there a race condition there?
Through searching other SO questions, I found that setting locationManager = nil will stop the extra calls to didUpdateLocations, which it does fix the issue in my case, but no real explanation. It seems that stopUpdatingLocation should take care of that. Any insight is much appreciated.
Thank you,
didUpdateLocations: can be called frequently and at any time because of its async nature and optimisations :
Because it can take several seconds to return an initial location, the
location manager typically delivers the previously cached location
data immediately and then delivers more up-to-date location data as it
becomes available. Therefore it is always a good idea to check the
timestamp of any location object before taking any actions. If both
location services are enabled simultaneously, they deliver events
using the same set of delegate methods.
Actually, if you uncomment locationManager = nil; it should help, but if you want to use your checkingForLocation property, you should make the assignment synchronised. #synchronized(self) is the easiest way in this case.
So, i need to start a timer in the app delegate method applicationDidEnterBackground:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
globalBackgroundTimer = [NSTimer timerWithTimeInterval:30 invocation:nil repeats:NO];
}
the timer is declared like so in app delegate.h:
extern NSTimer * globalBackgroundTimer;
While the timer runs, i receive background location updates (is enabled in plist), in a view controller, and i want to check constantly in locationManager:didUpdateLocations:
For when the timer has expired so i can end the location updates.
... //code omitted
//this is called repeatedly when the app is in the background, and checks whether the global variable is instantiated, and if it has expired.
if(globalBackgroundTimer)
{
NSLog(#"timer alive");
if(!globalBackgroundTimer.isValid)
{
[locationManager stopUpdatingLocation];
NSLog(#"background timer invalid, stopping location updates");
}
}
But i can't make it work (Mach-O-linker error),
however i also read that this approach was ill-advised. So what do you guys suggest?
Figure out a mechanism to store the state of the timer when the app enters the background (NSUserDefaults or Documents sandbox). Then, when the app enters foreground, recalculate the difference using the NSDate information you saved.
(I realize this may already have been what you were doing before you posted this question.)
I have location services enabled for updating locations when app in background, and I also listen for location updates when app in foreground, displaying a map. What should be the best way to design this scenario in iOS? I've thought about some options:
1) Having an instance of a class with a locationManager member that is its delegate itself. Then, in the body of the didUpdateToLocation delegate method, something like:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if (!background) {
// Perform some processing and notify the view controller which displays the map
// by means of Notification Center
}
else {
appDelegate.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.bgTask];
appDelegate.bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform some data processing
// Close background task
if (appDelegate.bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.bgTask];
appDelegate.bgTask = UIBackgroundTaskInvalid;
}
});
}
}
(Note: I'm not sure if having location services enabled as background service, it is needed to perform the processing of locations as if it were a finite background task...). The instance of such class could be a member of AppDelegate, and start listening for locations when entering background or calling the instance from the viewModel displaying the map. So, AppDelegate would have a reference to the instance managing the locationManager, and the map viewController would have a reference to AppDelegate, or communicate also by means of Notification Center.
2) Having the locationManager member directly in AppDelegate, and being the delegate itself.
Would it be better to encapsulate de locations listening and management in a different class, or handle it directly in AppDelegate? Having into account that I have to be able to listen for locations and perform some tasks both in foreground and in background.
Thanks in advance
I think your best strategy is to have a singleton instance of a class that is a locationManager delegate. This instance would be responsible to filter whatever comes back from locationManager and ultimately assigns the location to one of its properties, let's call it myLoc.
This solves your centralized logic issues where the location needs post-processing after being acquired.
Second, in order to get the correct UIViewControllers and other class instances to do what they need when the location is updated, I suggest you use Key-Value Observing (KVO) on myLoc. I assume you know how that works. The reason I suggest KVO is that you need to trigger changes in multiple areas of the code based on the location being changed, and KVO is made for that kind of pattern.
I would like to add more to #Rikkles answer by saying that you can create a BaseViewController, which is a subclass of UIViewController and has an instance of your singleton locationManager class too.
This approach allows us to move all the redundant code like, checking reachability, showing activity indicator,getting user location, etc to only one place and will be available to all other classes implementing it.
I have location processing newly implemented in my app, testing in foreground and background with satisfactory results. The application is monitoring significant location changes as well as several regions. I have not yet figured out if I will get the same results when the app is suspended or terminated.
As I understand it, when the app is woken from these states it will be as if the app just started except the launchOptionsLocation key will be found in the launchOptions Dictionary param. My question is, can I allow the app delegate to proceed as normal and assume all is well? Is it necessary to intercept all the view setup code?
The very first lines in my didFinishLaunchingWithOptions:launchOptions method are:
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
//Handle Error
}
self.sharedLocationHandler = [[[TTLocationHandler alloc] init] autorelease];
self.siteLogger = [[[ProjectSiteLogger alloc] initWithOptions:nil] autorelease];
self.siteLogger.locationHandler = self.sharedLocationHandler;
self.siteLogger.managedObjectContext = context;
In all likelihood, this covers all that I need in order to respond to the location event. I could easily test for the location key in launchOptions and skip the entire remainder of the method, though I am not sure what unforeseen complications that may entail.
I also question what would result then if a user happened to start the app intentionally while it was in that incomplete state having no views set up at all.
Is this something that has been tried, is it even necessary at all? I don't see how to test this as I don't know of a way to stay connected to Xcode debugger when the app is suspended.
---Additional Updated Info----
Initial testing on a day of carrying test phone around, my location processing seemed to do all the tasks I wanted it to with no changes to appDelegate. So I presume that wake from suspend/term came up and executed the full appDelegate procedure even though no view controllers ever became visible.
So, while it seems that it is not required to alter the startup procedures, may there still be a performance or battery concern that would make it prudent to cut the appDelegate procedure short and minimize processing?
After a good bit of testing and tweaking I find:
The app will wake from inactive or termed with no issues.
My location methods ran and completed regardless of the changes to the App Delegate.
When I cut the app delegate processes short, I did have intermittent start issues as anticipated.
Even though there were no apparent performance benefits, I went ahead and separated out the view setup code and added a flag so that it would not be run unless the application was being presented in the foreground. It just seems right to run no more processing than needed.
The code I ended up with is:
// Initialize a flag
BOOL needsViewsSetup = YES;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
// All my response to location events are handled in the these two classes
self.sharedLocationHandler = [[[TTLocationHandler alloc] init] autorelease];
self.siteLogger = [[[ProjectSiteLogger alloc] initWithOptions:nil] autorelease];
self.siteLogger.locationHandler = self.sharedLocationHandler;
self.siteLogger.managedObjectContext = self.managedObjectContext;
// check launchOptions, skip all the views if there we were woken by location event
if (![launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
[self confirmDataExistsAtStartup];
[self setupTabbarWithViews];
}
return YES;
}
Then you have the views setup:
-(void)setupTabbarsWithViews
{
// Code to setup initial views here
// ending with flag toggle to prevent repeat processing
needsViewsSetup = NO;
}
And in application will enter foreground:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if (needsViewsSetup) {
[self setupTabbarWithViews];
}
}
Note: My application is not running location services in the background, only listening for significant location changes and geofence.
I'm creating an app which listens to significant location change events and in case the app gets terminated then the iOS launches the app with UIApplicationLaunchOptionsLocationKey set.
So, the documentation says to create a new location manager and register for location updates again. However, doesn't mention if I'm supposed to initialize my viewController (as I do in normal app launch as well)? My view controllers initialize in viewDidLoad but are created in appDidFinishLaunchingWithOptions.
Any idea how much time does OS provides to the App for location update handling? My app needs to make a webservice request if the location change indicates an interested location for the app.
Thanks
You should consider moving your initialization code to a new method, something like initializeViews. This method would check to make sure the views haven't been initialized and then initialize them. You would call this method from application:didFinishLaunchingWithOptions: and applicationWillEnterForeground:, but the call in application:didFinishLaunchingWithOptions: would only occur if the application wasn't going to the background.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
if([UIApplication sharedApplication].applicationState != UIApplicationStateBackground)
[self initializeViews];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self initializeViews];
}
- (void)initializeViews {
if(!viewsAreInitialized) {
...
viewsAreInitialized = YES;
}
}