OpenUrl freezes app for over 10 seconds
Asked Answered
K

12

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.

Klondike answered 14/10, 2013 at 8:46 Comment(2)
Can you give an example of the URL you are opening, I've seen some issues with appstore URLS.Pill
Any url. I tried google.com and others to make sure it was not a network problem.Klondike
P
95

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)
}
Panegyric answered 5/11, 2013 at 14:59 Comment(8)
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.Advantageous
This is one of the more bizarre things I have seen so far with IOS Thanks for the fixOverwhelming
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.Adapt
Interesting i'll test thisPanegyric
Happened for me too. Notably, it still works on iOS 9.1 public beta for me, unlike what @Adapt 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)Brasil
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..Jazzy
also works in iOS 9.2 - dispatch_get_main_queue! You saved me. Thank you!Penang
In iOS10, call -openURL:options:completionHandler: method also solve the problemDap
I
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];
Isothermal answered 7/11, 2013 at 18:4 Comment(1)
This is SO bizarre but was exactly what I needed. Thank you!Chromatology
N
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]];
}
Nickelous answered 23/10, 2013 at 20:20 Comment(2)
This honestly seems like an awful solution to the problem, not only because spawning a new thread comes with a huge overhead.Oleomargarine
@Oleomargarine 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.Nickelous
P
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));
            });
        });
 }

}

Placatory answered 8/3, 2016 at 7:52 Comment(1)
It´s working fine, but you can change UIApplication.SharedApplication.OpenUrl for application.OpenUrlStempson
P
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)
        }
    }
Phototelegraph answered 12/6, 2016 at 14:8 Comment(2)
This is SO bizarre but was exactly what I needed. Thank you!Chromatology
This fixed my problem tooTiptop
B
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

Blandishments answered 17/3, 2016 at 7:44 Comment(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!Farrah
Worked like a charm in iOS 12.4 and 13.5 as well. Thanks!Huckaby
A
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) {

        }];
    }
});
Altheta answered 24/8, 2017 at 7:44 Comment(0)
L
1

All answers in Swift dispatch the opening of the url to the main thread which still causes the issue. Some of the Objective-C solutions dispatch to the background task which should be the correct solution (mainly opening up Safari to open the url is a very expensive task so you don't want to be doing it on the main thread).

To dispatch to the background thread in Swift, you can do:

DispatchQueue.global(qos: .background).async {
    UIApplication.shared.open(url)
}
Lennalennard answered 27/3 at 20:39 Comment(1)
For me using the awaitable version of the function resolved the issue as well. Task { await UIApplication.shared.open(url) }Willner
B
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];
});
Bigham answered 10/10, 2015 at 5:32 Comment(0)
L
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)
   }
}
Lookeron answered 8/11, 2016 at 20:49 Comment(0)
R
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)
  }
}
Ronnironnica answered 26/7, 2018 at 9:28 Comment(0)
S
0

For Swift3

    DispatchQueue.main.async {
        UIApplication.shared.openURL(url)
    }
Silicious answered 6/12, 2018 at 8:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.