2

I am trying to run some code during the applicationWillResignActive when the user opens the task switcher and it has worked fine until I began using bluetooth in my app.

When bluetooth tries to connect to a device it shows an alert window asking if the user wants to pair the device. This alert is enough to trigger the applicationWillResignActive method and then runs my code for when the app is being navigated away from (task switcher). This causes a large problem since the code I intend to run when switching away, turns off some much needed functionality within the actual app. So once they press "pair" or "cancel" on that alert, all of my app stops functioning as it should because the app has lost focus.

I have tried to detect the state of the application during this time with this... NSUInteger state = [[UIApplication sharedApplication] applicationState]; thinking of course that it would be considered active when the alert pops up and inactive when in the task switcher. However, this was not the case it shows up as active for both use cases.

Update #1

The question...

How can I differentiate in the application between the app causing a system level inactive focus state like running code to connect to bluetooth, versus the user causing the system level inactive focus like double tapping the home button? All in the efforts to distinguish what is causing the applicationWillResignActive method to fire.

Update #2

The intention of this functionality is to set a flag in NSUserDefaults when bluetooth connects to the device. This flag is being "observed" and used to trigger the changing of view controllers to a page related to this new BT connection. When the user double presses the home button and moves to task switcher I turn off BT and switch to iBeacon so I can notify of events. All is well with this current implementation all bar 1 use case.

If the user hasn't yet connected to the BT device and it connects for the first time and that pairing alert comes up it fires the applicationWillResignActive method just the same as double tapping the home button does. In this method the code then checks for that NSUserDefaults flag to see if it switched on (which by this time it is because the BT has already reached the CBCentralManager's didConnectPeripheral method and turned it on) and if it's on, it turns off BT and switched to scanning for iBeacon. Because the app is still open this obviously causes problems. The app is running so the user see's the BT connect, the new view slide in, the pairing alert come up, then the new view slide right back out and iBeacon starts sending notifications intended for when the user is in the task switcher.

I already have this exact functionality happening in the applicationWillEnterBackground method so that's not the answer. I need to have a way of saying "the app is running right now and we've received an alert instead of double tapping home, so please don't turn off BT and turn on iBeacon yet"

divibisan
  • 11,659
  • 11
  • 40
  • 58
GoreDefex
  • 1,461
  • 2
  • 17
  • 41
  • The pairing UI makes your app inactive for a short time. The question is saying, "When my app becomes inactive, I want the OS tell me **for how long it will be** inactive". Even with $783 billion in market cap, Apple doesn't (yet) have future-knowing technology. We'll need to understand what you're app is doing on those state transitions and why, and move stuff around to get what you want. – danh Jul 19 '17 at 13:33
  • That's actually not at all what my question is asking. If you were going to re-word my question, you could say that it is asking how I can differentiate between the app losing focus based on an inner application activated event (i.e. connect to BT and ask for pairing) versus something at the system level like double tapping the home button. I am not sure if your comment on "Apple future-knowing technology" is a joke or not but it is not helpful in a solution to this problem. – GoreDefex Jul 19 '17 at 13:37
  • I did explain what my app is doing. You need no further information to tell me how to differentiate between the 2 use cases both firing the same appDelegate method. – GoreDefex Jul 19 '17 at 13:38
  • 1
    My (apparently unwise) wisecrack followed a serious point: BT pairing makes your app inactive, if only for a short time. The app doesn't get to find out which thing is making it innactive, or for how long. You'll need to manage the app state with the existing hooks. If you're unable to, then you're doing something wrong. Happy to help you figure that out, but that requires further info. – danh Jul 19 '17 at 13:56
  • @danh updated my response to show as you've asked – GoreDefex Jul 19 '17 at 14:14

1 Answers1

1

Two possible solutions:

1. The answer may lie in this statement:

When bluetooth tries to connect to a device it shows an alert window asking if the user wants to pair the device.

Your app must do something to cause this alert to appear. You could set a Date field to the current time in your AppDelegate when this happens, and then when you get a call to applicationWillResignActive you can compare that timestamp to the current time, and if it is < 1 second or so, you have a pretty good clue that the bluetooth dialog went up.

Of course, this is not foolproof. As @danh notes in his comment, the design of iOS makes this really difficult. You won't know for sure if the bluetooth dialog went up, or if the user or OS just happened to bring something else to the foreground at the same time. What's more, it's always possible that even if the bluetooth dialog comes up, the user might decide at that very moment to go check his or her email or start browsing Facebook. In that case, it is both true that the bluetooth dialog is what sent your app to the background, AND the user navigated away from the app. Unfortunately, iOS doesn't really give you a way to differentiate the two.

2. You might use a background task to handle your cleanup logic.

You can request up to 180 seconds of background running time after the call to applicationWillResignActive, so you could defer your cleanup tasks until say 175 seconds have passed since your app is resigned to the background. If the user doesn't come back within 3 minutes, it's probably time to do this cleanup anyway. My blog post here shows the basics of setting up a background task. It is specifically targeted to extending beacon ranging time, but you can put whatever logic you want inside the background code block like this:

- (void)extendBackgroundRunningTime {
  if (_backgroundTask != UIBackgroundTaskInvalid) {
    // if we are in here, that means the background task is already running.
    // don't restart it.
    return;
  }
  NSLog(@"Attempting to extend background running time");

  __block Boolean self_terminate = YES;

  _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"DummyTask" expirationHandler:^{
    NSLog(@"Background task expired by iOS");
    if (self_terminate) {
        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
        _backgroundTask = UIBackgroundTaskInvalid;
    }
  }];



 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"Background task started.  Waiting 175 seconds before cleanup.");
    [NSThread sleepForTimeInterval:175];

    //TODO: perform cleanup code if app is not in the foreground by now

  });
}
davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • davidgyoung strikes again! :) Thanks this worked perfectly. I didn't run the entire 175 seconds for now but i'm sure that future use cases (like the example you gave) will be needing a longer time, down the road. I set the time to 10 seconds and so far it's seems fairly stable although I have only run through the flow 4 times to test. – GoreDefex Jul 19 '17 at 14:43
  • Glad this is working. Can it be improved as follows? use didBecomeActive to check CBCentralMgr for connected BT devices. That + CBCentralMgr delegate to observe connections gives you definitive state on BT, which you can use to manage UI. Then, *only in didEnterBackground* (+ davidgyoung's suggestion if extra time is needed) switch from BT to iBeacon. No need to observe willResignActive at all. Maybe I'm misunderstanding the app's requirements, but that would clean up the state hooks a little. – danh Jul 19 '17 at 14:51
  • Yes @danh, I find it difficult to obfuscate the true purpose of my app since it is not my intellectual property and it is the IP of the company I work for. It most likely is causing a lot of confusion on what I need to accomplish. My apologies. – GoreDefex Jul 19 '17 at 14:58
  • @davidgyoung I have another question here if your interested https://stackoverflow.com/questions/45197868/objective-c-bluetooth-says-its-connected-but-cbcentralmanager-does-not-discover – GoreDefex Jul 19 '17 at 18:11