Xamarin Forms WebView Bindable Actions

The WebView has a number of functions and properties that are only available by directly accessing the control.  To maintain a clean code behind and call functions from the ViewModel, we need to create a new custom control that provides new bindable properties.

Extended Control

First, lets create a new custom control that inherits from a WebView. Some of the most common usages are, Refresh, GoBack and a function that returns the result of CanGoBack.

public namespace Mobile.Control
{
    public class WebViewer : WebView
    {
        public static BindableProperty RefreshCommandProperty =
        BindableProperty.Create(nameof(RefreshCommand), typeof(Action), typeof(WebViewer), null, BindingMode.OneWayToSource);

        public Action RefreshCommand
        {
            get { return (Action)GetValue(RefreshCommandProperty); }
            set { SetValue(RefreshCommandProperty, value); }
        }

        public static BindableProperty GoBackCommandProperty =
        BindableProperty.Create(nameof(GoBackCommand), typeof(Action), typeof(WebViewer), null, BindingMode.OneWayToSource);

        public Action GoBackCommand
        {
            get { return (Action)GetValue(GoBackCommandProperty); }
            set { SetValue(GoBackCommandProperty, value); }
        }

        public static BindableProperty CanGoBackFunctionProperty =
        BindableProperty.Create(nameof(CanGoBackFunction), typeof(Func<bool>), typeof(WebViewer), null, BindingMode.OneWayToSource);

        public Func<bool> CanGoBackFunction
        {
            get { return (Func<bool>)GetValue(CanGoBackFunctionProperty); }
            set { SetValue(CanGoBackFunctionProperty, value); }
        }
    }
}

Android Renderer

Create this custom renderer in your Android project.

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace Mobile.Droid
{    
    public class WebViewRender : WebViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if (Control != null && e.NewElement != null)
            {
                InitializeCommands((WebViewer)e.NewElement);
            }
        }

        private void InitializeCommands(WebViewer element)
        {
            element.RefreshCommand = () =>
            {
                Control?.Reload();
            };

            element.GoBackCommand = () =>
            {
                var ctrl = Control;
                if (ctrl == null)
                    return;

                if (ctrl.CanGoBack())
                    ctrl.GoBack();
            };

            element.CanGoBackFunction = () =>
            {
                var ctrl = Control;
                if (ctrl == null)
                    return false;

                return ctrl.CanGoBack();
            };
        }
    }    
}

iOS Renderer

Create this custom renderer in your iOS project.

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace Mobile.iOS
{
    public class WebViewRender : WebViewRenderer
    {

        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            if (NativeView != null && e.NewElement != null)
                InitializeCommands((WebViewer)e.NewElement);

        }

        private void InitializeCommands(WebViewer element)
        {
            element.RefreshCommand = () =>
            {
                ((UIWebView)NativeView).Reload();
            };

            element.GoBackCommand = () =>
            {
                var control = ((UIWebView)NativeView);
                if (control.CanGoBack)
                {
                    element.IsBackNavigating = true;
                    control.GoBack();
                }
            };

            element.CanGoBackFunction = () =>
            {
                return ((UIWebView)NativeView).CanGoBack;
            };
        }
    }
}

UWP

Create this custom renderer in your UWP project.

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace Mobile.UWP
{
    public class WebViewRender : WebViewRenderer
    {

        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);

            if (Control != null && e.NewElement != null)
                InitializeCommands((WebViewer)e.NewElement);
        }
        private void InitializeCommands(WebViewer element)
        {
             element.RefreshCommand = () =>
             {
                 Control.Refresh();
             };

             element.GoBack = () =>
             {
                 if (Control.CanGoBack)
                     Control.GoBack();
             };

             element.CanGoBackFunction = () =>
             {
                 return Control.CanGoBack;
             };
        }
    }
}

Calling From ViewModel

First you need to create a number of properties in your ViewModel to bind to the new Commands.

private Action _refresh;
public Action Refresh
{
    get { return _refresh; }
    set { Set(value); }
}

private Action _goBack;
public Action GoBack
{
    get { return _goBack; }
    set { _goBack = value; }
}

private Func<bool> _canGoBack;
public Func<bool> CanGoBack
{
    get { return _canGoBack; }
    set { _canGoBack = value; }
}

In your view, add the following attribute, to ensure you can reference your new control.

xmlns:control="clr-namespace:Mobile.Control"

Now add in your control and bind each function to the property in your ViewModel. We only want a OneWayToSource binding here as the control manages the function and we don’t want out ViewModel to overwrite it.

<control:WebViewer Source="{Binding WebViewSource}"
                   CanGoBackFunction="{Binding CanGoBack, Mode=OneWayToSource}"
                   GoBackCommand="{Binding GoBackCommand, Mode=OneWayToSource}"
                   RefreshCommand="{Binding RefreshCommand, Mode=OneWayToSource}"
                   HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />

To call any of these, you can now execute any Action or call any Function from the ViewModel.

// From anywhere in your ViewModel

GoBack();
var result = CanGoBack();
Refresh();
Microsoft MVP (Xamarin) | Exrin | Xamarin Forms Developer | Melbourne, Australia | Open to sponsorship to Canada or US

Related Posts

Leave A Comment?