Norwald2
Norwald2

Reputation: 705

Open target="_blank" links outside of UIWebView in Safari

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?

Upvotes: 21

Views: 27735

Answers (7)

Sujith Sreedhar
Sujith Sreedhar

Reputation: 11

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

Upvotes: 1

Sujay Patil
Sujay Patil

Reputation: 21

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

Upvotes: 2

DeadlineX
DeadlineX

Reputation: 139

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.

Upvotes: 0

clauswey
clauswey

Reputation: 1741

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

Upvotes: 0

Benjamin Piette
Benjamin Piette

Reputation: 3783

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.

Upvotes: 38

mpemburn
mpemburn

Reputation: 2884

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

Upvotes: 3

Martin Magakian
Martin Magakian

Reputation: 3766

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

Upvotes: 5

Related Questions