¿Por qué WKWebView no abre enlaces con target = "_ blank"?

122

WKWebViewno abre ningún enlace que tenga target="_blank"el atributo "Abrir en una nueva ventana" en su etiqueta HTML <a href>.

stk
fuente
Actualice la respuesta correcta marcada
Efren

Respuestas:

184

Mi solución es cancelar la navegación y cargar la solicitud con loadRequest: nuevamente. Esto vendrá con un comportamiento similar como UIWebView que siempre abre una nueva ventana en el marco actual.

Implemente el WKUIDelegatedelegado y configúrelo en _webview.uiDelegate. Luego implemente:

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

  return nil;
}
Cloud Xu
fuente
4
Esta es la respuesta correcta. Probamos la respuesta aceptada sin éxito. Pero la respuesta de @ Cloud funciona, y esta respuesta en los foros de desarrollo explica por qué.
Christopher Pickslay
5
Esta solución funcionó para mí. No olvide establecer la UIDelegatepropiedad, ya que este método se declara en WKUIDelegatenot WKNavigationDelegate.
Taketo Sano
1
Ok, veo que la gente quiere usar WKWebView de esta manera. Implementé la posibilidad de abrir target = _blank-Links en el mismo WKWebView (como se muestra arriba) en mi proyecto github.com/sticksen/STKWebKitViewController .
stk
5
@ChristopherPickslay cuando se usa este método con mi WKWebView, la URL de solicitud siempre está en blanco, por lo que la vista web dirige a una página en blanco en blanco, ¿alguna idea?
Jason Murray
1
Cuando la solicitud '_blank' se envía desde un formulario con datos de publicación, ¡encuentro que los datos de publicación se perderán! ¿Alguna ayuda? @Cloud Wu
Andrew
66

La respuesta de @Cloud Xu es la respuesta correcta. Solo como referencia, aquí está en 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
fuente
2
¿Cómo lo escribirías si quisieras que se abriera en Safari? Además, ¿qué necesito hacer referencia en el controlador de vista para que esto funcione?
Jed Grant
1
Para que la nueva página se abra en un safari móvil, consulte esta respuesta: stackoverflow.com/a/30604481/558789
Paul Bruneau
1
Esto funciona para enlaces, pero no parece detectar nuevas ventanas abiertas con JavaScript, es decir, window.open (url, "_blank")
Crashalot
FYI webView.loadRequest aparentemente ha sido renombrado webView.load en versiones recientes de la API
Bill Weinman
52

Para usar la última versión de Swift 4.2+

import WebKit

Amplíe su clase con WKUIDelegate

Establecer delegado para vista web

self.webView.uiDelegate = self

Implementar el método de protocolo

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
        webView.load(navigationAction.request)
    }
    return nil
}
muhasturk
fuente
No es un duplicado real. Hay diferencias en la opcionalidad de los parámetros que no se ajustan al protocolo Swift 4.1 WKUIDelegate en la respuesta a la que se vinculó.
yakattack
Hola, Cuando estoy cargando WKWebview con la URL de Pinterest, no pude abrir "Continuar con Facebook". Ahora puedo hacer clic y obtener la información sobre "Continuar con Google". Ayúdame por favor.
Nrv
Estoy enfrentando el mismo problema con wkwebview; iniciar sesión y redireccionar no funcionó en mi aplicación. Alguna ayuda ?
Jamshed Alam
1
por favor, cualquiera puede ayudarme, he asignado el delegado uiDelegate a wkwebview pero su método de creación de vista web con configuración no está siendo llamado
chetan panchal
25

Agréguese como WKNavigationDelegate

_webView.navigationDelegate = self;

e implementar el siguiente código en la devolución de llamada delegada 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);
}

PD: Este código es de mi pequeño proyecto STKWebKitViewController, que envuelve una interfaz de usuario utilizable alrededor de WKWebView.

stk
fuente
1
Hay un error tipográfico. Falta un punto entre WKNavigationActionPolicy y Allow
pollo
@stk ¿Cómo puedo saber si UIApplication va a abrir un enlace en la aplicación Safari? Si es un enlace de Appstore, necesito abrir ot en la aplicación Appstore, pero si es una página web habitual, necesito abrirlo en el mismo WKWebView stackoverflow.com/questions/29056854/…
BergP
1
@LeoKoppelkamm No es un error tipográfico sino que es Objective-C, no Swift. ;)
Ayan Sengupta
1
Esto funciona para enlaces, pero no parece detectar nuevas ventanas abiertas con JavaScript, es decir,window.open(url, "_blank")
Crashalot
@Crashalot ¿Encontraste la solución para window.open?
Sam
14

Si ya ha configurado WKWebView.navigationDelegate

WKWebView.navigationDelegate = self;

solo necesitas implementar:

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

de esta forma no es necesario implementar el método WKUIDelegate.

Boris Georgiev
fuente
1
Esto funciona para enlaces, pero no parece detectar nuevas ventanas abiertas con JavaScript, es decir, window.open (url, "_blank")
Crashalot
@Crashalot ¿Ha comprobado esta respuesta? stackoverflow.com/questions/33190234/wkwebview-and-window-open
Jose Rego
8

Ninguna de esas soluciones funcionó para mí, resolví el problema de la siguiente manera:

1) Implementación de WKUIDelegate

@interface ViewController () <WKNavigationDelegate, WKUIDelegate>

2) Configuración del delegado UIDelegate de wkWebview

self.wkWebview.UIDelegate = self;

3) Implementar el método createWebViewWithConfiguration

- (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
fuente
Solo obtengo un SIGABRT al hacer esto. No funciona.
user2009449
8

Cloud xuLa respuesta resuelve mi problema.

En caso de que alguien necesite la versión equivalente de Swift (4.x / 5.0), aquí está:

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
}

Por supuesto, webView.uiDelegateprimero debes configurarlo .

Benjamin Wen
fuente
7

Confirmo que el código Swift de Bill Weinman es correcto. Pero debo mencionar que también debe delegar UIDelegate para que funcione, en caso de que sea nuevo en el desarrollo de iOS como yo.

Algo como esto:

self.webView?.UIDelegate = self

Entonces, hay tres lugares en los que necesita realizar cambios.

Oliver Zhang
fuente
Escriba su código, es:self.webView.UIDelegate = self
Henrik Petterson
No estoy seguro si está implícito, pero para que esta línea de código funcione en iOS 10, ViewControllerdebe heredar WKUIDelegate. ieclass ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate
ltrainpr
4

También puede presionar otro controlador de vista o abrir una nueva pestaña, 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
fuente
¿Es necesario configurar vc.url? No lo estoy configurando y la vista web se está cargando correctamente. Además, en mi experiencia, solo veo que se createWebViewWithConfigurationllama cuando navigationAction.targetFramees nil. ¿Puede describir un escenario en el que esto no sería cierto?
Mason G. Zhwiti
@ MasonG.Zhwiti Estaba usando activamente WKWebViews para un proyecto el otoño pasado, pero desde entonces no he hecho nada con ellos, así que realmente no puedo responder a su pregunta. En el momento en que publiqué lo anterior, funcionó. Sin embargo, no puedo comprender cómo funciona sin configurar vc.url.
David H
Creo que funciona sin porque está devolviendo la vista web que creó, y luego (supongo) lo que sea que le haya pedido que la cree se encarga de usar la vista web para cargar la URL en cuestión.
Mason G. Zhwiti
3

Basado en la respuesta de Allen Huang

Detalles

  • Xcode versión 10.3 (10G8), Swift 5

Objetivos

  • detectar enlaces con target=“_blank”
  • push Ver controlador con webView si el controlador actual tiene navigationController
  • present Ver controlador con webView en todos los demás casos

Solución

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

Muestra completa

Info.plist

agregue su configuración de seguridad de transporte Info.plist

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

Guiones gráficos

Versión 1

ingrese la descripción de la imagen aquí

Versión 2

ingrese la descripción de la imagen aquí

Vasily Bodnarchuk
fuente
hola, después de iniciar sesión con facebook, ¿cómo abrir la ventana de compartir para compartir en facebook de manera similar? He iniciado sesión con la creación de un nuevo wkwebview, ahora el usuario ha iniciado sesión. Ahora, ¿cómo realizar un seguimiento de la ventana compartida?
Jamshed Alam
Gracias, su solución me salvó la vida :)
Samrat Pramanik
2

Esto funcionó para mí:

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

Como puede ver, lo que hacemos aquí es simplemente abrir una nueva webViewcon la nueva URL y controlar la posibilidad de que se cierre, solo si necesita una respuesta de eso second webviewpara que se muestre en la primera.

Aitor Pagán
fuente
1

Encontré algunos problemas que no se pueden resolver con solo usar webView.load(navigationAction.request). Así que uso crear un nuevo webView para hacer y funciona bien.

//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
fuente
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
fuente