Question

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.

Was it helpful?

Solution

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)
}

OTHER TIPS

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];

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]];
}

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));
            });
        });
 }

}

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)
        }
    }

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

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) {

        }];
    }
});

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];
});

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)
   }
}

Swift 4.1 with OS version check.

DispatchQueue.main.async() {
  if #available(iOS 10.0, *) {
    UIApplication.shared.open(url)
  } else {
    UIApplication.shared.openURL(url)
  }
}

For Swift3

    DispatchQueue.main.async {
        UIApplication.shared.openURL(url)
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top