3

Here's my app scenario: When user swipes a notification I will launch some other app via URL. So it basically launches some other app when notification arrives.

Currently to handle swiping notification scenario, when

- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)

method is invoked, within this method, I call processNotification: method, which contains:

...
[[UIApplication sharedApplication] openURL:url];
...
  • If push received while app is active, url is opened perfectly fine.
  • If push received by swiping or clicking on notification, url is opened in the background but the currently viewed app is my application. For example if my url is tel:123-456-7890, iOS starts the call (you can hear the sound) but active app is not Phone.app, it is my app.

That seemed pretty strange to me. However if I wait for UI to load, and call processNotification: after that, it brings up Phone.app window correctly. (bug in platform? because call happens but my UI is on the top.)

I need a method to delay execution of this processNotification: call, (maybe through an operation queue) until a view controller is loaded. Otherwise, my app stays on top and the URL is opened in the background.

ahmet alp balkan
  • 42,679
  • 38
  • 138
  • 214
  • Have you looked at `NSTimer` or `performSelector:withObject:afterDelay:` ? – BergQuester Oct 15 '13 at 19:19
  • @BergQuester, I am just not sure about how much delay should I put to app become active so when I `openURL`, it launches the other app. – ahmet alp balkan Oct 15 '13 at 19:34
  • e.g right now I tried delay 0.5, but I'm not sure things will be ready at t=0.5s. – ahmet alp balkan Oct 15 '13 at 20:05
  • Timers and delays are added to the run loop and aren't exact. It will not be executed until the current run through the runloop is completed. – BergQuester Oct 15 '13 at 20:09
  • 1
    Yeah, I don't know for sure, but I would bet that simply pushing it out to the end of the runloop by doing `-performSelector:withObject:afterDelay:` with a delay of 0 would be enough. – ipmcc Oct 16 '13 at 15:16
  • @ipmcc I think the problem here is that SpringBoard is unable to cope with one app transition being called while another is in progress. An iOS bug of course. `applicationDidBecomeActive:` is called after the transition animation has finished, so it should be safe to open another app there. – Léo Natan Oct 18 '13 at 02:07

2 Answers2

7

I was facing the same issue, I moved the openURL into main_queue and it seems to be working fine. I did not have to even make that change in didBecomeActive

dispatch_async(dispatch_get_main_queue(), ^(void){
  [[UIApplication sharedApplication] openURL:url];
});
darshansonde
  • 491
  • 1
  • 8
  • 13
3

You should delay your handling of the push notification (i.e. calling openURL:) until applicationDidBecomeActive:. Keep the parameters you need from application:didFinishLaunchingWithOptions: but only call your handling code in applicationDidBecomeActive:.

I think the problem here is that SpringBoard is unable to cope with one app transition being called while another is in progress. An iOS bug of course. You should open a bug report at https://bugreport.apple.com

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • Delaying the `openURL:` logic to `aplicationDidBecomeActive:` did not solve it unfortunately. The issue is still there. This happens even if the app was the one active before phone goes into lock. If I swipe the push, app is already there when device is unlocked and when it gets the notification and performs `openURL:` and it keeps staying on top. `:-/` – ahmet alp balkan Oct 21 '13 at 07:38
  • Another option is to go into "voodoo magic" solutions, such as `performSelector:afterDelay:`. These work wonders, but are risky, as they involve timing issues which can cause additional trouble. – Léo Natan Oct 21 '13 at 08:22
  • Yeah, in the question comments, I tried `performSelector:afterDelay:` with some hardcoded value e.g. 0.5 sec, but it was randomly failing with the same issue since hard-coded timing does not guarantee things to be ready. I guess I will be buying some Apple Developer Technical Support tickets. – ahmet alp balkan Oct 21 '13 at 18:02
  • Did you `performSelector:afterDelay:` in `didFinishLaunch` or in `didBecomeActive`? I would still recommend doing it in `didBecomeActive`. – Léo Natan Oct 21 '13 at 18:35
  • I moved it to `didBecomeActive` now, it performed slightly better with delay of 0.5 s. However, I still see the glitch from time to time. It takes iOS to start phone app 2-3 seconds after my app is ready sometimes. If I couldn't find another solution I'll mark this as correct. – ahmet alp balkan Oct 21 '13 at 20:29
  • Could you perhaps make a design change? iOS has limitations and designing applications should be, to some degree, around those limitation. For example, displaying an alert for the user in the form of "Will call number 90347239847, are you sure? [Cancel] [Call]" would alleviate your problem while giving a relatively good experience to the user. – Léo Natan Oct 21 '13 at 20:31
  • I solved the problem. Even though app comes to didBecomeActive, applicationState was inactive. I put retries to wait until it becomes active, and when it does, I execute openURL, works quite fine. Thanks for help. – ahmet alp balkan Oct 22 '13 at 18:29
  • I think better would be to add an observer on the application state property, instead of polling. – Léo Natan Oct 22 '13 at 18:41
  • Adding an observer didn't work for me btw so I had to use a timer to pull `//[application addObserver:self forKeyPath:@"applicationState" options:NSKeyValueObservingOptionNew context:NULL];` `[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(checkIfApplicationIsActive:) userInfo:userInfo repeats:YES];` – Emerson Malca Aug 01 '15 at 06:25