Dlaczego WKWebView nie otwiera linków z target = „_ blank”?


122

WKWebViewnie otwiera żadnych linków, które mają target="_blank"atrybut „Otwórz w nowym oknie” w swoim <a href>tagu HTML .


zaktualizuj poprawną odpowiedź oznaczoną
— Efren

Odpowiedzi:


184

Moim rozwiązaniem jest anulowanie nawigacji i ponowne załadowanie żądania za pomocą loadRequest:. Będzie to podobne zachowanie jak UIWebView, który zawsze otwiera nowe okno w bieżącej ramce.

Zaimplementuj WKUIDelegatedelegata i ustaw go na _webview.uiDelegate. Następnie zastosuj:

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

  return nil;
}

4
To jest poprawna odpowiedź. Wypróbowaliśmy zaakceptowaną odpowiedź bez powodzenia. Ale odpowiedź @ Cloud działa, a ta odpowiedź na forach deweloperów wyjaśnia dlaczego.
— Christopher Pickslay

5
To rozwiązanie zadziałało dla mnie. Nie zapomnij ustawić UIDelegatewłaściwości, ponieważ te metody są zadeklarowane w WKUIDelegatenot WKNavigationDelegate.
— Taketo Sano

1
Ok, widzę, że ludzie chcą używać WKWebView w ten sposób. Zaimplementowałem możliwość otwarcia target = _blank-Links w tym samym WKWebView (jak pokazano powyżej) w moim projekcie github.com/sticksen/STKWebKitViewController .
— stk

5
@ChristopherPickslay podczas korzystania z tej metody z moim WKWebView adres URL żądania jest zawsze pusty, więc widok sieciowy kieruje do pustej białej strony. Masz jakieś pomysły?
— Jason Murray,

1
Kiedy żądanie „_blank” jest wysyłane z formularza zawierającego dane wiadomości, stwierdzam, że dane wiadomości zostaną utracone !!! Jakaś pomoc? @Cloud Wu
— Andrew

66

Odpowiedź od @Cloud Xu jest poprawna. Tylko w celach informacyjnych, tutaj jest w języku 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
}

2
jak byś to napisał, gdybyś chciał, żeby zamiast tego otwierał się w Safari? Do czego muszę się odwoływać w kontrolerze widoku, aby to zadziałało?
— Jed Grant

1
Aby otworzyć nową stronę w mobilnym safari, zobacz tę odpowiedź: stackoverflow.com/a/30604481/558789
— Paul Bruneau

1
Działa to w przypadku linków, ale wydaje się, że nie wykrywa nowych okien otwieranych za pomocą JavaScript, np. Window.open (url, "_blank")
— Crashalot

FYI webView.loadRequest najwyraźniej zmienił nazwę na webView.load w ostatnich wersjach API
— Bill Weinman

52

Aby korzystać z najnowszej wersji Swift 4.2+

import WebKit

Rozszerz swoją klasę dzięki WKUIDelegate

Ustaw delegata dla widoku internetowego

self.webView.uiDelegate = self

Implementuj metodę protokołu

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
        webView.load(navigationAction.request)
    }
    return nil
}

To nie jest prawdziwy duplikat. Istnieją różnice w opcjonalności parametrów, które nie są zgodne z protokołem Swift 4.1 WKUIDelegate w odpowiedzi, z którą utworzono łącze.
— yakattack

Cześć, Kiedy ładuję WKWebview z adresem URL Pinterest, nie mogę otworzyć „Kontynuuj z Facebookiem”. Teraz mogę kliknąć i uzyskać informacje o „Kontynuuj z Google”. Pomóż mi proszę.
— Nrv

Mam ten sam problem z wkwebview; logowanie i przekierowanie nie działały w mojej aplikacji. Jakaś pomoc ?
— Jamshed Alam

1
proszę, każdy może mi pomóc, przypisałem delegata uiDelegate do wkwebview, ale jego metoda tworzenia widoku internetowego z konfiguracją nie jest wywoływana
— chetan panchal

25

Dodaj siebie jako delegata WKNavigationDelegate

_webView.navigationDelegate = self;

i zaimplementuj następujący kod w wywołaniu zwrotnym delegata choosePolicyForNavigationAction: 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);
}

PS: Ten kod pochodzi z mojego małego projektu STKWebKitViewController, który otacza użyteczny interfejs użytkownika wokół WKWebView.


1
Jest literówka. Brakuje kropki między WKNavigationActionPolicy a Allow
— kurczak,

@stk Jak mogę się dowiedzieć, czy UIApplication otworzy łącze w aplikacji Safari? Jeśli jest to link do Appstore - muszę otworzyć w aplikacji Appstore, ale jeśli jest to zwykła strona internetowa - muszę ją otworzyć w tym samym stackoverflow
— WKWebView.com/questions/29056854/ ...

1
@LeoKoppelkamm To nie jest literówka, a raczej Objective-C, a nie Swift. ;)
— Ayan Sengupta

1
Działa to w przypadku linków, ale wydaje się, że nie wykrywa nowych okien otwieranych za pomocą JavaScript, tj.window.open(url, "_blank")
— Crashalot

@Crashalot Czy znalazłeś rozwiązanie dla window.open?
— Sam,

14

Jeśli ustawiłeś już WKWebView.navigationDelegate

WKWebView.navigationDelegate = self;

wystarczy zaimplementować:

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

w ten sposób nie musisz implementować metody WKUIDelegate.


1
Działa to w przypadku linków, ale wydaje się, że nie wykrywa nowych okien otwieranych za pomocą JavaScript, np. Window.open (url, "_blank")
— Crashalot

@Crashalot Czy sprawdziłeś tę odpowiedź? stackoverflow.com/questions/33190234/wkwebview-and-window-open
— Jose Rego

8

Żadne z tych rozwiązań nie zadziałało, problem rozwiązałem przez:

1) Wdrażanie WKUIDelegate

@interface ViewController () <WKNavigationDelegate, WKUIDelegate>

2) Ustawienie delegata UIDelegate wkWebview

self.wkWebview.UIDelegate = self;

3) Implementacja metody 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;  }

Robiąc to, po prostu dostaję SIGABRT. To nie działa.
— user2009449

8

Cloud xuodpowiedź rozwiązuje mój problem.

Jeśli ktoś potrzebuje równoważnej wersji Swift (4.x / 5.0), oto ona:

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
}

Oczywiście najpierw musisz ustawić webView.uiDelegate.


7

Potwierdzam, że kod Swift Billa Weinmana jest poprawny. Ale trzeba wspomnieć, że musisz również delegować UIDelegate, aby działał, na wypadek, gdybyś był nowy w programowaniu iOS, tak jak ja.

Coś takiego:

self.webView?.UIDelegate = self

Są więc trzy miejsca, w których musisz wprowadzić zmiany.


Wpisz swój kod, to:self.webView.UIDelegate = self
— Henrik Petterson

Nie jestem pewien, czy jest to domniemane, ale aby ten wiersz kodu działał w iOS 10, musisz ViewControllerdziedziczyć WKUIDelegate. ieclass ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate
— ltrainpr

4

Możesz także nacisnąć inny kontroler widoku lub otworzyć nową kartę itp:

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
}

Czy konieczne jest ustawienie vc.url? Nie ustawiam tego, a widok sieciowy ładuje się poprawnie. Z mojego doświadczenia wynika, że widzę createWebViewWithConfigurationwezwanie tylko wtedy, gdy navigationAction.targetFramejest nil. Czy możesz opisać scenariusz, w którym to nie byłoby prawdą?
— Mason G. Zhwiti

@ MasonG.Zhwiti Jesienią aktywnie korzystałem z WKWebViews w projekcie, ale od tamtej pory nic z nimi nie zrobiłem - więc naprawdę nie mogę odpowiedzieć na twoje pytanie. W momencie, gdy opublikowałem powyższe, zadziałało. Nie mogę jednak pojąć, jak to działa bez ustawienia vc.url.
— David H

Myślę, że działa bez, ponieważ zwracasz utworzony widok sieciowy, a następnie (zgaduję), cokolwiek poprosiło Cię o utworzenie, zajmuje się używaniem widoku internetowego do załadowania danego adresu URL.
— Mason G. Zhwiti

3

Na podstawie odpowiedzi Allena Huanga

Detale

  • Xcode w wersji 10.3 (10G8), Swift 5

Cele

  • wykrywać linki z target=“_blank”
  • push Wyświetl kontroler z webView, jeśli obecny kontroler ma navigationController
  • present Wyświetl kontroler z webView we wszystkich innych przypadkach

Rozwiązanie

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

Pełna próbka

Info.plist

dodaj swoje ustawienia bezpieczeństwa transportu 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)
    }
}

Scenorysy

Wersja 1

wprowadź opis obrazu tutaj

Wersja 2

wprowadź opis obrazu tutaj


cześć, po zalogowaniu się na Facebooku, jak w podobny sposób otworzyć okno udostępniania dla udostępniania na Facebooku? Zalogowałem się podczas tworzenia nowego wkwebview, teraz użytkownik jest zalogowany. Jak teraz śledzić okno udostępniania?
— Jamshed Alam

Dziękuję, Twoje rozwiązanie uratuje mi życie :)
— Samrat Pramanik

2

To zadziałało dla mnie:

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

Jak widać, po prostu otwieramy webViewnowy adres URL z nowym adresem i kontrolujemy możliwość zamknięcia, tylko jeśli potrzebujesz odpowiedzi z tego, second webviewaby była wyświetlana jako pierwsza.


1

Napotkałem pewne problemy, których nie można rozwiązać za pomocą samego narzędzia webView.load(navigationAction.request). Więc używam tworzenia nowego webView do zrobienia i po prostu działa dobrze.

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

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)) }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.