🐾

WKWebViewのコンテンツ量を取得するときにハマった話

2022/11/15に公開

初めに

WKWebViewをUISrollView内部に埋め込む必要があり、
表示するページの仕様上、WebViewの高さ取得に苦労しました。

表示するWebページが静的が高さが固定なのであれば、簡単です。
今回表示することになったWebページは、ロード後に高さが変わっていたため、無理やり実装しました。

方法

(前提: WKWebViewのscrollView.isScrollEnabledはfalseです。)

高さ固定の場合

    override func webView(
        _ webView: WKWebView,
        didFinish navigation: WKNavigation!
    ) {
        // これで取れます
        let height = webView.scrollView.contentSize.height
    }

ここで取得した高さをWebViewのAutolayoutの制約やframeに使ってあげれば、OKです。

ロード後に高さが変わる場合

方法1: フロント側からJS連携でタイミングを教えてもらう

このパターンが一番お行儀がいいと思います笑
しかし、フロントチームに依頼する必要があるため、納期の関係上難しいこともあります。

方法2: タイマーで監視する

私の場合は、アプリ側で完結するこのパターンで実装しました。
Webページの仕様としては、webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)が実行されて1,2秒後に高さが変わっていたので、「タイマーで1秒ごとに高さを確認し、高さが変わっていれば更新し、タイマーは5秒で破棄する」というロジックにしました。
(KVOでobserveすることも考えましたが、無限ループする可能性があり危険)

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {

    private var webView: WKWebView!

    private var timerToObserveWebViewHeight: Timer?
    private var timerToObserveWebViewHeightCount = 0
    private var timerToObserveWebViewHeightLimit = 5
    private var tmpWebViewHeight = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        webView = WKWebView(frame: .zero, configuration: config)
        webView.scrollView.isScrollEnabled = false
        webView.navigationDelegate = self
        webView.uiDelegate = self

        let url = URL(string: "https://hogehoge-")!
        let request = URLRequest(url: url)
        webView.load(request)
    }

    @objc private func timerToObserveWebViewHeightAction() {
        guard let timer = timerToObserveWebViewHeight else { return }

        if timerToObserveWebViewHeightLimit == timerToObserveWebViewHeightCount {
            timer.invalidate()
            timerToObserveMyPageHeight = nil
            timerToObserveWebViewHeightCount = 0
            tmpWebViewHeight = 0
        }

        let height = webView.scrollView.contentSize.height
    
        // 高さが更新されていれば、高さを更新する
        if height != CGFloat(tmpMyPageHeight) {
            webViewHeightConstraint?.constant = height
            view.layoutIfNeeded()
        }

        tmpWebViewHeight = Int(height)
        timerToObserveWebViewHeightCount += 1
    }

    override func webView(
        _ webView: WKWebView,
        didFinish navigation: WKNavigation!
    ) {
        // ロード後に、高さを監視するタイマーを生成し、スタート
        timerToObserveMyPageHeight = Timer.scheduledTimer(
                timeInterval: 1,
                target: self,
                selector: #selector(timerToObserveWebViewHeightAction),
                userInfo: nil,
                repeats: true
            )

        timerToObserveMyPageHeight?.fire()
    }
}

終わりに

タイマーで無理やり実装したので、他にいい方法があれば教えていただけると嬉しいです。
今の案件では、ロード後の数秒監視で対応できましたが、タイミングが分からない場合は、タイマーを動かし続けることになるので...

GitHubで編集を提案
カラビナテクノロジー デベロッパーブログ

Discussion