it-swarm.com.de

Wie bestimme ich die Inhaltsgröße einer WKWebView?

Ich experimentiere mit dem Ersetzen einer dynamisch zugewiesenen Instanz von UIWebView durch eine WKWebView-Instanz, wenn sie unter iOS 8 oder neuer ausgeführt wird, und ich kann keine Möglichkeit finden, die Inhaltsgröße einer WKWebView zu bestimmen.

Meine Webansicht ist in einen größeren UIScrollView-Container eingebettet. Daher muss ich die ideale Größe für die Webansicht ermitteln. Dadurch kann ich den Rahmen so ändern, dass der gesamte HTML-Inhalt angezeigt wird, ohne dass ein Bildlauf innerhalb der Webansicht erforderlich ist. Außerdem kann ich die richtige Höhe für den Bildlauf-Ansichtscontainer einstellen (durch das Setzen von scrollview.contentSize).

Ich habe sizeToFit und sizeThatFits ohne Erfolg ausprobiert. Hier ist mein Code, der eine WKWebView-Instanz erstellt und der Container-Bildlaufansicht hinzufügt:

// self.view is a UIScrollView sized to something like 320.0 x 400.0.
CGRect wvFrame = CGRectMake(0, 0, self.view.frame.size.width, 100.0);
self.mWebView = [[[WKWebView alloc] initWithFrame:wvFrame] autorelease];
self.mWebView.navigationDelegate = self;
self.mWebView.scrollView.bounces = NO;
self.mWebView.scrollView.scrollEnabled = NO;

NSString *s = ... // Load s from a Core Data field.
[self.mWebView loadHTMLString:s baseURL:nil];

[self.view addSubview:self.mWebView];

Hier ist eine experimentelle didFinishNavigation-Methode:

- (void)webView:(WKWebView *)aWebView
                             didFinishNavigation:(WKNavigation *)aNavigation
{
    CGRect wvFrame = aWebView.frame;
    NSLog(@"original wvFrame: %@\n", NSStringFromCGRect(wvFrame));
    [aWebView sizeToFit];
    NSLog(@"wvFrame after sizeToFit: %@\n", NSStringFromCGRect(wvFrame));
    wvFrame.size.height = 1.0;
    aWebView.frame = wvFrame;
    CGSize sz = [aWebView sizeThatFits:CGSizeZero];
    NSLog(@"sizeThatFits A: %@\n", NSStringFromCGSize(sz));
    sz = CGSizeMake(wvFrame.size.width, 0.0);
    sz = [aWebView sizeThatFits:sz];
    NSLog(@"sizeThatFits B: %@\n", NSStringFromCGSize(sz));
}

Und hier ist die Ausgabe, die generiert wird:

2014-12-16 17:29:38.055 App[...] original wvFrame: {{0, 0}, {320, 100}}
2014-12-16 17:29:38.055 App[...] wvFrame after sizeToFit: {{0, 0}, {320, 100}}
2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits A: {320, 1}
2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits B: {320, 1}

Der sizeToFit-Aufruf hat keine Auswirkungen und sizeThatFits gibt immer eine Höhe von 1 zurück.

35
Mark Smith

Ich denke, ich habe jede Antwort zu diesem Thema gelesen und alles, was ich hatte, war Teil der Lösung. Die meiste Zeit habe ich versucht, die von @davew beschriebene KVO-Methode zu implementieren, die gelegentlich funktionierte, die meiste Zeit jedoch eine Leerstelle unter dem Inhalt eines WKWebView-Containers hinterließ. Ich habe auch den @David Beck-Vorschlag implementiert und die Containerhöhe auf 0 gesetzt, wodurch die Möglichkeit vermieden wird, dass das Problem auftritt, wenn die Containerhöhe größer als die des Inhalts ist. Trotzdem hatte ich gelegentlich Leerzeichen. Für mich hatte der "contentSize" -Betrachter eine Menge Mängel. Ich habe nicht viel Erfahrung mit Webtechnologien, daher kann ich nicht beantworten, was das Problem mit dieser Lösung war, aber ich habe gesehen, dass ich nur Höhe in der Konsole drucke, aber nichts damit mache (z. B. die Einschränkungen der Größe anpassen). es springt zu einer Zahl (z. B. 5000) und geht dann zu der Zahl vor der höchsten (z. B. 2500 - die sich als die richtige erweist). Wenn ich die Höhenbeschränkung auf die Höhe setze, die ich von "contentSize" erhalte, wird der Wert auf die höchste Zahl gesetzt, die er erhält, und er wird nie auf die richtige Größe skaliert - was wiederum von @David Beck Kommentar erwähnt wird.

Nach vielen Versuchen habe ich eine Lösung gefunden, die für mich funktioniert:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
        if complete != nil {
            self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
                self.containerHeight.constant = height as! CGFloat
            })
        }

        })
}

Natürlich ist es wichtig, die Abhängigkeiten korrekt festzulegen, damit die Größe von scrollView gemäß der containerHeight-Einschränkung geändert wird. 

Wie sich herausstellte, wurde die didFinish-Navigationsmethode nie aufgerufen, wenn ich wollte, aber nachdem Sie document.readyState step eingestellt haben, wird die nächste (document.body.offsetHeight) im richtigen Moment aufgerufen und gibt mir die richtige Zahl für die Höhe zurück.

46
IvanMih

Sie könnten Key-Value Observing (KVO) verwenden ...

In Ihrem ViewController:

- (void)viewDidLoad {
    ...
    [self.webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
}


- (void)dealloc
{
    [self.webView.scrollView removeObserver:self forKeyPath:@"contentSize" context:nil];
}


- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (object == self.webView.scrollView && [keyPath isEqual:@"contentSize"]) {
        // we are here because the contentSize of the WebView's scrollview changed.

        UIScrollView *scrollView = self.webView.scrollView;
        NSLog(@"New contentSize: %f x %f", scrollView.contentSize.width, scrollView.contentSize.height);
    }
}

Dies würde die Verwendung von JavaScript sparen und Sie über alle Änderungen auf dem Laufenden halten.

20
davew

Ich musste mich in letzter Zeit selbst mit diesem Thema beschäftigen. Am Ende verwendete ich eine Modifikation der von Chris McClenaghan vorgeschlagenen Lösung .

Eigentlich ist seine ursprüngliche Lösung ziemlich gut und funktioniert in den meisten einfachen Fällen. Für mich funktionierte das allerdings nur auf Seiten mit Text. Es funktioniert wahrscheinlich auch auf Seiten mit Bildern, die eine statische Höhe haben. Es funktioniert jedoch definitiv nicht, wenn Sie Bilder haben, deren Größe mit den Attributen max-height und max-width definiert ist.

Dies liegt daran, dass diese Elemente geändert werden können, nachdem die Seite geladen wurde. Die in onLoad zurückgegebene Höhe ist also immer korrekt. Dies ist jedoch nur für diesen speziellen Fall richtig. Die Problemumgehung besteht darin, die Änderung der body -Höhe zu überwachen und darauf zu reagieren.

Überwachen der Größenänderung des document.body

var shouldListenToResizeNotification = false
lazy var webView:WKWebView = {
    //Javascript string
    let source = "window.onload=function () {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:true,height: document.body.scrollHeight});};"
    let source2 = "document.body.addEventListener( 'resize', incrementCounter); function incrementCounter() {window.webkit.messageHandlers.sizeNotification.postMessage({height: document.body.scrollHeight});};"

    //UserScript object
    let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

    let script2 = WKUserScript(source: source2, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

    //Content Controller object
    let controller = WKUserContentController()

    //Add script to controller
    controller.addUserScript(script)
    controller.addUserScript(script2)

    //Add message handler reference
    controller.add(self, name: "sizeNotification")

    //Create configuration
    let configuration = WKWebViewConfiguration()
    configuration.userContentController = controller

    return WKWebView(frame: CGRect.zero, configuration: configuration)
}()

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    guard let responseDict = message.body as? [String:Any],
    let height = responseDict["height"] as? Float else {return}
    if self.webViewHeightConstraint.constant != CGFloat(height) {
        if let _ = responseDict["justLoaded"] {
            print("just loaded")
            shouldListenToResizeNotification = true
            self.webViewHeightConstraint.constant = CGFloat(height)
        }
        else if shouldListenToResizeNotification {
            print("height is \(height)")
            self.webViewHeightConstraint.constant = CGFloat(height)
        }

    }
}

Diese Lösung ist bei weitem die eleganteste, die ich mir vorstellen konnte. Es gibt jedoch zwei Dinge, die Sie beachten sollten.

Bevor Sie Ihre URL laden, sollten Sie zunächst shouldListenToResizeNotification auf false setzen. Diese zusätzliche Logik wird in Fällen benötigt, in denen sich die geladene URL schnell ändern kann. In diesem Fall können sich Benachrichtigungen aus alten Inhalten aus bestimmten Gründen mit denen des neuen Inhalts überschneiden. Um ein solches Verhalten zu verhindern, habe ich diese Variable erstellt. Dadurch wird sichergestellt, dass nach dem Laden neuer Inhalte keine Benachrichtigungen mehr vom alten verarbeitet werden und die Verarbeitung von Größenänderungsbenachrichtigungen erst dann fortgesetzt wird, wenn neuer Inhalt geladen wird.

Am wichtigsten ist jedoch, dass Sie sich dessen bewusst sein müssen:

Wenn Sie diese Lösung verwenden, müssen Sie berücksichtigen, dass die Benachrichtigung erneut ausgelöst wird, wenn Sie die Größe Ihrer WKWebView auf eine andere als die von der Benachrichtigung gemeldete Größe ändern.

Seien Sie vorsichtig damit, da es leicht ist, eine Endlosschleife zu betreten. Wenn Sie sich beispielsweise für die Benachrichtigung entscheiden, indem Sie Ihre Höhe gleich der gemeldeten Höhe und einigen zusätzlichen Füllungen setzen:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let responseDict = message.body as? [String:Float],
        let height = responseDict["height"] else {return}
        self.webViewHeightConstraint.constant = CGFloat(height+8)
    }

Wie Sie sehen können, ändert sich die Größe meiner body, da ich die gemeldete Höhe um 8 addiere, und die Benachrichtigung wird erneut veröffentlicht.

Seien Sie auf solche Situationen aufmerksam, sonst sollten Sie in Ordnung sein.

Und bitte lassen Sie mich wissen, wenn Sie Probleme mit dieser Lösung entdecken - ich verlasse mich darauf selbst, also sollten Sie am besten wissen, ob es Fehler gibt, die ich nicht entdeckt habe!

9

Versuche Folgendes. Wo immer Sie Ihre WKWebView-Instanz instanziieren, fügen Sie Folgendes ähnlich hinzu:

    //Javascript string
    NSString * source = @"window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});";

    //UserScript object
    WKUserScript * script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];

    //Content Controller object
    WKUserContentController * controller = [[WKUserContentController alloc] init];

    //Add script to controller
    [controller addUserScript:script];

    //Add message handler reference
    [controller addScriptMessageHandler:self name:@"sizeNotification"];

    //Create configuration
    WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init];

    //Add controller to configuration
    configuration.userContentController = controller;

    //Use whatever you require for WKWebView frame
    CGRect frame = CGRectMake(...?);

    //Create your WKWebView instance with the configuration
    WKWebView * webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];

    //Assign delegate if necessary
    webView.navigationDelegate = self;

    //Load html
    [webView loadHTMLString:@"some html ..." baseURL:[[NSBundle mainBundle] bundleURL]];

Fügen Sie dann eine Methode ähnlich der folgenden hinzu, bei der die Klasse das Protokoll WKScriptMessageHandler zur Verarbeitung der Nachricht befolgt:

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    CGRect frame = message.webView.frame;
    frame.size.height = [[message.body valueForKey:@"height"] floatValue];
    message.webView.frame = frame;}

Das funktioniert für mich.

Wenn Sie mehr als nur Text in Ihrem Dokument haben, müssen Sie möglicherweise das Javascript so einwickeln, um sicherzustellen, dass alles geladen ist:

@"window.onload=function () { window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});};"

HINWEIS: Diese Lösung behebt keine laufenden Aktualisierungen des Dokuments.

5

Sie müssen warten, bis der Webview vollständig geladen ist. Hier ist ein Arbeitsbeispiel, das ich verwendet habe 

WKWebView Content-geladene Funktion wird nie aufgerufen

Nachdem der Webview-Vorgang abgeschlossen ist, können Sie die gewünschten Höhen bestimmen 

func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) {

   println(webView.scrollView.contentSize.height)

}
5
Brian Wells

Sie können die Inhaltshöhe von WKWebView auch durch evaluieren von JavaScript ermitteln.

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    [webView evaluateJavaScript:@"Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight)"
              completionHandler:^(id _Nullable result, NSError * _Nullable error) {
                  if (!error) {
                      CGFloat height = [result floatValue];
                      // do with the height

                  }
              }];
}
1
Ravi H Malviya

Dies ist eine kleine Bearbeitung der Antwort von @ IvanMih. Für diejenigen unter Ihnen, die am Ende Ihres WKWebview einen großen Leerraum haben, hat diese Lösung gut funktioniert:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in

    if complete != nil {
      let height = webView.scrollView.contentSize
      print("height of webView is: \(height)")
    }
  })
}

anstatt also die Höhe basierend auf scrollHeight zu berechnen, berechnen Sie die Höhe mit webView.scrollView.contentSize. Ich bin mir sicher, dass es Szenarien gibt, in denen dies nicht funktioniert, aber ich denke, dass dies bei statischen Inhalten ziemlich gut funktioniert und wenn Sie den gesamten Inhalt anzeigen, ohne dass der Benutzer einen Bildlauf durchführen muss.

1
Ronak Vora

mit @ Andriys Antwort und dieser Antwort konnte ich die Höhe von contentSize in WKWebView einstellen und deren Höhe ändern.

hier ist der vollständige Swift 4-Code: 

    var neededConstraints: [NSLayoutConstraint] = []

    @IBOutlet weak var webViewContainer: UIView!
    @IBOutlet weak var webViewHeight: NSLayoutConstraint! {
        didSet {
            if oldValue != nil, oldValue.constant != webViewHeight.constant {
                view.layoutIfNeeded()
            }
        }
    }


   lazy var webView: WKWebView = {
        var source = """
var observeDOM = (function(){
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
        eventListenerSupported = window.addEventListener;

    return function(obj, callback){
        if( MutationObserver ){
            // define a new observer
            var obs = new MutationObserver(function(mutations, observer){
                if( mutations[0].addedNodes.length || mutations[0].removedNodes.length )
                    callback();
            });
            // have the observer observe foo for changes in children
            obs.observe( obj, { childList:true, subtree:true });
        }
        else if( eventListenerSupported ){
            obj.addEventListener('DOMNodeInserted', callback, false);
            obj.addEventListener('DOMNodeRemoved', callback, false);
        }
    };
})();

// Observe a specific DOM element:
observeDOM( document.body ,function(){
    window.webkit.messageHandlers.sizeNotification.postMessage({'scrollHeight': document.body.scrollHeight,'offsetHeight':document.body.offsetHeight,'clientHeight':document.body.clientHeight});
});

"""

        let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        let controller = WKUserContentController()
        controller.addUserScript(script)
        controller.add(self, name: "sizeNotification")
        let configuration = WKWebViewConfiguration()
        configuration.userContentController = controller
        let this = WKWebView(frame: .zero, configuration: configuration)
        webViewContainer.addSubview(this)
        this.translatesAutoresizingMaskIntoConstraints = false
        this.scrollView.isScrollEnabled = false
        // constraint for webview when added to it's superview
        neededConstraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[web]|",
                                                            options: [],
                                                            metrics: nil,
                                                            views: ["web": this])
        neededConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[web]|",
                                                            options: [],
                                                            metrics: nil,
                                                            views: ["web": this])
        return this
    }()


    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        _  = webView // to create constraints needed for webView
        NSLayoutConstraint.activate(neededConstraints)
        let url = URL(string: "https://www.awwwards.com/")!
        let request = URLRequest(url: url)
        webView.load(request)
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let body = message.body as? Dictionary<String, CGFloat>,
            let scrollHeight = body["scrollHeight"],
            let offsetHeight = body["offsetHeight"],
            let clientHeight = body["clientHeight"] {
            webViewHeight.constant = scrollHeight
            print(scrollHeight, offsetHeight, clientHeight)
        }
    }
1
Mohammadalijf

Ich habe die Scroll-Ansicht KVO ausprobiert und habe versucht, Javascript im Dokument auszuwerten, indem Sie clientHeight, offsetHeight usw. verwenden.

Was für mich schließlich funktioniert hat: document.body.scrollHeight. Oder verwenden Sie die Variable scrollHeight Ihres obersten Elements, z. ein Container div.

Ich höre mir die loading WKWebview-Eigenschaftsänderungen mit KVO an:

[webview addObserver: self forKeyPath: NSStringFromSelector(@selector(loading)) options: NSKeyValueObservingOptionNew context: nil];

Und dann:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if(object == self.webview && [keyPath isEqualToString: NSStringFromSelector(@selector(loading))]) {
        NSNumber *newValue = change[NSKeyValueChangeNewKey];
        if(![newValue boolValue]) {
            [self updateWebviewFrame];
        }
    }
}

Die updateWebviewFrame-Implementierung:

[self.webview evaluateJavaScript: @"document.body.scrollHeight" completionHandler: ^(id response, NSError *error) {
     CGRect frame = self.webview.frame;
     frame.size.height = [response floatValue];
     self.webview.frame = frame;
}];
1
natanavra

Die meisten Antworten verwenden "document.body.offsetHeight".

Dies verbirgt das letzte Objekt des Körpers.

Ich habe dieses Problem überwunden, indem ich einen KVO-Beobachter verwendet habe, der auf Änderungen in WKWebview "contentSize" überwacht und dann diesen Code ausführt:

self.webView.evaluateJavaScript(
    "(function() {var i = 1, result = 0; while(true){result = 
    document.body.children[document.body.children.length - i].offsetTop + 
    document.body.children[document.body.children.length - i].offsetHeight;
    if (result > 0) return result; i++}})()",
    completionHandler: { (height, error) in
        let height = height as! CGFloat
        self.webViewHeightConstraint.constant = height
    }
)

Es ist nicht der schönste Code, aber es hat für mich funktioniert.

1
Stratubas

Funktioniert bei mir

extension TransactionDetailViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.webviewHeightConstraint.constant = webView.scrollView.contentSize.height
        }
    }
}
0
luhuiya

Ich habe die Javascript-Version in UITableViewCell ausprobiert und funktioniert einwandfrei. Wenn Sie es jedoch in die ScrollView aufnehmen möchten. Ich weiß nicht warum, die Höhe kann höher sein, aber nicht kürzer. Ich habe hier jedoch eine UIWebView-Lösung gefunden. https://stackoverflow.com/a/48887971/5514452

Es funktioniert auch in WKWebView. Ich denke, das Problem ist, dass das WebView Relayout benötigt, aber irgendwie schrumpft es nicht und kann sich nur vergrößern. Wir müssen die Höhe neu einstellen und die Größe wird definitiv geändert.

Bearbeiten: Ich setze die Rahmenhöhe zurück, nachdem Sie die Einschränkung eingestellt haben, weil sie manchmal nicht funktioniert, weil die Rahmenhöhe auf 0 gesetzt ist.

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.webView.frame.size.height = 0
    self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
        if complete != nil {
            self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
                let webViewHeight = height as! CGFloat
                self.webViewHeightConstraint.constant = webViewHeight
                self.webView.frame.size.height = webViewHeight
            })
        }
    })
}
0
Jeff Zhang

Versuchte auch verschiedene Methoden zu implementieren und kam schließlich zu einer Lösung. Als Ergebnis habe ich ein WKWebView mit eigener Größe erstellt, das seine intrinsicContentSize an die Größe seines Inhalts anpasst. Sie können es also in Auto-Layouts verwenden. Als Beispiel habe ich eine Ansicht erstellt, mit deren Hilfe Sie mathematische Formeln in iOS-Apps anzeigen können: https://github.com/Mazorati/SVLatexView

0
mazy

Sie müssen Verzögerung hinzufügen, es funktioniert für mich, anstatt mit den oben genannten Lösungen von JS:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
        print(size: webView.scrollView.contentSize)
    })
}