20

Inside my iOS Application I have an UIWebView.

Now I want all links that have the attribute target="_blank" not to open inside my WebView but externally in Safari.

How can I do this?

MrE
  • 19,584
  • 12
  • 87
  • 105
Norwald2
  • 695
  • 4
  • 9
  • 19

7 Answers7

38

My answer, which is a from an answer I found on stack overflow for the Android WebView. But actually, both webview have the same problem and same (dirty) fix:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request     navigationType:(UIWebViewNavigationType)navigationType
{
    if ([request.URL.absoluteString hasPrefix:@"newtab:"])
    {
        // JS-hacked URl is a target=_blank url - manually open the browser.
        NSURL *url = [NSURL URLWithString:[request.URL.absoluteString substringFromIndex:7]];
        [[UIApplication sharedApplication] openURL:url];

        return true;
    }

    return true;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    // JS Injection hack to solve the target="_blank" issue and open a real browser in such case.
    NSString *JSInjection = @"javascript: var allLinks = document.getElementsByTagName('a'); if (allLinks) {var i;for (i=0; i<allLinks.length; i++) {var link = allLinks[i];var target = link.getAttribute('target'); if (target && target == '_blank') {link.setAttribute('target','_self');link.href = 'newtab:'+link.href;}}}";
    [webView stringByEvaluatingJavaScriptFromString:JSInjection];
}

This solves both the target="_blank" issue to open in safari, AND keeps opening standard links within the webview.

Benjamin Piette
  • 3,645
  • 1
  • 27
  • 24
  • This is a very interesting approach. I just used this for changing the content of a 3rd party website so that the UIWebViewDelegate methods would get called consistently. Perhaps it is a shortcoming of UIWebViews that links with various target values respond and interact differently with the delegate. – Roderic Campbell Mar 13 '14 at 18:10
  • what is the link to the android app version of this answer? – Atma Apr 21 '15 at 20:21
  • I don't remember where the android version was, but something like that may help: http://stackoverflow.com/questions/8578332/webview-webchromeclient-method-oncreatewindow-not-called-for-target-blank – Benjamin Piette Apr 22 '15 at 06:45
  • Android can technically intercept ` – Russ May 21 '15 at 19:15
  • This is effectively the solution I had used for a project, (though using jQuery) however i had used a custom scheme `native://` with the command and had to escape the URL in javascript and then unescape it back in the `webView:shouldStartLoadWithRequest:...` but this simple prepending is much cleaner. Plus I need to do this on a new project where I can't rely on jQuery so your bare-bones javascript is very useful. Thanks! – Russ May 21 '15 at 19:27
  • Ok I have `hdfull.tv` website. The link is written as ` – Vyachaslav Gerchicov May 02 '16 at 08:55
  • Reviving this super old solution, but has anyone come across with issues where the javascript replaces '#' characters in a link with '%23' ? – venturidoo Mar 15 '18 at 23:14
  • Thanks @BenjaminPiette this works for me 6/10 times. not sure 4 times it open in same webview instead of opening Safari. have no clue where to check – newdeveloper Oct 28 '19 at 19:22
  • @BenjaminPiette https://stackoverflow.com/questions/58612520/nsurl-fastcstringcontentssome-link-url-giving-error-when-trying-to-open-from-i Please try to help on this question for the same issue. – newdeveloper Oct 29 '19 at 17:22
5

The problem with wedo's solution is that all your links will open in Safari.

Two solutions:

1 - JavaScript callback to Objective-C when target="_blank"
To achieve your problem you need to add some javascript on all your links, check if they have the attribute _blank, then call back your objective-C code from JavaScript and run:

[[UIApplication sharedApplication] openURL:myUrl];

I personally don't like this solution because it's a lot of code, callback, complexity and a bit tricky...

2 - Checking url parameter
If you have access to the HTML code (note in both solution you need access to HTML) I recommend you remove the target="_blank" and add the parameter ?openInSafari=true

In the UIWebViewDelegate add the following code:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (navigationType == UIWebViewNavigationTypeLinkClicked) {
        NSURL *url = [request URL];
        NSDictionary *param = [url queryParameters];
        NSString *openIsSafari = [param objectForKey:@"openInSafari"];

        if ( openIsSafari!= nil && ([openIsSafari isEqualToString:@"true"] ||  [openIsSafari isEqualToString:@"1"])){
            [[UIApplication sharedApplication] openURL:url];
            return NO;
        }
    }
    return YES;
}

A nice (bad?) point with this solution is that if the link x levels deeper can still open links into safari browser

<a href="http://www.google.com?openInSafari=true">Google in Safari</a>


Always add the protocol in the URL (http, https...)

Martin Magakian
  • 3,746
  • 5
  • 37
  • 53
  • like your version. but `queryParameters` is not an available function for NSURL. You need to parse params from `[url query]` – spankmaster79 Jan 15 '13 at 10:51
  • Thanks. I tried this as well and it works for me 6/10 times like above answer. not sure 4 times it open in same webview instead of opening Safari. have no clue where to check – newdeveloper Oct 28 '19 at 19:23
  • https://stackoverflow.com/questions/58612520/nsurl-fastcstringcontentssome-link-url-giving-error-when-trying-to-open-from-i please help on this related question. – newdeveloper Oct 29 '19 at 17:23
3

Kudos to Martin Magakian! Here is the modification based on spankmaster79's suggestion:

- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest: (NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType {
    if (navigationType == UIWebViewNavigationTypeLinkClicked) {
        NSURL *url = [request URL];
        NSString *param = [url query];

        if ([param rangeOfString: @"openInSafari=true"].location != NSNotFound){
            [[UIApplication sharedApplication] openURL: url];
            return NO;
        }
    }
    return YES;
}
mpemburn
  • 2,776
  • 1
  • 35
  • 41
2

Just in case if some one is looking for answer in Swift4

For internal loads make sure you call the decisionHandler() closure with .cancel so the load halts, while also calling UIApplication.shared.open() to have the URL open in the external browser.

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.host == "your url.com" {
            UIApplication.shared.open(url)
            decisionHandler(.cancel)
            return
        }
    }

    decisionHandler(.allow)
}
1

Try this.

UIApplication *app = [UIApplication sharedApplication];
NSURL         *url = navigationAction.request.URL;

if (!navigationAction.targetFrame) {
    if ([app canOpenURL:url]) {
        [app openURL:url];
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
}
if ([url.scheme isEqualToString:@"mailto"])
{
    if ([app canOpenURL:url])
    {
        [app openURL:url];
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
}

decisionHandler(WKNavigationActionPolicyAllow);
0

I based my answer on the one from Benjamin Piette but needed to adjust the script since the links to be adjusted in my case were generated asynchronously by an other javascript.

NSString* const kOpenInNewTabPrefix = @"myOpenInNewTabPrefix:";//This NEEDS to end with ':'

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request     navigationType:(UIWebViewNavigationType)navigationType
{
    if ([[request.URL.absoluteString lowercaseString] hasPrefix:[kOpenInNewTabPrefix lowercaseString]])
    {
        // JS-hacked URl is a target=_blank url - manually open the browser.
        NSURL *url = [NSURL URLWithString:[request.URL.absoluteString substringFromIndex:[kOpenInNewTabPrefix length]]];
        [[UIApplication sharedApplication] openURL:url];

        return YES;
    }

    return YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //based on http://stackoverflow.com/questions/8490038/open-target-blank-links-outside-of-uiwebview-in-safari
    // JS Injection hack to solve the target="_blank" issue and open a real browser in such case.
    NSString *JSInjection = [NSString stringWithFormat:@"javascript: "
                             "document.getElementsByTagName('body')[0].addEventListener('click', function(e){"
                             "  var a = e.target;"
                             "  if(a.nodeName != 'A'){"
                             "      return;"
                             "  }"
                             "  var target = a.target;"
                             "  var href = a.href;"
                             "  var prefix = '%@';"
                             "  if(href.substring(0, %lu) != '%@' && target == '_blank'){"
                             "      a.href = prefix + href;"
                             "  }"
                             "})"
                             , [kOpenInNewTabPrefix lowercaseString]
                             , (unsigned long)[kOpenInNewTabPrefix length]
                             , [kOpenInNewTabPrefix lowercaseString]];
    [webView stringByEvaluatingJavaScriptFromString:JSInjection];
}
clauswey
  • 1,706
  • 1
  • 12
  • 15
0

I had the same question and unfortunately these answers drew me in a completely wrong and very complex way. Really the question is answered as simply as "you need to use WebViewPolicyDelegateProtocol".

In -viewDidLoad of the view controller implementation you write:

[myWebView setPolicyDelegate:self];

In your view controller class interface you must add two items:

- (void)webView:(WebView *)webView 
decidePolicyForNavigationAction:(NSDictionary *)actionInformation 
        request:(NSURLRequest *)request 
          frame:(WebFrame *)frame 
decisionListener:(id<WebPolicyDecisionListener>)listener;
- (void)webView:(WebView *)webView 
decidePolicyForNewWindowAction:(NSDictionary *)actionInformation 
        request:(NSURLRequest *)request 
   newFrameName:(NSString *)frameName 
decisionListener:(id<WebPolicyDecisionListener>)listener;

And implement them as easy as:

- (void)webView:(WebView *)webView 
decidePolicyForNavigationAction:(NSDictionary *)actionInformation 
        request:(NSURLRequest *)request 
          frame:(WebFrame *)frame 
decisionListener:(id<WebPolicyDecisionListener>)listener {
    // just the default behavior, though you're free to add any url filtering you like...
    [listener use];
}
- (void)webView:(WebView *)webView 
decidePolicyForNewWindowAction:(NSDictionary *)actionInformation 
        request:(NSURLRequest *)request 
   newFrameName:(NSString *)frameName 
decisionListener:(id<WebPolicyDecisionListener>)listener {
    // frameName is your "target" parameter value
    if([frameName isEqualToString:@"_blank"]) {
      [[NSWorkSpace sharedWorkSpace] loadURL:[request URL]];
    } else {
        [listener use];
    }
}

Also refer to the Apple docs

I've used this way in my project, where frameset is used in the root HTML, loaded into the WebView. All cross-links pointing to another existing frame don't cause the second message call, so only new (external) targets are processed here. It works OK for me.

DeadlineX
  • 129
  • 1
  • 4