77

iOS 8 以降で実行しているときに、UIWebView の動的に割り当てられたインスタンスを WKWebView インスタンスに置き換える実験を行っていますが、WKWebView のコンテンツ サイズを決定する方法が見つかりません。

私の Web ビューはより大きな UIScrollView コンテナーに埋め込まれているため、Web ビューの理想的なサイズを決定する必要があります。これにより、フレームを変更して Web ビュー内でスクロールしなくてもすべての HTML コンテンツを表示できるようになり、スクロール ビュー コンテナーの正しい高さを設定できるようになります (scrollview.contentSize を設定することにより)。

sizeToFit と sizeThatFits を試しましたが成功しませんでした。WKWebView インスタンスを作成し、それをコンテナーのスクロールビューに追加する私のコードは次のとおりです。

// 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];

以下は、実験的な didFinishNavigation メソッドです。

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

そして、生成される出力は次のとおりです。

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}

sizeToFit 呼び出しは効果がなく、sizeThatFits は常に高さ 1 を返します。

4

18 に答える 18

19

私は最近、この問題に自分で対処しなければなりませんでした。結局、私はChris McClenaghan によって提案されたソリューションの修正を使用していました。

実際、彼の元の解決策は非常に優れており、ほとんどの単純なケースで機能します。ただし、テキストのあるページでのみ機能しました。おそらく、静的な高さを持つ画像を含むページでも機能します。max-heightただし、サイズがおよびmax-width属性で定義されている画像がある場合は、確実に機能しません。

これは、ページの読み込み後にこれらの要素のサイズが変更される可能性があるためです。したがって、実際には、返される高さonLoadは常に正しいものになります。しかし、それはその特定のインスタンスに対してのみ正しいでしょう。回避策は、body高さの変化を監視し、それに対応することです。

のサイズ変更を監視する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)
        }
        
    }
}

このソリューションは、私が思いついた中で最もエレガントです。ただし、注意すべき点が 2 つあります。

まず、URL をロードする前に に設定shouldListenToResizeNotificationする必要がありますfalse。この追加のロジックは、読み込まれた URL が急速に変化する可能性がある場合に必要です。この場合、何らかの理由で古いコンテンツからの通知と新しいコンテンツからの通知が重なる場合があります。このような動作を防ぐために、この変数を作成しました。新しいコンテンツのロードを開始すると、古いコンテンツからの通知を処理しなくなり、新しいコンテンツがロードされた後にのみサイズ変更通知の処理を再開することが保証されます。

ただし、最も重要なことは、次の点に注意する必要があることです。

WKWebViewこのソリューションを採用する場合は、通知によって報告されたサイズ以外のサイズに変更すると、通知が再度トリガーされることを考慮する必要があります。

無限ループに陥りやすいので注意。たとえば、高さを報告された高さと追加のパディングに等しくすることで通知を処理することにした場合:

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

ご覧のとおり、レポートされた高さに 8 を追加しているため、これが完了するとサイズbodyが変更され、通知が再度投稿されます。

そのような状況に注意してください。それ以外の場合は問題ありません。

また、このソリューションで問題を発見した場合はお知らせください。私自身、このソリューションに頼っています。

于 2017-01-06T17:51:38.640 に答える
7

以下を試してください。WKWebView インスタンスをインスタンス化するたびに、次のようなものを追加します。

    //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]];

次に、WKScriptMessageHandler プロトコルに従ってメッセージを処理するクラスに、次のようなメソッドを追加します。

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

これは私にとってはうまくいきます。

ドキュメントに複数のテキストがある場合は、すべてが確実に読み込まれるように、次のように JavaScript をラップする必要がある場合があります。

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

注: このソリューションは、ドキュメントの継続的な更新には対応していません。

于 2015-01-27T22:05:15.150 に答える
7

また、evaluateJavaScript で WKWebView のコンテンツの高さを取得することもできます。

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

                  }
              }];
}
于 2019-01-18T09:14:27.957 に答える
6

webview の読み込みが完了するまで待つ必要があります。これが私が使用した実際の例です

WKWebView コンテンツが読み込まれた関数が呼び出されない

次に、webview の読み込みが完了したら、次の方法で必要な高さを決定できます。

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

   println(webView.scrollView.contentSize.height)

}
于 2014-12-17T19:07:54.167 に答える
1

スクロール ビュー KVO を試してみました。ドキュメントで JavaScript を評価してみましclientHeightoffsetHeight

最終的に私のために働いたのは次のとおりdocument.body.scrollHeightです。またはscrollHeight、コンテナなどの最上位の要素を使用しますdiv

loadingKVO を使用して WKWebview プロパティの変更をリッスンします。

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

その後:

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

updateWebviewFrame実装:

[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;
}];
于 2016-12-25T13:49:01.247 に答える
1

また、さまざまな方法を実装しようとし、最終的に解決に至りました。その結果、intrinsicContentSizeをコンテンツのサイズに適応させるセルフサイズの WKWebView を作成しました。したがって、 Auto Layoutsで使用できます。例として、iOS アプリで数式を表示するのに役立つビューを作成しました: https://github.com/Mazorati/SVLatexView

于 2019-07-15T22:06:43.187 に答える
0

最良の方法は、webView.scrollView のcontentSizeプロパティを観察し、それに応じて webView の高さの制約を更新することです。

private var contentSizeObserver: NSKeyValueObservation?
    
contentSizeObserver = webView.scrollView.observe(\.contentSize, options: .new) { [weak self] _, change in
    guard let contentSize = change.newValue else { return }
    self?.csWebViewHeight?.update(offset: contentSize.height)
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    // Recalculate webView size
    csWebViewHeight?.update(offset: 0)
    webView.setNeedsLayout()
    webView.layoutIfNeeded()
}
于 2021-11-15T19:35:27.010 に答える