Mohamed Emad Hegab
Mohamed Emad Hegab

Reputation: 2675

iOS : detect UIWebView reaching the top or bottom

How can I detect UIWebView reaching the top or bottom? Because I need to trigger an action when I reach the bottom.

So is that doable?

Upvotes: 11

Views: 14060

Answers (6)

Muhammad Shauket
Muhammad Shauket

Reputation: 2778

If you are using Wkwebview:

I am bit late, I implemented delegate of wkwebview to check scrollview reaches on top or bottom.I used to read javascript code through evaluateJavaScript function. Before use set scrollview delegate of your webview and implement scrollViewDidEndDragging delegate function of UIScrollViewDelegate

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")
                                    } else if winpageoffset == 0 {
                                            print("top of webview")
                                        }
                                })

                            })

                        })

                    })

                })
            })


        })
    }

})

}
}

Upvotes: 0

Dulgan
Dulgan

Reputation: 6694

You could use KVO to detect any change in the contentOffset property of your UIWebView instance.

Warning : Be careful because this property is changing very often when scrolling, and the events in the example below will pop really frequently. Don't implement or call high cost function at each contentOffset change !

Here is a code example for doing it in a UIView subclass containing a UIWebView :

    @IBOutlet weak var webView: UIWebView!
    override func awakeFromNib() {
    super.awakeFromNib()
        webView.delegate = self
        webView.scrollView.addObserver(self, forKeyPath: "contentOffset", options: [.New, .Old], context: nil)
    }

    deinit {
        webView.scrollView.removeObserver(self, forKeyPath: "contentOffset", context: nil)
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        guard let kp = keyPath else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            return
        }
        if kp == "contentOffset" {
            self.scrollViewDidScroll(webView.scrollView)
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }

    func scrollViewDidScroll(scrollView: UIScrollView) {
        if scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.size.height < 100 {
            print("this is the end")
        } else if scrollView.contentOffset.y < 100 {
            print("this is the beginning")
        }
    }

As documented by Apple in UIWebview documentation, you should avoid placing your UIWebview instance in a UIScrollView to detect the end/beginning of scrolling.

Upvotes: 1

gfrigon
gfrigon

Reputation: 2267

First of all, you should not put your UIWebView inside a UIScrollView as mentionned in the UIWebView documentation.

Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result because touch events for the two objects can be mixed up and wrongly handled.

Instead, you can access the UIScrollView through the UIWebView properties. Your view controller can implement the UIScrollViewDelegate.

@interface MyViewController : UIViewController<UIScrollViewDelegate>
@end

Then you can hook to the UIScrollView:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Set self as scroll view protocol
    webView.scrollView.delegate = self;
}

Then finally detect the top and bottom being reached:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    if(scrollView.contentOffset.y 
       >= 
       (scrollView.contentSize.height - scrollView.frame.size.height)){
        NSLog(@"BOTTOM REACHED");
    }
    if(scrollView.contentOffset.y <= 0.0){
        NSLog(@"TOP REACHED");
    }
}

Voila, you can detect when top and bottom are reached.

Upvotes: 27

macbirdie
macbirdie

Reputation: 16193

You could put UIWebView inside a UIScrollView (that has bouncing disabled, so the bounce behavior of the views does not conflict in any way) and then when UIWebView scrolls to bottom, the UIScrollView above will take over scrolling and you can listen to its scrolling events.

Scratch that.

Seems that it's not that simple after all.

You can find the UIScrollView among UIWebView's subviews and tap into its scroll delegate methods.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    NSLog(@"We scrolled! Offset now is %f, %f", scrollView.contentOffset.x, scrollView.contentOffset.y);
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIWebView *view = (UIWebView *)self.view;

    for (UIView * subview in view.subviews) {
        if ([subview isKindOfClass:[UIScrollView class]]) {
            UIScrollView *webScrollView = (UIScrollView *)subview;
            webScrollView.delegate = self;
            break;
        }
    }

    [view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://stackoverflow.com"]]];
}

As the UIWebView loads the web content, its UIScrollView should change its contentSize property. You can use that to determine whether you're at the bottom or at the top already or not.

Upvotes: 1

Srikar Appalaraju
Srikar Appalaraju

Reputation: 73588

You can give this a shot -

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if(scrollView == yourWebView)
    {
        int percentScrolled = abs((int)ceil((scrollView.contentOffset.y/scrollView.contentSize.height)*100));
        if(percentScrolled > 100 || percentScrolled < 0)
            percentScrolled = 0;

        if(percentScrolled > 90)
        {
            //scroll percent is 90. do your thing here.
        }
    }
    return;
}

What is being done here is everytime user scrolls webView, calculate the % scrolled & when the % scrolled is coming to end (say anything > 90%) do whatever you planned to do.

Upvotes: 3

gregheo
gregheo

Reputation: 4270

If you're targeting iOS 5, UIWebView has a read-only scrollView property that allows you to access the underlying UIScrollView.

From there, you can hook into the scrollView's scrollViewDidScroll: delegate method to receive information on scroll events.

Upvotes: 2

Related Questions