5
votes

I have a migration issue from UIViewView to WKWebView, detecting the Scroll View reached bottom when using WKWebView. Prior to WKWebView I used the UIScrollViewDelegate detecting wether the User had seen all of the content by scrolling till the end of the WebView. If he did, the "confirm" button was enabled. iPhone - knowing if a UIScrollView reached the top or bottom

Now with WKWebView this doesn't work anymore. I guess the reason is, when using a WKWebView and load a html string, it scales the view down for full visiblity of the content. So I had to set the viewport by appending it to the html string. This displays the content in the same way, the UIWebView did, providing the html string, without setting a viewport.

But now the UIScrollViewDelegate on load always tells that the bottom already reached. I guess, that the WKWebView loads the html, scales it at full visiblity, the scrollViewDelegate recognizes, that the content was fully visible, after that the viewport comes in and scales the page up, so a vertical scroll is needed to display the full content. But at this time, my "confirm" button is already enabled.

Code Snippet

override func scrollViewDidScroll(_ scrollView: UIScrollView){
    let scrollViewHeight = scrollView.frame.size.height;
    let scrollContentSizeHeight = scrollView.contentSize.height;
    let scrollOffset = scrollView.contentOffset.y;
    if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
    {
        self.confirmButton.isEnabled = true; 
    }
}

With WKWebView, the scrollContentSizeHeight always is the same as scrollViewHeight on load, but after the scrollViewDidScroll delegate function invokes mulitple times (without scrolling) the scrollContentSizeHeight is larger than the scrollViewHeight at real size.

4

4 Answers

9
votes

But now the UIScrollViewDelegate on load always tells that the bottom already reached.

The issue in the particular case is UIScrollView delegate is getting called before the WKWebView is loaded completely.

Take one private instance variable to check if the URL is loaded completely or not.

var isURLLoaded = false

Confirm WKWebView delegates to your viewController.

webView.scrollView.delegate = self
webView.navigationDelegate = self

And override these delegate methods:

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if isURLLoaded {
        if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) {
            confirmButton.isEnabled = true
        } else if scrollView.contentOffset.y < scrollView.contentSize.height {
            confirmButton.isEnabled = false
        }
    }
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

    isURLLoaded = true
}
4
votes

I wrote scrollview extension for Wkwebview using to read javascript code.through this you can use to check webview reaches to end of page or not. Before use, Set webview scroll delegate.

extension BaseWebViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
                                       willDecelerate decelerate: Bool) {
    webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
        if complete != nil {
            self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
                let bodyScrollHeight = height as! CGFloat
                var bodyoffsetheight: CGFloat = 0
                var htmloffsetheight: CGFloat = 0
                var htmlclientheight: CGFloat = 0
                var htmlscrollheight: CGFloat = 0
                var wininnerheight: CGFloat = 0
                var winpageoffset: CGFloat = 0
                var winheight: CGFloat = 0

                //body.offsetHeight
                self.webView.evaluateJavaScript("document.body.offsetHeight", completionHandler: { (offsetHeight, error) in
                    bodyoffsetheight = offsetHeight as! CGFloat

                    self.webView.evaluateJavaScript("document.documentElement.offsetHeight", completionHandler: { (offsetHeight, error) in
                        htmloffsetheight = offsetHeight as! CGFloat

                        self.webView.evaluateJavaScript("document.documentElement.clientHeight", completionHandler: { (clientHeight, error) in
                            htmlclientheight = clientHeight as! CGFloat

                            self.webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (scrollHeight, error) in
                                htmlscrollheight = scrollHeight as! CGFloat

                                self.webView.evaluateJavaScript("window.innerHeight", completionHandler: { (winHeight, error) in
                                    if error != nil {
                                        wininnerheight = -1
                                    } else {
                                        wininnerheight = winHeight as! CGFloat
                                    }

                                    self.webView.evaluateJavaScript("window.pageYOffset", completionHandler: { (winpageOffset, error) in
                                        winpageoffset = winpageOffset as! CGFloat

                                        let docHeight = max(bodyScrollHeight, bodyoffsetheight, htmlclientheight, htmlscrollheight,htmloffsetheight)

                                        winheight = wininnerheight >= 0 ? wininnerheight : htmloffsetheight
                                        let winBottom = winheight + winpageoffset
                                        if (winBottom >= docHeight) {
                                            print("end scrolling")
                                        }
                                    })

                                })

                            })

                        })

                    })
                })


            })
        }

    })

}
}

After scroll reaches to end you will see in console "end scrolling"

3
votes

Create a ScrollView inside the class, WKWebView has a scrollview embedded inside, make the scrollview you created relative with the embedded scrollview and implemente the method :

func scrollViewDidScroll(_ scrollView: UIScrollView){
    if (scrollView.contentOffset.y + 1) >= (scrollView.contentSize.height - scrollView.frame.size.height) {
        //bottom reached
        your code here

    }
}

and then, make your class inherit from UIScrollViewDelegate :

class : UIViewController,

0
votes
func updateButtonState(_ scrollView : UIScrollView){
    if isURLLoaded {
        if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) {
            confirmButton.isEnabled = true
        } else if scrollView.contentOffset.y < scrollView.contentSize.height {
            confirmButton.isEnabled = false
        }
    }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    self.updateButtonState(scrollView); 

}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

    isURLLoaded = true
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500), execute:{       
        self.updateButtonState(webView.scrollView)
    });

}

Unfortunatley I can't find a callback where the scrollView is ready to evaluate, so I set a timeout of 500ms, with this modification it works as expected