137

WKWebView does not open any links which have target="_blank" a.k.a. 'Open in new Window' attribute in their HTML <a href>-Tag.

Tamás Sengel
  • 47,657
  • 24
  • 144
  • 178
stk
  • 5,687
  • 10
  • 39
  • 53

15 Answers15

212

My solution is to cancel the navigation and load the request with loadRequest: again. This will be come the similar behavior like UIWebView which always open new window in the current frame.

Implement the WKUIDelegate delegate and set it to _webview.uiDelegate. Then implement:

- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
  if (!navigationAction.targetFrame.isMainFrame) {
    [webView loadRequest:navigationAction.request];
  }

  return nil;
}
Lukas
  • 1,264
  • 6
  • 23
  • 48
Cloud Xu
  • 2,837
  • 2
  • 13
  • 14
  • 5
    This is the correct answer. We tried the accepted answer with no success. But @Cloud's answer works, and [this response on the dev forums](https://devforums.apple.com/message/1059229#1059229) explains why. – Christopher Pickslay Oct 22 '14 at 19:05
  • 6
    This solution worked for me. Don't forget to set the `UIDelegate` property, since this methods is declared in `WKUIDelegate` not `WKNavigationDelegate`. – Taketo Sano Oct 28 '14 at 05:53
  • 1
    Ok, I see that people wanna use the WKWebView like this. I implemented the possibility to open target=_blank-Links in the same WKWebView (as shown above) in my project https://github.com/sticksen/STKWebKitViewController. – stk Nov 03 '14 at 17:09
  • @ChristopherPickslay But how do you open links that should be opened in an other applications? Appstore links? There is an issue http://stackoverflow.com/questions/29056854/how-can-i-understand-if-uiapplication-is-going-to-open-link-in-safari-app – BergP Mar 15 '15 at 03:23
  • @BergP we parse the link. If the host contains `itunes.apple.com`, we call `openURL:`. Otherwise, if the scheme starts with `http`, we do the above. Finally if it's not an app store link or http url, we call `openURL:`. Incidentally, we also show a prompt before calling `openURL:`, giving the user the option to ignore it, as lots of sites and ads are now doing auto-redirects to their app or the App Store. – Christopher Pickslay Mar 16 '15 at 17:17
  • 5
    @ChristopherPickslay when using this method with my WKWebView the request url is always blank, so the webview directs to a blank white page, any ideas? – Jason Murray Mar 24 '15 at 10:12
  • 1
    @Cloud can we get this in Swift? – Jed Grant Jun 02 '15 at 16:57
  • @JasonMurray Refer to my stack overflow [question](http://stackoverflow.com/questions/31955880/wkwebview-not-opening-some-target-blank-links) regarding this issue and how I resolved it . HTH – shrutim Aug 12 '15 at 23:24
  • 1
    When the '_blank' request is sent from a form with post data, I find the post data will be lost!!! Any help? @Cloud Wu – Andrew Feb 23 '17 at 09:36
  • Having the same issue with @JasonMurray. Rolled back to UIWebView with the custom shitty swipe back gesture. – felixwcf Feb 01 '18 at 10:06
79

The answer from @Cloud Xu is the correct answer. Just for reference, here it is in Swift:

// this handles target=_blank links by opening them in the same view
func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
    if navigationAction.targetFrame == nil {
        webView.loadRequest(navigationAction.request)
    }
    return nil
}
Bill Weinman
  • 1,626
  • 14
  • 11
  • 2
    how would you write it if you wanted it to open in Safari instead? Also what do I need to reference in the view controller to get this to work? – Jed Grant Jun 02 '15 at 16:57
  • 1
    To get the new page to open in mobile safari, see this answer: http://stackoverflow.com/a/30604481/558789 – Paul Bruneau Sep 23 '16 at 16:52
  • 1
    This works for links, but doesn't seem to detect new windows opened with JavaScript, i.e., window.open(url, "_blank") – Crashalot Oct 28 '16 at 18:51
  • FYI webView.loadRequest has apparently been renamed webView.load in recent versions of the API – Bill Weinman Jun 02 '20 at 19:57
58

To use latest version of Swift 4.2+

import WebKit

Extend your class with WKUIDelegate

Set delegate for webview

self.webView.uiDelegate = self

Implement protocol method

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
        webView.load(navigationAction.request)
    }
    return nil
}
muhasturk
  • 1,965
  • 15
  • 15
  • Not a real duplicate. There are differences in the optionality of the parameters that do not conform to the Swift 4.1 WKUIDelegate protocol in the answer you linked to. – yakattack Apr 03 '18 at 21:43
  • Hi, When I'm loading WKWebview with Pinterest URL, I was unable to open "Continue with Facebook". Now I can able to click and get the information on "Continuew with Google". Help me out please. – Nrv Apr 23 '18 at 08:37
  • I am facing the same issue with wkwebview ; login and redirect not worked in my app. Any Help ? – Jamshed Alam Mar 27 '19 at 05:14
  • 2
    please anyone can help me out, I have assigned delegate uiDelegate to wkwebview but its method create web view with configuration is not being called – chetan panchal Jul 22 '19 at 10:21
30

Add yourself as the WKNavigationDelegate

_webView.navigationDelegate = self;

and implement following code in the delegate callback decidePolicyForNavigationAction:decisionHandler:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    //this is a 'new window action' (aka target="_blank") > open this URL externally. If we´re doing nothing here, WKWebView will also just do nothing. Maybe this will change in a later stage of the iOS 8 Beta
    if (!navigationAction.targetFrame) { 
        NSURL *url = navigationAction.request.URL;
        UIApplication *app = [UIApplication sharedApplication];
        if ([app canOpenURL:url]) {
            [app openURL:url];
        }
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

P.S.: This code is from my little project STKWebKitViewController, which wraps a usable UI around WKWebView.

stk
  • 5,687
  • 10
  • 39
  • 53
  • 1
    There's a typo. A dot is missing between WKNavigationActionPolicy and Allow – chicken Feb 17 '15 at 16:58
  • @stk How can I understand if UIApplication is going to open link in Safari app? If it is Appstore link - I need to open ot in the Appstore app, but if it is usual web page - I need to open it in the same WKWebView http://stackoverflow.com/questions/29056854/how-can-i-understand-if-uiapplication-is-going-to-open-link-in-safari-app – BergP Mar 15 '15 at 03:25
  • 1
    @LeoKoppelkamm It's not a typo rather it's Objective-C not Swift. ;) – Ayan Sengupta Jul 28 '16 at 00:39
  • @Crashalot Did you find the solution for window.open ? – Sam Dec 20 '16 at 01:59
  • @Sam Have you checked this answer ? https://stackoverflow.com/questions/33190234/wkwebview-and-window-open – Jose Rego Jun 15 '18 at 13:37
  • This is the correct answer - Cloud's answer above is using a method deprecated since ios9 – WeezyKrush Mar 26 '20 at 19:41
19

If you've already set the WKWebView.navigationDelegate

WKWebView.navigationDelegate = self;

you just need to implement:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    BOOL shouldLoad = [self shouldStartLoadWithRequest:navigationAction.request]; // check the url if necessary

    if (shouldLoad && navigationAction.targetFrame == nil) {
        // WKWebView ignores links that open in new window
        [webView loadRequest:navigationAction.request];
    }

    // always pass a policy to the decisionHandler
    decisionHandler(shouldLoad ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel);
}

this way you don't need to implement the WKUIDelegate method.

kelin
  • 9,553
  • 6
  • 63
  • 92
Boris Georgiev
  • 191
  • 1
  • 3
15

Cloud xu's answer solves my issue.

In case someone need the equivalent Swift(4.x/5.0) version, here is it:

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    if let frame = navigationAction.targetFrame,
        frame.isMainFrame {
        return nil
    }
    // for _blank target or non-mainFrame target
    webView.load(navigationAction.request)
    return nil
}

Of course you have to set webView.uiDelegate firstly.

Benjamin Wen
  • 2,386
  • 1
  • 21
  • 38
8

None of those solutions worked for me, I did solve the issue by :

1) Implementing WKUIDelegate

@interface ViewController () <WKNavigationDelegate, WKUIDelegate>

2) Setting the UIDelegate delegate of the wkWebview

self.wkWebview.UIDelegate = self;

3) Implementing the createWebViewWithConfiguration method

- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {

if (!navigationAction.targetFrame.isMainFrame) {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    [[UIApplication sharedApplication] openURL:[navigationAction.request URL]];
}
return nil;  }
Ugo Marinelli
  • 635
  • 10
  • 14
7

I confirm that Bill Weinman's Swift code is correct. But need to mention that you also need to delegate UIDelegate for it to work, in case you are new to iOS developing like me.

Something like this:

self.webView?.UIDelegate = self

So there's three places that you need to make changes.

Dan Beaulieu
  • 17,926
  • 15
  • 92
  • 126
Oliver Zhang
  • 381
  • 3
  • 14
  • Type in your code, it's: `self.webView.UIDelegate = self` – Henrik Petterson May 26 '16 at 12:09
  • Not sure if it's implied, but in order for this line of code to work in iOS 10 you `ViewController` needs to inherit `WKUIDelegate`. i.e. `class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate` – ltrainpr Oct 27 '16 at 20:54
4

You can also push another view controller, or open a new tab, etc:

func webView(webView: WKWebView, createWebViewWithConfiguration configuration: WKWebViewConfiguration, forNavigationAction navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    var wv: WKWebView?

    if navigationAction.targetFrame == nil {
        if let vc = self.storyboard?.instantiateViewControllerWithIdentifier("ViewController")  as? ViewController {
            vc.url = navigationAction.request.URL
            vc.webConfig = configuration
            wv = vc.view as? WKWebView

            self.navigationController?.pushViewController(vc, animated: true)
        }
    }

    return wv
}
David H
  • 39,114
  • 12
  • 86
  • 125
  • Is it necessary to set `vc.url`? I'm not setting it, and the web view is being loaded properly. Also, in my experience I am only seeing `createWebViewWithConfiguration` called when `navigationAction.targetFrame` is `nil`. Can you describe a scenario where this wouldn't be true? – Mason G. Zhwiti May 27 '15 at 16:32
  • @MasonG.Zhwiti I was actively using WKWebViews for a project last fall, but since then haven't done anything with them - so I really cannot answer your question. At the time I posted the above it did work. I cannot fathom how it works without setting the vc.url though. – David H May 27 '15 at 17:59
  • I think it works without because you're returning the web view you created, and then (I'm guessing) whatever asked you to create it takes care of using the web view to load the URL in question. – Mason G. Zhwiti May 27 '15 at 23:04
3

Based on allen huang answer

Details

  • Xcode Version 10.3 (10G8), Swift 5

Targets

  • detect links with target=“_blank”
  • push view controller with webView if current controller has navigationController
  • present view controller with webView in all other cases

Solution

webView.uiDelegate = self

// .....

extension ViewController: WKUIDelegate {
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard   navigationAction.targetFrame == nil,
                let url =  navigationAction.request.url else { return nil }
        let vc = ViewController(url: url, configuration: configuration)
        if let navigationController = navigationController {
            navigationController.pushViewController(vc, animated: false)
            return vc.webView
        }
        present(vc, animated: true, completion: nil)
        return nil
    }
}

Full sample

Info.plist

add in your Info.plist transport security setting

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

ViewController

import UIKit
import WebKit

class ViewController: UIViewController {

    private lazy var url = URL(string: "https://www.w3schools.com/html/tryit.asp?filename=tryhtml_links_target")!
    private weak var webView: WKWebView!

    init (url: URL, configuration: WKWebViewConfiguration) {
        super.init(nibName: nil, bundle: nil)
        self.url = url
        navigationItem.title = ""
    }

    required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }

    override func viewDidLoad() {
        super.viewDidLoad()
        initWebView()
        webView.loadPage(address: url)
    }

    private func initWebView() {
        let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
        view.addSubview(webView)
        self.webView = webView
        webView.navigationDelegate = self
        webView.uiDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        webView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
        webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        webView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
    }
}

extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        guard let host = webView.url?.host else { return }
        navigationItem.title = host
    }
}

extension ViewController: WKUIDelegate {
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard   navigationAction.targetFrame == nil,
                let url =  navigationAction.request.url else { return nil }
        let vc = ViewController(url: url, configuration: configuration)
        if let navigationController = navigationController {
            navigationController.pushViewController(vc, animated: false)
            return vc.webView
        }
        present(vc, animated: true, completion: nil)
        return nil
    }
}

extension WKWebView {
    func loadPage(address url: URL) { load(URLRequest(url: url)) }
    func loadPage(address urlString: String) {
        guard let url = URL(string: urlString) else { return }
        loadPage(address: url)
    }
}

Storyboards

Version 1

enter image description here

Version 2

enter image description here

Vasily Bodnarchuk
  • 19,860
  • 8
  • 111
  • 113
2

This worked for me:

-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {

if (!navigationAction.targetFrame.isMainFrame) {


    WKWebView *newWebview = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    newWebview.UIDelegate = self;
    newWebview.navigationDelegate = self;
    [newWebview loadRequest:navigationAction.request];
    self.view = newWebview;

    return  newWebview;
}

return nil;
}

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

    decisionHandler(WKNavigationActionPolicyAllow);
}

- (void)webViewDidClose:(WKWebView *)webView {
    self.view = self.webView;
}

As you can see, what we do here is just opening a new webViewwith the new url and controlling the posibility of being closed, just if you need a response from that second webviewto be displayed on the first.

Aitor Pagán
  • 423
  • 5
  • 17
1

I encountered some issues that can not be resolved by just using webView.load(navigationAction.request). So I use create a new webView to do and it just works fine.

//MARK:- WKUIDelegate
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    NSLog(#function)

    if navigationAction.targetFrame == nil {
        NSLog("=> Create a new webView")

        let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
        webView.uiDelegate = self
        webView.navigationDelegate = self

        self.webView = webView

        return webView
    }
    return nil
}
allen huang
  • 135
  • 1
  • 3
1

To open _blank pages in Mobile Safari and if you use Swift:

webView.navigationDelegate = self

And implement this in the WKNavigationDelegate:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if navigationAction.targetFrame == nil, let url = navigationAction.request.url {
        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
    }
    decisionHandler(WKNavigationActionPolicy.allow)
}
pegpeg
  • 1,049
  • 8
  • 10
0
**Use following function to create web view**

func initWebView(configuration: WKWebViewConfiguration) 
{
        let webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view.addSubview(webView)
        self.webView = webView
    }

**In View Did Load:**

 if webView == nil { initWebView(configuration: WKWebViewConfiguration()) }
   webView?.load(url: url1)


**WKUIDelegate Method need to be implemented**

extension WebViewController: WKUIDelegate {

    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        // push new screen to the navigation controller when need to open url in another "tab"
        print("url:\(String(describing: navigationAction.request.url?.absoluteString))")
        if let url = navigationAction.request.url, navigationAction.targetFrame == nil {
            let viewController = WebViewController()
            viewController.initWebView(configuration: configuration)
            viewController.url1 = url
            DispatchQueue.main.async { [weak self] in
                self?.navigationController?.pushViewController(viewController, animated: true)
            }
            return viewController.webView
        }

        return nil
    }
}

extension WKWebView 

{
    func load(url: URL) { load(URLRequest(url: url)) }
}
jayant rawat
  • 184
  • 11
0

Use this method to download pdf in web view

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?