Search This Blog

Saturday 26 June 2021

Hybrid WebView in Xamarin Forms.

Hybrid WebView


Github Url: https://github.com/mistrypragnesh40/HybridWebView

Xamarin Forms Project.

Create CustomWebView Renderer in your Xamarin form Project.

HybridWebView/Renderer/CustomWebView.cs

using System;
using Xamarin.Forms;
 
namespace HybridWebView.Renderer
{
    public class CustomWebView : View
    {
        Action<string> action;
 
        public static readonly BindableProperty SourceProperty = BindableProperty.Create(
            propertyName: "Source",
            returnType: typeof(string),
            declaringType: typeof(CustomWebView),
            defaultValue: default(string),
            propertyChanged: ItemsSourcePropertyChanged
            );
        private static void ItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (CustomWebView)bindable;
            if (control != null)
            {
                control.Source = newValue.ToString();
            }
        }
 
        public string Source
        {
            get { return (string)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }
 
        public void RegisterAction(Action<string> callback)
        {
            action = callback;
        }
 
        public void Cleanup()
        {
            action = null;
        }
 
        public void InvokeAction(string data)
        {
            if (action == null || data == null)
            {
                return;
            }
            action.Invoke(data);
        }
    }
}

Now Implement CustomWebView Renderer in your Xamarin iOS  and Android Project.

Xamarin iOS Implementation.

HybridWebView.iOS/Renderer/HybridWebViewRenderer.cs

using System;
using System.ComponentModel;
using System.IO;
using Foundation;
using HybridWebView.iOS.Renderer;
using HybridWebView.Renderer;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
 
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace HybridWebView.iOS.Renderer
{
    public class HybridWebViewRenderer : ViewRenderer<CustomWebView, WKWebView>, IWKScriptMessageHandler
    {
        const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
        WKUserContentController userController;
 
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
 
            if (e.PropertyName == "Source")
            {
                if (Element.Source != null)
                {
                    string contentDirectoryPath = Path.Combine(NSBundle.MainBundle.BundlePath);
                    Control.LoadHtmlString(Element.Source, new NSUrl(contentDirectoryPath, true));
                }
    
            }
        }
        protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
        {
            base.OnElementChanged(e);
 
            if (e.OldElement != null)
            {
                userController.RemoveAllUserScripts();
                userController.RemoveScriptMessageHandler("invokeAction");
                var hybridWebView = e.OldElement as CustomWebView;
                hybridWebView.Cleanup();
            }
            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    userController = new WKUserContentController();
                    var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
                    userController.AddUserScript(script);
                    userController.AddScriptMessageHandler(this, "invokeAction");
 
                    var config = new WKWebViewConfiguration { UserContentController = userController };
                    var webView = new WKWebView(Frame, config);
                    SetNativeControl(webView);
                }
                if (Element.Source != null)
                {
                    string contentDirectoryPath = Path.Combine(NSBundle.MainBundle.BundlePath);
                    Control.LoadHtmlString(Element.Source, new NSUrl(contentDirectoryPath, true));
                }
            }
        }
 
        public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
        {
            if (message.Body != null)
            {
                Element.InvokeAction(message.Body.ToString());
            }
        }
    }
}

Now Add HtmlWebView.JS File in your HybridWebView.iOS/Resources Folder.

HtmlWebView.JS

$(document).ready(function () {
    $('body').on('click', 'a', function (e) {
        e.preventDefault();
        invokeCSCode(e.target.href);
    })
});
function invokeCSCode(data) {
    try {
        invokeCSharpAction(data);
    } catch (err) {
    }
}
 

Xamarin Android Implementation.

HybridWebView.Android/Renderer/HybridWebViewRenderer.cs

using System;
using System.ComponentModel;
using Android.Content;
using Android.Service.Controls;
using Android.Webkit;
using HybridWebView.Droid.Renderer;
using HybridWebView.Renderer;
using Java.Interop;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
 
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace HybridWebView.Droid.Renderer
{
    public class HybridWebViewRenderer : ViewRenderer<CustomWebView, Android.Webkit.WebView>
    {
        const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
        Context _context;
        public string BaseUrl = "file:///android_asset/";
        public HybridWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
 
            if (e.PropertyName == "Source")
            {
                Control.LoadDataWithBaseURL(BaseUrl, Element.Source, "text/html", "UTF-8", null);
            }
        }
        protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
        {
            base.OnElementChanged(e);
 
            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                var hybridWebView = e.OldElement as CustomWebView;
                hybridWebView.Cleanup();
            }
            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    var webView = new Android.Webkit.WebView(_context);
                    webView.Settings.JavaScriptEnabled = true;
                    webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
                    SetNativeControl(webView);
                }
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
                Control.LoadDataWithBaseURL(BaseUrl, Element.Source, "text/html", "UTF-8", null);
            }
        }
    }
 
 
    /// <summary>
    /// JavascriptWebViewClient
    /// </summary>
    public class JavascriptWebViewClient : WebViewClient
    {
        string _javascript;
 
        public JavascriptWebViewClient(string javascript)
        {
            _javascript = javascript;
        }
 
        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
 
    /// <summary>
    /// JsBridge Class
    /// </summary>
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
 
        public JSBridge(HybridWebViewRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
        }
 
        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            HybridWebViewRenderer hybridRenderer;
 
            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                hybridRenderer.Element.InvokeAction(data);
            }
        }
    }
}
 

Now Add HtmlWebView.JS File in your HybridWebView.Android/Assets Folder.

HtmlWebView.JS

$(document).ready(function () {
    $('body').on('click', 'a', function (e) {
        e.preventDefault();
        invokeCSCode(e.target.href);
    })
});
function invokeCSCode(data) {
    try {
        invokeCSharpAction(data);
    } catch (err) {
    }
}
 

How to use CustomWebView in Xamarin Form Content Page.

Add CustomWebView in Your Content Page.

HybridWebView/MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:renderer="clr-namespace:HybridWebView.Renderer"
             x:Class="HybridWebView.MainPage">
 
    <StackLayout>
        <renderer:CustomWebView x:Name="hybridWebView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
    </StackLayout>
 
</ContentPage>
 


In CodeBehind Set Jquery & HtmlWebView.JS Reference and bind htmlContent to hybridwebView.Source.

hybridWebView.RegisterAction(data => ShowAction(data)); // showAction() method will invoke when Anchor tag is clicked.

HybridWebView/MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
 
namespace HybridWebView
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
 
 
            string jquery = @" <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script>";
            string webView = @"<script src=""HtmlWebView.js""></script>";
 
            string html = $"<html><head>{jquery} {webView}</head>" +
                           "Html Link1 : <a href='http://www.google.com'>Click here</a> </br>" +
                           "Html Link2: <a href='https://xamarincodingtutorial.blogspot.com'>Xamarin Coding Tutorial</a> </html>";
            hybridWebView.Source = html;
           hybridWebView.RegisterAction(data => ShowAction(data));
        }
 
        public void ShowAction(string data)
        {
            if (!string.IsNullOrWhiteSpace(data))
            {
                try
                {
                    Browser.OpenAsync(data, BrowserLaunchMode.SystemPreferred);
                }
                catch (Exception ex)
                {
 
                }
            }
        }
    }
}
 



No comments:

Post a Comment

Popular Posts