4

I'm building an app which displays result data based on your current location.

At the moment, I'm using the viewDidLoad method of a UIViewController to start the CLLocationManager and getting the current location. Once I have the location that matches the accuracy I desire, I do a request to my web service to get the results and dump it into a UITableView.

My problem is that when you close the app (although it's still running in the background). If you were to drive to another town, re-open the app, the data doesn't get updated and continues to show the results from your old location.

Basically when the UIViewController loads from the background, I need to be able to check the users location, and if they have moved a significant distance, update the contents of my UITableView.

However, because the viewDidAppear of the UIViewController isn't triggered when you load the app from the background, I'm not sure which method I can use.

I am aware of the stopMonitoringSignificantLocationChanges method which wakes up your app when a new location is found. However, this seems a little OTT because I only need to know once the app has loaded.

Is there any alternative to using the stopMonitoringSignificantLocationChanges method?

Dan Ellis
  • 5,537
  • 8
  • 47
  • 73

3 Answers3

12

You could register to receive notifications from UIApplication in -viewDidLoad. You may be interested in UIApplicationDidBecomeActiveNotification. Registering for notifications is easy.

- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}

In -viewDidLoad we add ourselves as an observer of the UIApplicationDidBecomeActiveNotification and specify a selector to be invoked when that particular notification is received.

- (void)applicationDidBecomeActive:(NSNotification *)notification
{
    // Kick off your CLLocationManager
    [self updateCurrentLocation];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}

Finally, remember to remove yourself as an observer for this notification when the view unloads. It's good practice to balance your addObserver/removeObserver calls to NSNotificationCenter in this way.

Mark Adams
  • 30,776
  • 11
  • 77
  • 77
  • Thanks for your answer, worked perfectly. This seems better than using the AppDelegate method as it keeps it all within the same UIViewController. Thanks. – Dan Ellis Sep 14 '11 at 21:09
2

You can register your view for notifications. For views that need to keep track of application state I use this handy superclass.

@implementation BackgroundAwareObject

-init
{
    if(self=[super init])
    {
        NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(notifyApplicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
        [center addObserver:self selector:@selector(notifyApplicationWillEnterForeground:) name: UIApplicationWillEnterForegroundNotification object:nil];
        [center addObserver:self selector:@selector(notifyApplicationDidFinishLaunching:) name: UIApplicationDidFinishLaunchingNotification object:nil];
        [center addObserver:self selector:@selector(notifyApplicationDidBecomeActive:) name: UIApplicationDidBecomeActiveNotification object:nil];
        [center addObserver:self selector:@selector(notifyApplicationWillResignActive:) name: UIApplicationWillResignActiveNotification object:nil];
        [center addObserver:self selector:@selector(notifyApplicationDidReceiveMemoryWarning:) name: UIApplicationDidReceiveMemoryWarningNotification object:nil];
        [center addObserver:self selector:@selector(notifyApplicationWillTerminate:) name: UIApplicationWillTerminateNotification object:nil];
    }
    return self;
}

-(void)notifyApplicationDidEnterBackground:(NSNotification*)n
{
    [self applicationDidEnterBackground:[UIApplication sharedApplication]];
}

-(void)notifyApplicationWillEnterForeground:(NSNotification*)n
{
    [self applicationWillEnterForeground:[UIApplication sharedApplication]];
}

-(void)notifyApplicationDidFinishLaunching:(NSNotification*)n
{
    [self application:[UIApplication sharedApplication] didFinishLaunchingWithOptions: [n userInfo]];
}

-(void)notifyApplicationDidBecomeActive:(NSNotification*)n
{
    [self applicationDidBecomeActive:[UIApplication sharedApplication]];
}

-(void)notifyApplicationWillResignActive:(NSNotification*)n
{
    [self applicationWillResignActive:[UIApplication sharedApplication]];
}

-(void)notifyApplicationDidReceiveMemoryWarning:(NSNotification*)n
{
    [self applicationDidReceiveMemoryWarning:[UIApplication sharedApplication]];
}

-(void)notifyApplicationWillTerminate:(NSNotification*)n
{
    [self applicationWillTerminate:[UIApplication sharedApplication]];
}

-(void)configurationChanged
{
    // User has update application configuration panel

}

- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{    
    // Override point for customization after application launch.  

}


- (void)applicationWillResignActive:(UIApplication *)application 
{
    /*
     Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
     Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
     */
}


- (void)applicationDidEnterBackground:(UIApplication *)application 
{
    /*
     Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
     If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
     */
    _background = YES;
}


- (void)applicationWillEnterForeground:(UIApplication *)application 
{
    /*
     Called as part of  transition from the background to the active state: here you can undo many of the changes made on entering the background.
     */
}


- (void)applicationDidBecomeActive:(UIApplication *)application 
{
    /*
     Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
     */
    _background = NO;
}


/**
 applicationWillTerminate: saves changes in the application's managed object context before the application terminates.
 */
- (void)applicationWillTerminate:(UIApplication *)application
{
}

// try to clean up as much memory as possible. next step is to terminate app
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
}

-(void)dealloc
{
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    [super dealloc];
}

@end
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • Looks great. I will give it a try. I think you should post this in GitHub so you could receive comments on how to expand it or to keep it as a shareable resource. Thanks for sharing. – agarcian Jul 26 '12 at 02:30
1

In your AppDelegate.m you must have this method predefined (as in the template you created initially when starting your project in Xcode) -

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    /*
     Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
     */
}

As the description says, this method is automatically invoked by iOS when the app is about to become active. Here you could get the latest location to do further processing.

Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
  • Once I have my updated location from there, is there a way I can make my UIViewController that the location has changed? – Dan Ellis Sep 13 '11 at 20:02
  • you could import your UIViewController.h file here & pass that location back or make the location variable a singleton (i.e. make it as part of app delegate). there are various ways actually... – Srikar Appalaraju Sep 13 '11 at 20:07
  • Ok thanks. Doing it this method, is there any advantage over the Mark Adams suggested answer. Personally his seems easier as it's all managed within the UIView. – Dan Ellis Sep 13 '11 at 20:31
  • yes this is cleaner. That's why these methods are there. This exactly suits your needs. You could also do what mark suggested but why? Its cleaner this way... – Srikar Appalaraju Sep 13 '11 at 20:36
  • ok, I'm just trying to implement this method. I'm just getting a bit stumped on passing the value back to my UIViewController. To be able to trigger the update, I need to call a method on my UIViewController, passing back the location as I do so. How would I access the active instance of my UIViewController form the AppDelegate? – Dan Ellis Sep 13 '11 at 21:22
  • Actually I consider this method to be less desirable because it introduces unnecessary coupling between the App Delegate and the view controller in question. The view controller is responsible for obtaining the user's location and displaying data, so it stands to reason that the view controller is also responsible for getting notified of changes in the application to complete this task. – Mark Adams Sep 13 '11 at 21:54
  • I agree with Mark here. It is a much better design to de-couple the parts here. If the view controller is on the stack, let it handle the notification. – Mark Jun 22 '12 at 17:58