Search code examples
xamarinxamarin.formswebviewxamarin.androidxamarin.ios

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?


Solution

  • 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);
                }
            }
        }
    }