Divyesh
Divyesh

Reputation: 2413

How to detect webview scrolled to bottom?

I want to know when the user has ended the scroll on a Webview displaying a Terms & Conditions to display an "Accept" button only when the user has read this.

<StackLayout Spacing="0" BackgroundColor="{StaticResource WhiteColor}">
    <CustomView:HeaderView VerticalOptions="Start" LeftImageSource="{Binding LeftImage}" RightImageSource="{Binding RightImage}" LeftCommand="{Binding LeftClickCommand}" RightCommand="{Binding RightClickCommand}" HeaderText="{Binding ScreenTitle}" PrevText="{Localize:ETranslate PrevText}" />
    <WebView Source="{Binding Html, Converter={StaticResource HtmlSourceConverter}}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</StackLayout>

public class HtmlSourceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var html = new HtmlWebViewSource();
        if (value != null)
        {
            html.Html = value.ToString();
        }
        return html;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I have tried to achieve this using Renderer but in iOS WkWebViewRenderer does not have Scrolled() method which is available in WebViewRenderer.

Is there any way to achieve this in Xamarin.Forms?

Upvotes: 0

Views: 1436

Answers (3)

Zevi Britz
Zevi Britz

Reputation: 33

After implementing this answer, https://stackoverflow.com/a/66931493/15125666 I was still having trouble with the android renderer, as it wasn't detecting when the user reached the bottom of the WebView.

To solve that, you'll need to add a buffer to the to the calculation, that will account for any potential floating-point errors when multiplying the content height by the scale.


Here is the updated code for the Android: ScrollWebViewRenderer

private void Control_ScrollChange(object sender, ScrollChangeEventArgs e)
    {
        var nativeWebView = e.V as global::Android.Webkit.WebView;
        int height = (int)(nativeWebView.ContentHeight * nativeWebView.Scale);            
        int webViewheight = nativeWebView.Height;
        int bottomThreshold = (int)Math.Floor(0.004 * webViewheight); // the number that I used 0.004 was what worked for me. you might need to adjust it for your use case.

        int cutOff = height - webViewheight - bottomThreshold;

        if (e.ScrollY >= cutOff)
        {
            view.IsBottomReached = true;
            System.Diagnostics.Debug.WriteLine("Bottom Reached");
        }
        else
        {
            view.IsBottomReached = false;
        }
    }

Here I added a dynamic bottomThreshold buffer, and used it for the cutOff calculation. It adds a small buffer to the calculation to ensure that even small discrepancies won't prevent the detection of reaching the bottom.

Upvotes: 1

Divyesh
Divyesh

Reputation: 2413

I have got the solution for Android as well. Here is entire solution, hope this might be helpful to someone looking for same!

MyPage.xaml

<webview:ScrollWebView x:Name="webView" Source="{Binding Html, Converter={StaticResource HtmlSourceConverter}}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" IsBottomReached="{Binding IsShowAgreeButton}"/>

ScrollWebView Control

public class ScrollWebView : WebView
{
    public static BindableProperty IsBottomReachedProperty =
        BindableProperty.Create(nameof(IsBottomReached), typeof(bool), typeof(ScrollWebView), default(bool), BindingMode.TwoWay, propertyChanged: null);

    public bool IsBottomReached
    {
        get
        {
            return (bool)GetValue(IsBottomReachedProperty);
        }
        set
        {
            SetValue(IsBottomReachedProperty, value);
        }
    }
}

Android: ScrollWebViewRenderer

[assembly: Xamarin.Forms.ExportRenderer(typeof(ScrollWebView), typeof(ScrollWebViewRenderer))]
namespace MyApp.Droid
{
    public class ScrollWebViewRenderer : WebViewRenderer
    {
        public static ScrollWebView view = null;

        public ScrollWebViewRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);
            view = (ScrollWebView)Element;
            if (Control != null)
            {
                Control.ScrollChange += Control_ScrollChange;
            }
        }

        private void Control_ScrollChange(object sender, ScrollChangeEventArgs e)
        {
            var nativeWebView = e.V as global::Android.Webkit.WebView;
            int height = (int)Math.Floor(nativeWebView.ContentHeight * nativeWebView.Scale);
            int webViewheight = nativeWebView.Height;

            int cutOff = height - webViewheight;

            if (e.ScrollY >= cutOff)
            {
                view.IsBottomReached = true;
                System.Diagnostics.Debug.WriteLine("Bottom Reached");
            }
            else
            {
                view.IsBottomReached = false;
            }
        }
    }
}

iOS: ScrollWebViewRenderer

[assembly: ExportRenderer(typeof(ScrollWebView), typeof(ScrollWebViewRenderer))]
namespace MyApp.iOS.RendererClasses
{
    public class ScrollWebViewRenderer : WkWebViewRenderer
    {
        public static ScrollWebView view = null;
        
        public ScrollWebViewRenderer() { }

        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);
            view = (ScrollWebView)Element;

            MyDelegate myDel = new MyDelegate();
            myDel.ProcessCompleted += MyDel_ProcessCompleted;

            this.ScrollView.Delegate = myDel;
        }

        private void MyDel_ProcessCompleted(object sender, bool isScrolledToBottom)
        {
            view.IsBottomReached = isScrolledToBottom;
        }
    }

    public class MyDelegate : UIScrollViewDelegate
    {
        public event EventHandler<bool> ProcessCompleted;

        public override void Scrolled(UIScrollView scrollView)
        {
            if (scrollView.ContentOffset.Y >= scrollView.ContentSize.Height - scrollView.Frame.Size.Height)
            {
                System.Diagnostics.Debug.WriteLine("Bottom Reached");
                ProcessCompleted?.Invoke(this, true);
            }
            else
            {
                ProcessCompleted?.Invoke(this, false);
            }
        }
    }
}

Upvotes: 1

ColeX
ColeX

Reputation: 14475

WKWebView has a perperty ScrollView inside it , so we could override the Delegate with it .

Code in WKWebViewRenderer

[assembly: ExportRenderer(typeof(WebView), typeof(MyRenderer))]
namespace FormsA.iOS
{
    public class MyDelegate : UIScrollViewDelegate
    {
        public override void Scrolled(UIScrollView scrollView)
        {
         
            if(scrollView.ContentOffset.Y >= scrollView.ContentSize.Height - scrollView.Frame.Size.Height)
            {
                 //here rearch bottom 
            }
            else if(scrollView.ContentOffset.Y < scrollView.ContentSize.Height)
            {
                
            }

            
        }
    }

    public class MyRenderer: WkWebViewRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            this.ScrollView.Delegate = new MyDelegate();
        }
    }
}

It works perfectly on my side , refer to https://stackoverflow.com/a/52872317/8187800 .

Upvotes: 1

Related Questions