Personalización de WebViewCustomizing a WebView

Descargar ejemplo Descargar el ejemploDownload Sample Download the sample

Un objeto WebView de Xamarin.Forms es una vista que muestra contenido web y HTML en la aplicación. En este artículo se explica cómo crear un representador personalizado que extienda WebView para permitir la invocación de código de C# desde JavaScript.A Xamarin.Forms WebView is a view that displays web and HTML content in your app. This article explains how to create a custom renderer that extends the WebView to allow C# code to be invoked from JavaScript.

Todas las vistas de Xamarin.Forms tienen un representador que las acompaña para cada plataforma y que crea una instancia de un control nativo.Every Xamarin.Forms view has an accompanying renderer for each platform that creates an instance of a native control. Cuando una aplicación de Xamarin.Forms representa un WebView en iOS, se crea una instancia de la clase WkWebViewRenderer, que a su vez crea una instancia del control WkWebView nativo.When a WebView is rendered by a Xamarin.Forms application on iOS, the WkWebViewRenderer class is instantiated, which in turn instantiates a native WkWebView control. En la plataforma de Android, la clase WebViewRenderer crea una instancia de un control WebView nativo.On the Android platform, the WebViewRenderer class instantiates a native WebView control. En Plataforma universal de Windows (UWP), la clase WebViewRenderer crea una instancia de un control WebView nativo.On the Universal Windows Platform (UWP), the WebViewRenderer class instantiates a native WebView control. Para obtener más información sobre el representador y las clases de control nativo a las que se asignan los controles de Xamarin.Forms, vea Clases base y controles nativos del representador.For more information about the renderer and native control classes that Xamarin.Forms controls map to, see Renderer Base Classes and Native Controls.

El siguiente diagrama muestra la relación entre la clase View y los controles nativos correspondientes que la implementan:The following diagram illustrates the relationship between the View and the corresponding native controls that implement it:

Relación entre la clase WebView y sus clases nativas de implementación

El proceso de representación se puede usar para implementar personalizaciones de plataforma mediante la creación de un representador personalizado para un objeto WebView en cada plataforma.The rendering process can be used to implement platform customizations by creating a custom renderer for a WebView on each platform. Para hacerlo, siga este procedimiento:The process for doing this is as follows:

  1. Cree el control HybridWebView personalizado.Create the HybridWebView custom control.
  2. Consuma el elemento HybridWebView de Xamarin.Forms.Consume the HybridWebViewfrom Xamarin.Forms.
  3. Cree el representador personalizado para el elemento HybridWebView en cada plataforma.Create the custom renderer for the HybridWebView on each platform.

Ahora se describirá cada elemento para implementar un representador de HybridWebView que mejore los objetos WebView de Xamarin.Forms para permitir la invocación de código de C# desde JavaScript.Each item will now be discussed in turn to implement a HybridWebView renderer that enhances the Xamarin.Forms WebView to allow C# code to be invoked from JavaScript. Se usa la instancia de HybridWebView para mostrar una página HTML que pide al usuario que escriba su nombre.The HybridWebView instance will be used to display an HTML page that asks the user to enter their name. Luego, cuando el usuario hace clic en un botón HTML, una función de JavaScript invoca a un elemento Action de C# que muestra una ventana emergente que contiene el nombre de los usuarios.Then, when the user clicks an HTML button, a JavaScript function will invoke a C# Action that displays a pop-up containing the users name.

Para más información sobre el proceso de invocación de C# desde JavaScript, vea Invocación de C# desde JavaScript.For more information about the process for invoking C# from JavaScript, see Invoke C# from JavaScript. Para más información sobre la página HTML, vea Creación de la página web.For more information about the HTML page, see Create the Web Page.

Nota

Un objeto WebView puede invocar una función de JavaScript desde C# y devolver cualquier resultado al código de C# que realiza la llamada.A WebView can invoke a JavaScript function from C#, and return any result to the calling C# code. Para más información, vea Invocación de JavaScript.For more information, see Invoking JavaScript.

Creación de HybridWebViewCreate the HybridWebView

El control personalizado HybridWebView se puede crear mediante la creación de subclases de la clase WebView:The HybridWebView custom control can be created by subclassing the WebView class:

public class HybridWebView : WebView
{
    Action<string> action;

    public static readonly BindableProperty UriProperty = BindableProperty.Create(
        propertyName: "Uri",
        returnType: typeof(string),
        declaringType: typeof(HybridWebView),
        defaultValue: default(string));

    public string Uri
    {
        get { return (string)GetValue(UriProperty); }
        set { SetValue(UriProperty, 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);
    }
}

El control HybridWebView personalizado se crea en el proyecto de biblioteca de .NET Standard y define la siguiente API para el control:The HybridWebView custom control is created in the .NET Standard library project and defines the following API for the control:

  • Una propiedad Uri que especifica la dirección de la página web que se va a cargar.A Uri property that specifies the address of the web page to be loaded.
  • Un método RegisterAction que registra un elemento Action con el control.A RegisterAction method that registers an Action with the control. Se invoca a la acción registrada desde el código de JavaScript incluido en el archivo HTML al que hace referencia la propiedad Uri.The registered action will be invoked from JavaScript contained in the HTML file referenced through the Uri property.
  • Un método CleanUp que quita la referencia al elemento registrado Action.A CleanUp method that removes the reference to the registered Action.
  • Un método InvokeAction que invoca al elemento registrado Action.An InvokeAction method that invokes the registered Action. Este método se llamará desde un representador personalizado en cada proyecto de plataforma.This method will be called from a custom renderer in each platform project.

Uso de HybridWebViewConsume the HybridWebView

En XAML, se puede hacer referencia al control personalizado HybridWebView en el proyecto de biblioteca de .NET Standard al declarar un espacio de nombres para su ubicación y usar el prefijo del espacio de nombres en el control personalizado.The HybridWebView custom control can be referenced in XAML in the .NET Standard library project by declaring a namespace for its location and using the namespace prefix on the custom control. El siguiente ejemplo de código muestra cómo se puede usar el control personalizado HybridWebView en una página XAML:The following code example shows how the HybridWebView custom control can be consumed by a XAML page:

<ContentPage ...
             xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
             x:Class="CustomRenderer.HybridWebViewPage"
             Padding="0,40,0,0">
    <local:HybridWebView x:Name="hybridWebView"
                         Uri="index.html" />
</ContentPage>

El prefijo de espacio de nombres local puede tener cualquier nombre.The local namespace prefix can be named anything. Pero los valores clr-namespace y assembly tienen que coincidir con los detalles del control personalizado.However, the clr-namespace and assembly values must match the details of the custom control. Una vez que se declara el espacio de nombres, el prefijo se usa para hacer referencia al control personalizado.Once the namespace is declared, the prefix is used to reference the custom control.

El siguiente ejemplo de código muestra cómo se puede usar el control personalizado HybridWebView en una página C#:The following code example shows how the HybridWebView custom control can be consumed by a C# page:

public HybridWebViewPageCS()
{
    var hybridWebView = new HybridWebView
    {
        Uri = "index.html"
    };
    // ...
    Padding = new Thickness(0, 40, 0, 0);
    Content = hybridWebView;
}

La instancia de HybridWebView se usa para mostrar un control web nativo en cada plataforma.The HybridWebView instance will be used to display a native web control on each platform. Su propiedad Uri se establece en un archivo HTML que se almacena en cada proyecto de plataforma y que se mostrará mediante el control web nativo.It's Uri property is set to an HTML file that is stored in each platform project, and which will be displayed by the native web control. El HTML representado pide al usuario que escriba su nombre, con una función de JavaScript que invoca a un elemento Action de C# en respuesta a un clic de botón HTML.The rendered HTML asks the user to enter their name, with a JavaScript function invoking a C# Action in response to an HTML button click.

HybridWebViewPage registra la acción que se va a invocar desde JavaScript, como se muestra en el ejemplo de código siguiente:The HybridWebViewPage registers the action to be invoked from JavaScript, as shown in the following code example:

public partial class HybridWebViewPage : ContentPage
{
    public HybridWebViewPage()
    {
        // ...
        hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
    }
}

Esta acción llama al método DisplayAlert para mostrar un elemento emergente modal que presenta el nombre especificado en la página HTML que muestra la instancia de HybridWebView.This action calls the DisplayAlert method to display a modal pop-up that presents the name entered in the HTML page displayed by the HybridWebView instance.

Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para mejorar los controles web de la plataforma si se permite la invocación de código de C# desde JavaScript.A custom renderer can now be added to each application project to enhance the platform web controls by allowing C# code to be invoked from JavaScript.

Creación del representador personalizado en cada plataformaCreate the custom renderer on each platform

El proceso para crear la clase del representador personalizado es el siguiente:The process for creating the custom renderer class is as follows:

  1. Cree una subclase de la clase WkWebViewRenderer en iOS y de la clase WebViewRenderer en Android y UWP, que representa el control personalizado.Create a subclass of the WkWebViewRenderer class on iOS, and the WebViewRenderer class on Android and UWP, that renders the custom control.
  2. Invalide el método OnElementChanged que representa el objeto WebView y escriba lógica para personalizarlo.Override the OnElementChanged method that renders the WebView and write logic to customize it. Este método se llama cuando se crea un objeto HybridWebView.This method is called when a HybridWebView object is created.
  3. Agregue un atributo ExportRenderer a la clase del representador personalizado o AssemblyInfo.cs, para especificar que se va a usar para representar el control personalizado de Xamarin.Forms.Add an ExportRenderer attribute to the custom renderer class or AssemblyInfo.cs, to specify that it will be used to render the Xamarin.Forms custom control. Este atributo se usa para registrar al representador personalizado con Xamarin.Forms.This attribute is used to register the custom renderer with Xamarin.Forms.

Nota

Para la mayoría de los elementos de Xamarin.Forms, proporcionar un representador personalizado en cada proyecto de la plataforma es un paso opcional.For most Xamarin.Forms elements, it is optional to provide a custom renderer in each platform project. Si no se registra un representador personalizado, se usará el representador predeterminado de la clase base del control.If a custom renderer isn't registered, then the default renderer for the control's base class will be used. Pero los representadores personalizados son necesarios en cada proyecto de plataforma al representar un elemento View.However, custom renderers are required in each platform project when rendering a View element.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las relaciones entre ellos:The following diagram illustrates the responsibilities of each project in the sample application, along with the relationships between them:

Responsabilidades de proyecto del representador personalizado de HybridWebView

El control personalizado HybridWebView se representa mediante clases de representador de la plataforma, que se derivan de la clase WkWebViewRenderer en iOS y de la clase WebViewRenderer en Android y UWP.The HybridWebView custom control is rendered by platform renderer classes, which derive from the WkWebViewRenderer class on iOS, and from the WebViewRenderer class on Android and UWP. Esto da lugar a que cada control personalizado HybridWebView se represente con controles web, como se muestra en las capturas de pantalla siguientes:This results in each HybridWebView custom control being rendered with native web controls, as shown in the following screenshots:

HybridWebView en cada plataforma

Las clases WkWebViewRenderer y WebViewRenderer exponen el método OnElementChanged, al que se llama cuando se crea el control personalizado de Xamarin.Forms para representar el control web nativo correspondiente.The WkWebViewRenderer and WebViewRenderer classes expose the OnElementChanged method, which is called when the Xamarin.Forms custom control is created to render the corresponding native web control. Este método toma un parámetro VisualElementChangedEventArgs que contiene propiedades OldElement y NewElement.This method takes a VisualElementChangedEventArgs parameter that contains OldElement and NewElement properties. Estas propiedades representan al elemento de Xamarin.Forms al que estaba asociado el representador y al elemento de Xamarin.Forms al que está asociado el representador, respectivamente.These properties represent the Xamarin.Forms element that the renderer was attached to, and the Xamarin.Forms element that the renderer is attached to, respectively. En la aplicación de ejemplo, la propiedad OldElement es null y la propiedad NewElement contiene una referencia a la instancia de HybridWebView.In the sample application the OldElement property will be null and the NewElement property will contain a reference to the HybridWebView instance.

El lugar para realizar la personalización del control nativo es una versión reemplazada del método OnElementChanged, en cada clase de representador de la plataforma.An overridden version of the OnElementChanged method, in each platform renderer class, is the place to perform the native web control customization. Mediante la propiedad Xamarin.Forms se puede obtener una referencia al control de Element que se representa.A reference to the Xamarin.Forms control that's being rendered can be obtained through the Element property.

Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el representador con Xamarin.Forms.Each custom renderer class is decorated with an ExportRenderer attribute that registers the renderer with Xamarin.Forms. El atributo toma dos parámetros: el nombre de tipo del control personalizado de Xamarin.Forms que se representa y el nombre de tipo del representador personalizado.The attribute takes two parameters – the type name of the Xamarin.Forms custom control being rendered, and the type name of the custom renderer. El prefijo assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.The assembly prefix to the attribute specifies that the attribute applies to the entire assembly.

En las secciones siguientes se describe la estructura de la página web cargada por cada control web nativo, el proceso para invocar C# desde JavaScript y su implementación en cada clase de representador personalizado de la plataforma.The following sections discuss the structure of the web page loaded by each native web control, the process for invoking C# from JavaScript, and the implementation of this in each platform custom renderer class.

Creación de la página webCreate the web page

El ejemplo de código siguiente muestra la página web que va a mostrar el control personalizado HybridWebView:The following code example shows the web page that will be displayed by the HybridWebView custom control:

<html>
<body>
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    <h1>HybridWebView Test</h1>
    <br />
    Enter name: <input type="text" id="name">
    <br />
    <br />
    <button type="button" onclick="javascript: invokeCSCode($('#name').val());">Invoke C# Code</button>
    <br />
    <p id="result">Result:</p>
    <script type="text/javascript">function log(str) {
            $('#result').text($('#result').text() + " " + str);
        }

        function invokeCSCode(data) {
            try {
                log("Sending Data:" + data);
                invokeCSharpAction(data);
            }
            catch (err) {
                log(err);
            }
        }</script>
</body>
</html>

La página web permite que un usuario escriba su nombre en un elemento input y proporciona un elemento button que va a invocar a código de C# cuando se haga clic sobre él.The web page allows a user to enter their name in an input element, and provides a button element that will invoke C# code when clicked. El proceso para lograrlo es el siguiente:The process for achieving this is as follows:

  • Cuando el usuario hace clic en el elemento button, se llama a la función de JavaScript invokeCSCode y el valor del elemento input se pasa a la función.When the user clicks on the button element, the invokeCSCode JavaScript function is called, with the value of the input element being passed to the function.
  • La función invokeCSCode llama a la función log para mostrar los datos que está enviando al elemento Action de C#.The invokeCSCode function calls the log function to display the data it is sending to the C# Action. Luego llama al método invokeCSharpAction para invocar al elemento Action de C#, pasando el parámetro recibido desde el elemento input.It then calls the invokeCSharpAction method to invoke the C# Action, passing the parameter received from the input element.

La función de JavaScript invokeCSharpAction no está definida en la página web, así que es cada representador personalizado el que la inserta en ella.The invokeCSharpAction JavaScript function is not defined in the web page, and will be injected into it by each custom renderer.

En iOS, este archivo HTML se encuentra en la carpeta de contenido del proyecto de la plataforma e incluye una acción de compilación de BundleResource.On iOS, this HTML file resides in the Content folder of the platform project, with a build action of BundleResource. En Android, este archivo HTML se encuentra en la carpeta de contenido o recursos del proyecto de la plataforma e incluye una acción de compilación de AndroidAsset.On Android, this HTML file resides in the Assets/Content folder of the platform project, with a build action of AndroidAsset.

Invocación de C# desde JavaScriptInvoke C# from JavaScript

El proceso para invocar a C# desde JavaScript es idéntico en cada plataforma:The process for invoking C# from JavaScript is identical on each platform:

  • El representador personalizado crea un control web nativo y carga el archivo HTML especificado por la propiedad HybridWebView.Uri.The custom renderer creates a native web control and loads the HTML file specified by the HybridWebView.Uri property.
  • Una vez que se ha cargado la página web, el representador personalizado inserta la función de JavaScript invokeCSharpAction en la página web.Once the web page is loaded, the custom renderer injects the invokeCSharpAction JavaScript function into the web page.
  • Cuando el usuario escribe su nombre y hace clic en el elemento HTML button, se invoca a la función invokeCSCode, que a su vez invoca a la función invokeCSharpAction.When the user enters their name and clicks on the HTML button element, the invokeCSCode function is invoked, which in turn invokes the invokeCSharpAction function.
  • La función invokeCSharpAction invoca a un método del representador personalizado, que a su vez invoca al método HybridWebView.InvokeAction.The invokeCSharpAction function invokes a method in the custom renderer, which in turn invokes the HybridWebView.InvokeAction method.
  • El método HybridWebView.InvokeAction invoca al elemento registrado Action.The HybridWebView.InvokeAction method invokes the registered Action.

En las secciones siguientes se habla de cómo se implementa este proceso en cada plataforma.The following sections will discuss how this process is implemented on each platform.

Creación del representador personalizado en iOSCreate the custom renderer on iOS

El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:The following code example shows the custom renderer for the iOS platform:

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.iOS
{
    public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
    {
        const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
        WKUserContentController userController;

        public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
        {
        }

        public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)
        {
            userController = config.UserContentController;
            var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
            userController.AddUserScript(script);
            userController.AddScriptMessageHandler(this, "invokeAction");
        }

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

            if (e.OldElement != null)
            {
                userController.RemoveAllUserScripts();
                userController.RemoveScriptMessageHandler("invokeAction");
                HybridWebView hybridWebView = e.OldElement as HybridWebView;
                hybridWebView.Cleanup();
            }

            if (e.NewElement != null)
            {
                string filename = Path.Combine(NSBundle.MainBundle.BundlePath, $"Content/{((HybridWebView)Element).Uri}");
                LoadRequest(new NSUrlRequest(new NSUrl(filename, false)));
            }
        }

        public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
        {
            ((HybridWebView)Element).InvokeAction(message.Body.ToString());
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((HybridWebView)Element).Cleanup();
            }
            base.Dispose(disposing);
        }        
    }
}

La clase HybridWebViewRenderer carga la página web especificada en la propiedad HybridWebView.Uri en un control nativo WKWebView y la función de JavaScript invokeCSharpAction se inserta en la página web.The HybridWebViewRenderer class loads the web page specified in the HybridWebView.Uri property into a native WKWebView control, and the invokeCSharpAction JavaScript function is injected into the web page. Una vez que el usuario escribe su nombre y hace clic en el elemento HTML button, se ejecuta la función de JavaScript invokeCSharpAction y se llama al método DidReceiveScriptMessage después de que se reciba un mensaje de la página web.Once the user enters their name and clicks the HTML button element, the invokeCSharpAction JavaScript function is executed, with the DidReceiveScriptMessage method being called after a message is received from the web page. A su vez, este método invoca al método HybridWebView.InvokeAction, que invoca a la acción registrada para mostrar la ventana emergente.In turn, this method invokes the HybridWebView.InvokeAction method, which will invoke the registered action to display the pop-up.

Esta funcionalidad se logra del siguiente modo:This functionality is achieved as follows:

  • El constructor del representador crea un objeto WkWebViewConfiguration y recupera su objeto WKUserContentController.The renderer constructor creates a WkWebViewConfiguration object, and retrieves its WKUserContentController object. El objeto WkUserContentController permite publicar mensajes e insertar scripts de usuario en una página web.The WkUserContentController object allows posting messages and injecting user scripts into a web page.
  • El constructor del representador crea un WKUserScript objeto, que inserta la función invokeCSharpAction de JavaScript en la página web después de cargarla.The renderer constructor creates a WKUserScript object, which injects the invokeCSharpAction JavaScript function into the web page after the web page is loaded.
  • El constructor del representador llama al método WKUserContentController.AddUserScript para agregar el objeto WKUserScript al controlador de contenido.The renderer constructor calls the WKUserContentController.AddUserScript method to add the WKUserScript object to the content controller.
  • El constructor del representador llama al método WKUserContentController.AddScriptMessageHandler para agregar un controlador de mensajes de script denominado invokeAction al objeto WKUserContentController, lo que hace que la función window.webkit.messageHandlers.invokeAction.postMessage(data) de JavaScript se defina en todos los marcos de todas las instancias de WebView en las que se usa el objeto WKUserContentController.The renderer constructor calls the WKUserContentController.AddScriptMessageHandler method to add a script message handler named invokeAction to the WKUserContentController object, which will cause the JavaScript function window.webkit.messageHandlers.invokeAction.postMessage(data) to be defined in all frames in all WebView instances that use the WKUserContentController object.
  • Siempre que el representador personalizado está asociado a un nuevo elemento de Xamarin.Forms:Provided that the custom renderer is attached to a new Xamarin.Forms element:
    • El método WKWebView.LoadRequest carga el archivo HTML especificado por la propiedad HybridWebView.Uri.The WKWebView.LoadRequest method loads the HTML file that's specified by the HybridWebView.Uri property. El código especifica que el archivo se almacena en la carpeta Content del proyecto.The code specifies that the file is stored in the Content folder of the project. Una vez que se muestra la página web, la función de JavaScript invokeCSharpAction se inserta en la página web.Once the web page is displayed, the invokeCSharpAction JavaScript function will be injected into the web page.
  • Los recursos se liberan cuando cambia el elemento al que está asociado el representador.Resources are released when the element the renderer is attached to changes.
  • El elemento de Xamarin.Forms se limpia cuando se desecha el representador.The Xamarin.Forms element is cleaned up when the renderer is disposed of.

Nota

La clase WKWebView solo se admite en iOS 8 y versiones posteriores.The WKWebView class is only supported in iOS 8 and later.

Además, Info.plist debe actualizarse para que incluya los siguientes valores:In addition, Info.plist must be updated to include the following values:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Creación del representador personalizado en AndroidCreate the custom renderer on android

En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:The following code example shows the custom renderer for the Android platform:

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
    public class HybridWebViewRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
        Context _context;

        public HybridWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }

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

            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((HybridWebView)Element).Cleanup();
            }
            if (e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
                Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((HybridWebView)Element).Cleanup();
            }
            base.Dispose(disposing);
        }        
    }
}

La clase HybridWebViewRenderer carga la página web especificada en la propiedad HybridWebView.Uri en un control nativo WebView y la función de JavaScript invokeCSharpAction se inserta en la página web, una vez que la página web ha terminado de cargarse, con la invalidación OnPageFinished en la clase JavascriptWebViewClient:The HybridWebViewRenderer class loads the web page specified in the HybridWebView.Uri property into a native WebView control, and the invokeCSharpAction JavaScript function is injected into the web page, after the web page has finished loading, with the OnPageFinished override in the JavascriptWebViewClient class:

public class JavascriptWebViewClient : FormsWebViewClient
{
    string _javascript;

    public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)
    {
        _javascript = javascript;
    }

    public override void OnPageFinished(WebView view, string url)
    {
        base.OnPageFinished(view, url);
        view.EvaluateJavascript(_javascript, null);
    }
}

Una vez que el usuario escribe su nombre y hace clic en el elemento HTML button, se ejecuta la función de JavaScript invokeCSharpAction.Once the user enters their name and clicks the HTML button element, the invokeCSharpAction JavaScript function is executed. Esta funcionalidad se logra del siguiente modo:This functionality is achieved as follows:

  • Siempre que el representador personalizado está asociado a un nuevo elemento de Xamarin.Forms:Provided that the custom renderer is attached to a new Xamarin.Forms element:
    • El método SetWebViewClient establece un nuevo objeto JavascriptWebViewClient como la implementación de WebViewClient.The SetWebViewClient method sets a new JavascriptWebViewClient object as the implementation of WebViewClient.
    • El método WebView.AddJavascriptInterface inserta una nueva instancia de JSBridge en el marco principal del contexto de JavaScript de WebView y le asigna el nombre jsBridge.The WebView.AddJavascriptInterface method injects a new JSBridge instance into the main frame of the WebView's JavaScript context, naming it jsBridge. Esto permite acceder a los métodos de la clase JSBridge desde JavaScript.This allows methods in the JSBridge class to be accessed from JavaScript.
    • El método WebView.LoadUrl carga el archivo HTML especificado por la propiedad HybridWebView.Uri.The WebView.LoadUrl method loads the HTML file that's specified by the HybridWebView.Uri property. El código especifica que el archivo se almacena en la carpeta Content del proyecto.The code specifies that the file is stored in the Content folder of the project.
    • En la clase JavascriptWebViewClient, la función de JavaScript invokeCSharpAction se inserta en la página web una vez que esta termina de cargarse.In the JavascriptWebViewClient class, the invokeCSharpAction JavaScript function is injected into the web page once the page has finished loading.
  • Los recursos se liberan cuando cambia el elemento al que está asociado el representador.Resources are released when the element the renderer is attached to changes.
  • El elemento de Xamarin.Forms se limpia cuando se desecha el representador.The Xamarin.Forms element is cleaned up when the renderer is disposed of.

Cuando se ejecuta la función de JavaScript invokeCSharpAction, a su vez invoca al método JSBridge.InvokeAction, que se muestra en el ejemplo de código siguiente:When the invokeCSharpAction JavaScript function is executed, it in turn invokes the JSBridge.InvokeAction method, which is shown in the following code example:

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))
        {
            ((HybridWebView)hybridRenderer.Element).InvokeAction(data);
        }
    }
}

La clase debe derivar de Java.Lang.Object y los métodos que se exponen a JavaScript deben decorarse con los atributos [JavascriptInterface] y [Export].The class must derive from Java.Lang.Object, and methods that are exposed to JavaScript must be decorated with the [JavascriptInterface] and [Export] attributes. Por lo tanto, cuando se inserta la función de JavaScript invokeCSharpAction en la página web y se ejecuta, llama al método JSBridge.InvokeAction, puesto que está decorado con los atributos [JavascriptInterface] y [Export("invokeAction")].Therefore, when the invokeCSharpAction JavaScript function is injected into the web page and is executed, it will call the JSBridge.InvokeAction method due to being decorated with the [JavascriptInterface] and [Export("invokeAction")] attributes. A su vez, el método InvokeAction invoca al método HybridWebView.InvokeAction, que invocará a la acción registrada para mostrar la ventana emergente.In turn, the InvokeAction method invokes the HybridWebView.InvokeAction method, which will invoke the registered action to display the pop-up.

Importante

En los proyectos de Android en los que se use el atributo [Export] se debe incluir una referencia a Mono.Android.Export, o bien se producirá un error del compilador.Android projects that use the [Export] attribute must include a reference to Mono.Android.Export, or a compiler error will result.

Tenga en cuenta que la clase JSBridge mantiene un elemento WeakReference para la clase HybridWebViewRenderer.Note that the JSBridge class maintains a WeakReference to the HybridWebViewRenderer class. Esto es para evitar la creación de una referencia circular entre las dos clases.This is to avoid creating a circular reference between the two classes. Para más información, vea Referencias débiles.For more information see Weak References.

Creación del representador personalizado en UWPCreate the custom renderer on UWP

En el siguiente ejemplo de código se muestra el representador personalizado para UWP:The following code example shows the custom renderer for UWP:

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.UWP
{
    public class HybridWebViewRenderer : WebViewRenderer
    {
        const string JavaScriptFunction = "function invokeCSharpAction(data){window.external.notify(data);}";

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

            if (e.OldElement != null)
            {
                Control.NavigationCompleted -= OnWebViewNavigationCompleted;
                Control.ScriptNotify -= OnWebViewScriptNotify;
            }
            if (e.NewElement != null)
            {
                Control.NavigationCompleted += OnWebViewNavigationCompleted;
                Control.ScriptNotify += OnWebViewScriptNotify;
                Control.Source = new Uri($"ms-appx-web:///Content//{((HybridWebView)Element).Uri}");
            }
        }

        async void OnWebViewNavigationCompleted(Windows.UI.Xaml.Controls.WebView sender, WebViewNavigationCompletedEventArgs args)
        {
            if (args.IsSuccess)
            {
                // Inject JS script
                await Control.InvokeScriptAsync("eval", new[] { JavaScriptFunction });
            }
        }

        void OnWebViewScriptNotify(object sender, NotifyEventArgs e)
        {
            ((HybridWebView)Element).InvokeAction(e.Value);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((HybridWebView)Element).Cleanup();
            }
            base.Dispose(disposing);
        }        
    }
}

La clase HybridWebViewRenderer carga la página web especificada en la propiedad HybridWebView.Uri en un control nativo WebView y la función de JavaScript invokeCSharpAction se inserta en la página web, una vez cargada, con el método WebView.InvokeScriptAsync.The HybridWebViewRenderer class loads the web page specified in the HybridWebView.Uri property into a native WebView control, and the invokeCSharpAction JavaScript function is injected into the web page, after the web page has loaded, with the WebView.InvokeScriptAsync method. Una vez que el usuario escribe su nombre y hace clic en el elemento HTML button, se ejecuta la función de JavaScript invokeCSharpAction y se llama al método OnWebViewScriptNotify después de que se reciba una notificación de la página web.Once the user enters their name and clicks the HTML button element, the invokeCSharpAction JavaScript function is executed, with the OnWebViewScriptNotify method being called after a notification is received from the web page. A su vez, este método invoca al método HybridWebView.InvokeAction, que invoca a la acción registrada para mostrar la ventana emergente.In turn, this method invokes the HybridWebView.InvokeAction method, which will invoke the registered action to display the pop-up.

Esta funcionalidad se logra del siguiente modo:This functionality is achieved as follows:

  • Siempre que el representador personalizado está asociado a un nuevo elemento de Xamarin.Forms:Provided that the custom renderer is attached to a new Xamarin.Forms element:
    • Se registran controladores de eventos para los eventos NavigationCompleted y ScriptNotify.Event handlers for the NavigationCompleted and ScriptNotify events are registered. El evento NavigationCompleted se desencadena cuando el control nativo WebView ha terminado de cargar el contenido actual o si se ha producido un error de navegación.The NavigationCompleted event fires when either the native WebView control has finished loading the current content or if navigation has failed. El evento ScriptNotify se desencadena cuando el contenido del control nativo WebView usa JavaScript para pasar una cadena a la aplicación.The ScriptNotify event fires when the content in the native WebView control uses JavaScript to pass a string to the application. La página web desencadena el evento ScriptNotify mediante una llamada a window.external.notify al pasar un parámetro string.The web page fires the ScriptNotify event by calling window.external.notify while passing a string parameter.
    • La propiedad WebView.Source está establecida en el URI del archivo HTML especificado por la propiedad HybridWebView.Uri.The WebView.Source property is set to the URI of the HTML file that's specified by the HybridWebView.Uri property. El código da por supuesto que el archivo está almacenado en la carpeta Content del proyecto.The code assumes that the file is stored in the Content folder of the project. Una vez que se muestra la página web, se desencadena el evento NavigationCompleted y se invoca al método OnWebViewNavigationCompleted.Once the web page is displayed, the NavigationCompleted event will fire and the OnWebViewNavigationCompleted method will be invoked. La función de JavaScript invokeCSharpAction se inserta en la página web con el método WebView.InvokeScriptAsync, siempre que la navegación se haya realizado correctamente.The invokeCSharpAction JavaScript function will then be injected into the web page with the WebView.InvokeScriptAsync method, provided that the navigation completed successfully.
  • La suscripción de los eventos se cancela cuando cambia el representador al que está adjunto el elemento.Event are unsubscribed from when the element the renderer is attached to changes.
  • El elemento de Xamarin.Forms se limpia cuando se desecha el representador.The Xamarin.Forms element is cleaned up when the renderer is disposed of.