42

I'm currently developing an App, that needs to open a browser to display a webpage. To do that i use the [UIApplication sharedApplication] openURL method with an url.

In iOS 6 this works perfectly, but in iOS 7 it freezes the app for 10+ seconds, then opens the browser and all is good.

This happens using ad hoc provisioning. Someone on the internet commented that this was a known problem, however, that one comment was all i could find regarding this problem.

smoove
  • 3,920
  • 3
  • 25
  • 31

11 Answers11

94

I noticed the same problem when calling -[UIApplication openUrl:] from the Application Delegate didReceiveRemoteNotification: or didFinishLaunchingWithOptions: since iOS 7.

I solved it by delaying the call a bit using GCD :

// objc
dispatch_async(dispatch_get_main_queue(), ^{
    [[UIApplication sharedApplication] openURL:url];
});

It let iOS some time to finish application initialization and the call is then performed without any problem. Don't ask me why.

Does this works for you ?

As this answer is often seen, I added the swift version:

// swift
dispatch_async(dispatch_get_main_queue()) {
    UIApplication.sharedApplication().openURL(url)
}
kamidude
  • 1,301
  • 8
  • 13
  • 2
    Did you find out what was the root cause for this? fixing this is like putting a "patch"... i would like to know what is the reason for this. – lysergic-acid Apr 07 '14 at 12:30
  • This is one of the more bizarre things I have seen so far with IOS Thanks for the fix – Stein van Broekhoven May 19 '14 at 20:34
  • 6
    FYI, this workaround no longer seems to work in iOS 9. I've also seen that the delay can cause a crash due to "failed to scene-update after 10.00s". Moving the opening of the URL to a background thread does seem to fix the problem so changing the dispatch_get_main_queue to dispatch_get_global_queue does the job. – mcsheffrey Aug 17 '15 at 20:45
  • Interesting i'll test this – kamidude Aug 17 '15 at 21:02
  • 1
    Happened for me too. Notably, it still works on iOS 9.1 public beta for me, unlike what @mcsheffrey reports. Also, mcsheffrey you shouldn't send messages to any class named "UI*" on any thread but Thread 1 (with some few exceptions, like UIImages -- but read the docs) – Eric G Sep 30 '15 at 00:11
  • I was going to ask why...but then I see you said "Don't ask why". So maybe it's just some mysterious behavior of iOS.. – John the Traveler Dec 20 '15 at 03:36
  • also works in iOS 9.2 - dispatch_get_main_queue! You saved me. Thank you! – Async- Feb 01 '16 at 12:30
  • In iOS10, call -openURL:options:completionHandler: method also solve the problem – yuyeqingshan May 08 '17 at 11:47
10

I have seen the same issue in iOS 7. My solution is only slightly different from those already proposed. By using performSelector with just a 0.1 second delay, the app immediately opens the URL.

[self performSelector:@selector(methodToRedirectToURL:) withObject:url afterDelay:0.1];
lidsinker
  • 1,190
  • 1
  • 11
  • 20
9

Had the exact same symptoms that you described: worked fine on iOS6, but ~10 second hang on iOS7. Turns out to be a threading issue.

We were issuing the [UIApplication sharedApplication] openURL directly from the AppDelegate method applicationDidBecomeActive(). Moving this to a background thread instantly solved the problem:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    ...

    // hangs for 10 seconds
    // [[UIApplication sharedApplication] openURL:[NSURL URLWithString: url]];

    // Fix: use threads!
    [NSThread detachNewThreadSelector:@selector(openbrowser_in_background:) toTarget:self withObject:url];

    ...
}

- (void)openbrowser_in_background:(NSString *)url
{
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString: url]];
}
colm.anseo
  • 19,337
  • 4
  • 43
  • 52
  • This honestly seems like an awful solution to the problem, not only because spawning a new thread comes with a huge overhead. – JustSid Oct 23 '13 at 20:23
  • 7
    @JustSid the `[UIApplication sharedApplication] openURL: ...]` API that the OP is using causes a context switch to another app (Safari in this case) - which is an expensive operation in and of itself. The cost of creating a thread < 1ms? All the while saving a potentially 10 *second* timeout is a pretty good trade. – colm.anseo Oct 24 '13 at 11:50
7

Thanks for the advise from all the guys above, this is how I solved it in Xamarin.iOS (and Xamarin.Forms). The solution is inspired by what the guys have discussed above, and hope it helps others facing the same problem but using Xamarin.


[Register("AppDelegate")]
public class AppDelegate
{
     ....

 public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
 {
      // We do some logic to respond to launching app, and return to that app. 
      Task.Delay(500).ContinueWith(_ => {
            this.InvokeOnMainThread( () => {
                UIApplication.SharedApplication.OpenUrl(NSUrl.FromString(openUri));
            });
        });
 }

}

Has AlTaiar
  • 4,052
  • 2
  • 36
  • 37
  • It´s working fine, but you can change `UIApplication.SharedApplication.OpenUrl` for `application.OpenUrl` – xleon Aug 05 '17 at 17:52
5

After doing some very quick benchmarking I found @lidsinkers method to quite clearly be the fastest. Especially when I replaced the delay of 0.1 with 0.001.

Thus I decided to convert it to Swift code:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
            UIApplication.sharedApplication().openURL(url)
        }

Full method:

/// An attempt at solving 'openUrl()' freeze problem
func lidsinkerOpenURL(url: NSURL) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
            UIApplication.sharedApplication().openURL(url)
        }
    }
Jacob R
  • 1,243
  • 1
  • 16
  • 23
4

For ios 9

if([[UIApplication sharedApplication] canOpenURL:url]){            
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [[UIApplication sharedApplication] openURL:url];
        });
    }

this seems to have worked for me

Zaartha
  • 1,106
  • 9
  • 25
  • 2
    Worked for me on iOS 10 as well. The important thing was that openURL is _not_ called on the main queue, despite some of the other answers! – pipacs Feb 28 '17 at 13:01
  • 1
    Worked like a charm in iOS 12.4 and 13.5 as well. Thanks! – Wing Jun 15 '20 at 03:05
1

I found it will get better to use this since iOS 10.

dispatch_async(dispatch_get_main_queue(), ^{
    if ([[[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."].firstObject integerValue] < 10) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel:..."]];
    } else {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel:..."] options:@{} completionHandler:^(BOOL success) {

        }];
    }
});
Lumialxk
  • 6,239
  • 6
  • 24
  • 47
0

If you put the "openURL" action in the viewDidLoad method, then it sure will execute slowly. You can put it in the viewDidAppear method. Or, you can use the GCD in the viewDidLoad method like below:

dispatch_async(dispatch_get_main_queue(), ^{
    [[UIApplication sharedApplication] openURL:url];
});
Chris Forever
  • 678
  • 1
  • 5
  • 18
0

Here is the answer in Swift 3.0 with a check to see if we can open the URL or not.

guard let url = URL(string: myURLString) else {
    return
}


if UIApplication.shared.canOpenURL(url) {
   DispatchQueue.main.async {
     UIApplication.shared.openURL(url)
   }
}
Jon Vogel
  • 5,244
  • 1
  • 39
  • 54
0

Swift 4.1 with OS version check.

DispatchQueue.main.async() {
  if #available(iOS 10.0, *) {
    UIApplication.shared.open(url)
  } else {
    UIApplication.shared.openURL(url)
  }
}
Yash
  • 227
  • 1
  • 4
0

For Swift3

    DispatchQueue.main.async {
        UIApplication.shared.openURL(url)
    }