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 .

stk
źródło
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;
}
Cloud Xu
źródło
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
}
Bill Weinman
źródło
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
}
muhasturk
źródło
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.

stk
źródło
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.

Boris Georgiev
źródło
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;  }
Ugo Marinelli
źródło
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.

Benjamin Wen
źródło
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.

Oliver Zhang
źródło
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
}
David H.
źródło
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

Wasilij Bodnarchuk
źródło
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.

Aitor Pagán
źródło
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
}
Allen Huang
źródło
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
źródło