Search code examples
javascriptiosswiftwebkitwkwebview

Using scrollIntoVIew in WKWebView jumps to top of page


With the code below, I'm trying to load HTML into a WKWebview, then scroll it to a particular element.

  let url = Bundle.main.url(forResource: "TestHTML", withExtension: "html")!
    myWebView.loadFileURL(url, allowingReadAccessTo: url)
    myWebView.evaluateJavaScript("document.getElementById('4').scrollIntoView(true);") {[weak self] (result, error) in
        print("result: \(result)")
        print (error)
    }

However, when I run it, the page loads, scrolls to the section, but then jumps back to the top of the page.

I have also tried setting the contentOffset of the scrollView, resulting in the exact same issue


Solution

  • I published this question a few months ago, and while this answer worked for a demo project, it wouldn't work for me in another project - it would go to the offset, then just go back to the top.

    The issue seems to be that while when the code in webView(didFinish) runs, the web view has indeed finished loading, but the scroll view has not finished loading.

    My solution is as follows:

    1. Make the ViewController conform to both WKWebViewNavigationDelegate and UIScrollViewDelegate protocols
    class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelegate {
    //..
    }
    
    1. Add two properties to the ViewController. One will be an CGPoint to store the content offset of the Scroll View, and the other will be a boolean to keep track of whether or not the Web View has finished loading, and will have a value of false.
    var position: CGPoint?
    var webViewFinished = false
    
    1. in the didFinish delegate method, set the value of position to the scroll view's content offset, and set webViewFinished to true
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            position = myWebView.scrollView.contentOffset
            webViewFinished = true
        }
    
    1. In the scrollViewDidScroll delegate method, check if the Web View has finished loading. If it has, set the Scroll View's content offset to the value of position, and set webViewFinished to false (since otherwise, this code will run each time the user scrolls). Don't forget to do optional chaining on position as well!
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
            if webViewFinished {
                webViewFinished = false
                if let p = position {
                    web.scrollView.setContentOffset(p, animated: false)
                }
            }
        }
    
    1. Last but not least, set the Web View/Scroll View's delegates
    override func viewDidLoad(){
        super.viewDidLoad()
        myWebView.navigationDelegate = self
        myWebView.scrollView.delegate = self
    }