UIApplicationBackgroundRefreshStatusDidChangeNotification usage without corresponding delegate method
Asked Answered
W

2

4

I feel that UIApplicationBackgroundRefreshStatusDidChangeNotification introduced in iOS 7 is of little use without supporting UIApplication delegate method. Because, the app is not notified when user has switch ON the background refresh state for my app.

This is my notification handler...

- (void)applicationDidChangeBackgroundRefreshStatus:(NSNotification *)notification
{
    NSLog(@"applicationDidChangeBackgroundRefreshStatus with notification info = %@ and refresh status = %d", notification, UIApplication.sharedApplication.backgroundRefreshStatus);

    if (UIApplication.sharedApplication.backgroundRefreshStatus == UIBackgroundRefreshStatusAvailable) {
//        if ([CLLocationManager locationServicesEnabled]) {
            [self.locationManager startUpdatingLocation];
//        }
    }
}

As above, I want to start updating core location when UIBackgroundRegreshStatus is made Available through app Settings > General > Background App Refresh. I feel there should have been an appropriate delegate method in UIApplicationDelegate to let the app know about this change so that App could re-establish everything it needs to.

Either I'm missing something (pre-existing API) or Apple SDK engineers have some other/limited intentions about this notification usage. Please advice.

Woodman answered 7/10, 2013 at 21:32 Comment(0)
C
6

ideally, you never have to check for that setting. It looks as though you are going around background fetching the wrong way. With the application minimised, the system will periodically wake your application up and allow it to perform tasks. From your code , you want to update the location. first place to start is here , using this delegate method that gets called when the app is woken up for a background fetch

/// Applications with the "fetch" background mode may be given opportunities to fetch updated content in the background or when it is convenient for the system. This method will be called in these situations. You should call the fetchCompletionHandler as soon as you're finished performing that operation, so the system can accurately estimate its power and data cost. - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);

this is how you use it, in your application delegate implementation, define the method body as follows

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{

    NSLog(@"APP IS AWAKE FOR A BACKGROUND FETCH");
    //do all the work you want to do 

//once done, its important to call the completion hadler, otherwise the system will complain
          completionHandler(UIBackgroundFetchResultNewData);

 }

however, since you are updating the location, which has its own delegates, you only want the completion handler to be called when your delegates return and not before that. Calling the completion handler will send your application back to sleep. Since the completion handler is a block object, it can be passed around like any other object. One way of doing that is as follows: in the application delegate header file, define a block object:

void (^fetchCompletionHandler)(UIBackgroundFetchResult);

then in your performFetchWithCompletionHandler have :

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    fetchCompletionHandler = completionHandler;

    NSLog(@"APP IS AWAKE FOR A BACKGROUND FETCH");
    //do all the work you want to do 
            [self.locationManager startUpdatingLocation];


 }

at some appropriate time, after your location delegate methods have returned, you call

fetchCompletionHandler (UIBackgroundFetchResultNewData);

be sure to check if your fetchCompletionHandler is non nill, calling it when nil will immediately crash your app. read more on blocks from the apple docs here https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html

also have a look at the call [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval: ]; which Specifies the minimum amount of time that must elapse between background fetch operations.

You would probably chuck that into your app delegate application didFinishLaunchingWithOptions method.

hopefully this helps you.

Cockhorse answered 9/10, 2013 at 8:48 Comment(0)
S
5

You need to register for notification:

[[NSNotificationCenter defaultCenter]
 addObserverForName:UIApplicationBackgroundRefreshStatusDidChangeNotification
 object: [UIApplication sharedApplication]
 queue:nil
 usingBlock:^(NSNotification* notification){
     NSLog(@"Just changed background refresh status because of this notification:%@",notification);
 }];

You will get this output if your app is running and then you switch and disable background App refresh.

> Just changed background refresh status because of this notification:NSConcreteNotification 0x165657e0 {name = UIApplicationBackgroundRefreshStatusDidChangeNotification; object = <UIApplication: 0x16668670>}

You can obviously select a different queue. Also, this does not have to be in the app delegate. Any class can listens to this notification.

Recalling WWDC sessions, they said this was not available in DP seed 1. I am not sure if this is not an app delegate method now because they did not get to it or that is how they intended it since this is more likely to be used outside the app delegate (in your own model or VC) to listen and adjust to an app background refresh privileges being changed. In the latter case, a notification to anyone who registers for it makes more sense.

Hope this helps.

Update: Based on the WWDC 2013 Core location video, Location updates that you want to work in the background has to start in the foreground. So this means that once the user turns off the background refresh for the app, location updates would stop and the only thing that would restart location updates, is restarting location updates in the foreground! In that case, a delegate would not be useful since it cannot restart location updates while in background. This is by design.

Showman answered 2/12, 2013 at 12:22 Comment(2)
This notification block/handler is only called when the settings is turned OFF (since the app is in background and running due to location services). But, as told in my question, the handler does NOT run when background refresh is changed back to ON state (remember the app is still in background but obviously it's not running now). That's the reason I feel there should be some mechanism to inform the app - e.g. an app delegate method that gets invoked whenever this background refresh setting change in background.Woodman
Ok, I agree this could be 'as-designed'. I believe application:performFetchWithCompletionHandler is the only limited (not guaranteed) option to restore the app state, as told by @michael.Woodman

© 2022 - 2024 — McMap. All rights reserved.