Implementación de HybridWebViewImplementing a HybridWebView

Descargar ejemplo Descargar el ejemploDownload Sample Download the sample

Los controles de interfaz de usuario personalizados de Xamarin.Forms deben derivar de la clase View, que se usa para colocar diseños y controles en la pantalla. En este artículo se muestra cómo crear un representador personalizado para un control personalizado HybridWebView, lo que mejora los controles web específicos de la plataforma para permitir la invocación de código de C# desde JavaScript.Xamarin.Forms custom user interface controls should derive from the View class, which is used to place layouts and controls on the screen. This article demonstrates how to create a custom renderer for a HybridWebView custom control, which demonstrates how to enhance the platform-specific web controls to allow C# code to be invoked from JavaScript.

Todas las vistas de Xamarin.Forms tienen un representador adjunto para cada plataforma 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 una instancia de View en iOS, se crea una instancia de la clase ViewRenderer, que a su vez crea una instancia del control UIView nativo.When a View is rendered by a Xamarin.Forms application in iOS, the ViewRenderer class is instantiated, which in turn instantiates a native UIView control. En la plataforma Android, la clase ViewRenderer crea una instancia de un control View.On the Android platform, the ViewRenderer class instantiates a View control. En Plataforma universal de Windows (UWP), la clase ViewRenderer crea una instancia de un control FrameworkElement nativo.On the Universal Windows Platform (UWP), the ViewRenderer class instantiates a native FrameworkElement 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 Renderer Base Classes and Native Controls (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:

El proceso de representación puede usarse para implementar personalizaciones específicas de plataforma al crear un representador personalizado para una clase View en cada plataforma.The rendering process can be used to implement platform-specific customizations by creating a custom renderer for a View on each platform. Para ello, siga este procedimiento:The process for doing this is as follows:

  1. Cree el control HybridWebView personalizado.Create the HybridWebView custom control.
  2. Use 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 va a hablar de cada elemento para implementar un representador de HybridWebView que mejore los controles web específicos de la plataforma a fin de 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 platform-specific web controls 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 obtener más información sobre el proceso de invocación de C# desde JavaScript, vea Invocación a C# desde JavaScript.For more information about the process for invoking C# from JavaScript, see Invoking C# from JavaScript. Para obtener 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 Creating the Web Page.

Creación de HybridWebViewCreating the HybridWebView

Se puede crear el control personalizado HybridWebView mediante la creación de subclases de la clase View, como se muestra en el siguiente ejemplo de código:The HybridWebView custom control can be created by subclassing the View class, as shown in the following code example:

public class HybridWebView : View
{
  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. Se llama a este método desde un representador personalizado en cada proyecto específico de plataforma.This method will be called from a custom renderer in each platform-specific project.

Uso de HybridWebViewConsuming 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,20,0,0">
    <ContentPage.Content>
        <local:HybridWebView x:Name="hybridWebView" Uri="index.html"
          HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
    </ContentPage.Content>
</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 deben 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 class HybridWebViewPageCS : ContentPage
{
  public HybridWebViewPageCS ()
  {
    var hybridWebView = new HybridWebView {
      Uri = "index.html",
      HorizontalOptions = LayoutOptions.FillAndExpand,
      VerticalOptions = LayoutOptions.FillAndExpand
    };
    ...
    Padding = new Thickness (0, 20, 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 específico de plataforma y que muestra el control web nativo.It's Uri property is set to an HTML file that is stored in each platform-specific 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 específicos de la plataforma al permitir 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-specific web controls by allowing C# code to be invoked from JavaScript.

Creación del representador personalizado en cada plataformaCreating 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 ViewRenderer<T1,T2> que representa el control personalizado.Create a subclass of the ViewRenderer<T1,T2> class that renders the custom control. El primer argumento de tipo debe ser el control personalizado para el que es el representador, en este caso HybridWebView.The first type argument should be the custom control the renderer is for, in this case HybridWebView. El segundo argumento de tipo debe ser el control nativo que va a implementar la vista personalizada.The second type argument should be the native control that will implement the custom view.
  2. Invalide el método OnElementChanged que representa el control personalizado y escriba lógica para personalizarlo.Override the OnElementChanged method that renders the custom control and write logic to customize it. Se llama a este método cuando se crea el correspondiente control personalizado de Xamarin.Forms.This method is called when the corresponding Xamarin.Forms custom control is created.
  3. Agregue un atributo ExportRenderer a la clase del representador personalizado para especificar que se va a usar para representar el control personalizado de Xamarin.Forms.Add an ExportRenderer attribute to the custom renderer class to specify that it will be used to render the Xamarin.Forms custom control. Este atributo se usa para registrar el 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 hay un representador personalizado registrado, se usa 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:

El control personalizado HybridWebView se representa mediante clases de representador específicas de la plataforma, que se derivan de la clase ViewRenderer de cada plataforma.The HybridWebView custom control is rendered by platform-specific renderer classes, which all derive from the ViewRenderer class for each platform. Esto da lugar a que cada control personalizado HybridWebView se represente con controles web específicos de la plataforma, como se muestra en las capturas de pantalla siguientes:This results in each HybridWebView custom control being rendered with platform-specific web controls, as shown in the following screenshots:

La clase ViewRenderer expone 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 ViewRenderer class exposes 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 ElementChangedEventArgs que contiene propiedades OldElement y NewElement.This method takes an ElementChangedEventArgs 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.

Una versión invalidada del método OnElementChanged, en cada clase de representador específica de la plataforma, es el lugar en el que realizar la personalización y la creación de instancias del control web nativo.An overridden version of the OnElementChanged method, in each platform-specific renderer class, is the place to perform the native web control instantiation and customization. Se debe usar el método SetNativeControl para crear instancias del control web nativo; además, este método también asigna la referencia del control a la propiedad Control.The SetNativeControl method should be used to instantiate the native web control, and this method will also assign the control reference to the Control property. Además, se puede obtener una referencia al control de Xamarin.Forms que se va a representar mediante la propiedad Element.In addition, a reference to the Xamarin.Forms control that's being rendered can be obtained through the Element property.

En algunas circunstancias, se puede llamar al método OnElementChanged varias veces.In some circumstances the OnElementChanged method can be called multiple times. Por lo tanto, para evitar pérdidas de memoria, se debe tener cuidado a la hora de crear instancias de un nuevo control nativo.Therefore, to prevent memory leaks, care must be taken when instantiating a new native control. El enfoque que usar al crear instancias de un nuevo control nativo en un presentador personalizado se muestra en el ejemplo de código siguiente:The approach to use when instantiating a new native control in a custom renderer is shown in the following code example:

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

  if (e.OldElement != null) {
    // Unsubscribe from event handlers and cleanup any resources
  }

  if (e.NewElement != null) {
    if (Control == null) {
      // Instantiate the native control and assign it to the Control property with
      // the SetNativeControl method
    }
    // Configure the control and subscribe to event handlers
  }
}

Solo se debe crear una instancia de un nuevo control nativo una vez, cuando la propiedad Control es null.A new native control should only be instantiated once, when the Control property is null. Además, solo se debe crear, configurar el control y suscribir los controladores de eventos cuando se adjunta el presentador personalizado a un nuevo elemento de Xamarin.Forms.In addition, the control should only be created, configured, and event handlers subscribed to when the custom renderer is attached to a new Xamarin.Forms element. De forma similar, solo se debe cancelar la suscripción de los controladores de eventos que se han suscrito cuando cambia el elemento al que está asociado el presentador.Similarly, any event handlers that were subscribed to should only be unsubscribed from when the element the renderer is attached to changes. La adopción de este enfoque ayuda a crear un representador personalizado eficaz que no sufra pérdidas de memoria.Adopting this approach will help to create a performant custom renderer that doesn't suffer from memory leaks.

Importante

El método SetNativeControl solo se debe llamar si e.NewElement no es null.The SetNativeControl method should only be called if e.NewElement is not null.

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 va a representar 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 del 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 habla de la estructura de la página web cargada por cada control web nativo, el proceso para invocar a C# desde JavaScript y su implementación en cada clase de representador personalizado específico 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-specific custom renderer class.

Creación de la página webCreating 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-1.11.0.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 JavaScriptInvoking 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 iOSCreating the Custom Renderer on iOS

El ejemplo de código siguiente 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 : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
    {
        const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
        WKUserContentController userController;

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

            if (e.OldElement != null) {
                userController.RemoveAllUserScripts ();
                userController.RemoveScriptMessageHandler ("invokeAction");
                var hybridWebView = e.OldElement as HybridWebView;
                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);
                }
                string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
                Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
            }
        }

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

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:

  • 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:
    • Siempre que la propiedad Control es null, se efectúan las siguientes operaciones:Provided that the Control property is null, the following operations are carried out:
    • 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.
  • Cuando cambia el elemento al que está asociado el representador:When the element the renderer is attached to changes:
    • Se liberan recursos.Resources are released.

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 AndroidCreating 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 : ViewRenderer<HybridWebView, Android.Webkit.WebView>
    {
        const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
        Context _context;

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

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

            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                var hybridWebView = e.OldElement as HybridWebView;
                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.LoadUrl($"file:///android_asset/Content/{Element.Uri}");
            }
        }
    }
}

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 : WebViewClient
{
    string _javascript;

    public JavascriptWebViewClient(string javascript)
    {
        _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:
    • Siempre que la propiedad Control es null, se efectúan las siguientes operaciones:Provided that the Control property is null, the following operations are carried out:
      • Se crea una instancia nativa de WebView, JavaScript se habilita en el control y se establece una instancia de JavascriptWebViewClient como la implementación de WebViewClient.A native WebView instance is created, JavaScript is enabled in the control, and a JavascriptWebViewClient instance is set as the implementation of WebViewClient.
      • Se llama al método SetNativeControl para asignar una referencia al control nativo WebView para la propiedad Control.The SetNativeControl method is called to assign a reference to the native WebView control to the Control property.
    • 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.
  • Cuando cambia el elemento al que está asociado el representador:When the element the renderer is attached to changes:
    • Se liberan recursos.Resources are released.

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))
    {
      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 invoca a la acción registrada para mostrar la ventana emergente.In turn, the InvokeAction method invokes the HybridWebView.InvokeAction method, which will invoked the registered action to display the pop-up.

Nota

Los proyectos que usan el atributo [Export] deben incluir una referencia a Mono.Android.Export, o se produce un error del compilador.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 obtener más información, vea Referencias débiles en MSDN.For more information see Weak References on MSDN.

Creación del representador personalizado en UWPCreating the Custom Renderer on UWP

En el ejemplo de código siguiente 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 : ViewRenderer<HybridWebView, Windows.UI.Xaml.Controls.WebView>
    {
        const string JavaScriptFunction = "function invokeCSharpAction(data){window.external.notify(data);}";

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

            if (e.OldElement != null)
            {
                Control.NavigationCompleted -= OnWebViewNavigationCompleted;
                Control.ScriptNotify -= OnWebViewScriptNotify;
            }
            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    SetNativeControl(new Windows.UI.Xaml.Controls.WebView());
                }
                Control.NavigationCompleted += OnWebViewNavigationCompleted;
                Control.ScriptNotify += OnWebViewScriptNotify;
                Control.Source = new Uri(string.Format("ms-appx-web:///Content//{0}", Element.Uri));
            }
        }

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

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

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:
    • Siempre que la propiedad Control es null, se efectúan las siguientes operaciones:Provided that the Control property is null, the following operations are carried out:
      • Se llama al método SetNativeControl para crear instancias de un nuevo control nativo WebView y asignar una referencia a él para la propiedad Control.The SetNativeControl method is called to instantiate a new native WebView control and assign a reference to it to the Control property.
    • 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.
  • Cuando cambia el elemento al que está asociado el representador:When the element the renderer is attached to changes:
    • Se cancela la suscripción a los eventos.Events are unsubscribed from.

ResumenSummary

En este artículo se ha mostrado cómo crear un representador personalizado para un control personalizado HybridWebView, lo que mejora los controles web específicos de la plataforma para permitir la invocación de código de C# desde JavaScript.This article has demonstrated how to create a custom renderer for a HybridWebView custom control, that demonstrates how to enhance the platform-specific web controls to allow C# code to be invoked from JavaScript.