Partager via


Personnalisation d’une WebView

Il Xamarin.FormsWebView s’agit d’une vue qui affiche du contenu web et HTML dans votre application. Cet article explique comment créer un renderer personnalisé qui étend le WebView code C# à appeler à partir de JavaScript.

Chaque Xamarin.Forms vue a un renderer associé pour chaque plateforme qui crée une instance d’un contrôle natif. Lorsqu’une WebView Xamarin.Forms application est rendue sur iOS, la WkWebViewRenderer classe est instanciée, qui instancie à son tour un contrôle natif WkWebView . Sur la plateforme Android, la classe WebViewRenderer instancie un contrôle WebView natif. Sur la plateforme Windows universelle (UWP), la classe WebViewRenderer instancie un contrôle WebView natif. Pour plus d’informations sur les classes de renderer et de contrôle natives qui contrôlent Xamarin.Forms la correspondance, consultez Les classes de base du renderer et les contrôles natifs.

Le diagramme suivant illustre la relation entre View et les contrôles natifs correspondants qui l’implémentent :

Relation entre la classe WebView et ses classes natives d’implémentation

Le processus de rendu peut être utilisé pour implémenter des personnalisations de plateforme en créant un renderer personnalisé pour une WebView plateforme sur chaque plateforme. Le processus pour y parvenir est le suivant :

  1. Créez le contrôle personnalisé HybridWebView.
  2. Consommer à partir de HybridWebViewXamarin.Forms.
  3. Créez le renderer personnalisé pour le HybridWebView sur chaque plateforme.

Chaque élément sera maintenant abordé à son tour pour implémenter un HybridWebView renderer qui améliore la possibilité d’appeler du Xamarin.FormsWebView code C# à partir de JavaScript. L’instance HybridWebView est utilisée pour afficher une page HTML qui invite l’utilisateur à entrer son nom. Ensuite, quand l’utilisateur clique sur un bouton HTML, une fonction JavaScript appelle une Action C# qui affiche une fenêtre contextuelle contenant le nom de l’utilisateur.

Pour plus d’informations sur le processus d’appel de C# à partir de JavaScript, consultez Appeler C# à partir de JavaScript. Pour plus d’informations sur la page HTML, consultez Créer la page web.

Remarque

Un WebView peut appeler une fonction JavaScript à partir de C# et retourner tout résultat au code C# appelant. Pour plus d’informations, consultez Appel de JavaScript.

Créer hybridWebView

Le HybridWebView contrôle personnalisé peut être créé en sous-classe de la WebView classe :

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

Le contrôle personnalisé HybridWebView est créé dans le projet de bibliothèque .NET Standard et définit l’API suivante :

  • Une propriété Uri qui spécifie l’adresse de la page web à charger.
  • Une méthode RegisterAction qui inscrit une Action auprès du contrôle. L’action inscrite est appelée à partir d’un code JavaScript contenu dans le fichier HTML référencé par le biais de la propriété Uri.
  • Une méthode CleanUpqui supprime la référence à l’Action inscrite.
  • Une méthode InvokeAction qui appelle l’Action inscrite. Cette méthode est appelée à partir d’un renderer personnalisé dans chaque projet de plateforme.

Utiliser HybridWebView

Vous pouvez référencer le contrôle personnalisé HybridWebView en XAML dans le projet de bibliothèque .NET Standard en déclarant un espace de noms pour son emplacement et en utilisant le préfixe d’espace de noms sur le contrôle personnalisé. L’exemple de code suivant montre comment le contrôle personnalisé HybridWebView peut être consommé par une page XAML :

<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>

Le préfixe d’espace de noms local peut porter n’importe quel nom. Toutefois, les valeurs clr-namespace et assembly doivent correspondre aux détails du contrôle personnalisé. Une fois l’espace de noms déclaré, le préfixe est utilisé pour référencer le contrôle personnalisé.

L’exemple de code suivant montre comment le contrôle personnalisé HybridWebView peut être consommé par une page C# :

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

L’instance HybridWebView est utilisée pour afficher un contrôle web natif sur chaque plateforme. Uri Sa propriété est définie sur un fichier HTML stocké dans chaque projet de plateforme et qui sera affiché par le contrôle web natif. Le code HTML restitué invite l’utilisateur à entrer son nom, une fonction JavaScript appelant une Action C# en réponse à un clic de bouton HTML.

La HybridWebViewPage enregistre l’action à appeler à partir de JavaScript, comme illustré dans l’exemple de code suivant :

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

Cette action appelle la méthode DisplayAlertpour afficher une fenêtre contextuelle modale qui présente le nom entré dans la page HTML affichée par l’instance HybridWebView.

Un renderer personnalisé peut désormais être ajouté à chaque projet d’application pour améliorer les contrôles web de plateforme en autorisant l’appel du code C# à partir de JavaScript.

Créer le renderer personnalisé sur chaque plateforme

Le processus de création de la classe de renderer personnalisé est le suivant :

  1. Créez une sous-classe de la WkWebViewRenderer classe sur iOS et la WebViewRenderer classe sur Android et UWP, qui restitue le contrôle personnalisé.
  2. Remplacez la OnElementChanged méthode qui restitue et écrit la logique pour la WebView personnaliser. Cette méthode est appelée lorsqu’un HybridWebView objet est créé.
  3. Ajoutez un ExportRenderer attribut à la classe de renderer personnalisé ou AssemblyInfo.cs, pour spécifier qu’il sera utilisé pour afficher le Xamarin.Forms contrôle personnalisé. Cet attribut est utilisé pour inscrire le renderer personnalisé avec Xamarin.Forms.

Remarque

Pour la plupart des Xamarin.Forms éléments, il est facultatif de fournir un renderer personnalisé dans chaque projet de plateforme. Si un renderer personnalisé n’est pas inscrit, le renderer par défaut de la classe de base du contrôle est utilisé. Toutefois, les renderers personnalisés sont nécessaires dans chaque projet de plateforme lors du rendu d’un élément View.

Le diagramme suivant illustre les responsabilités de chaque projet dans l’exemple d’application ainsi que les relations qu’ils entretiennent les uns avec les autres :

Responsabilités du projet de renderer personnalisé HybridWebView

Le HybridWebView contrôle personnalisé est rendu par les classes de renderer de plateforme, qui dérivent de la WkWebViewRenderer classe sur iOS et de la WebViewRenderer classe sur Android et UWP. Cela entraîne le rendu de chaque HybridWebView contrôle personnalisé avec des contrôles web natifs, comme illustré dans les captures d’écran suivantes :

HybridWebView sur chaque plateforme

Les WkWebViewRenderer classes exposent WebViewRenderer la OnElementChanged méthode, appelée lorsque le Xamarin.Forms contrôle personnalisé est créé pour afficher le contrôle web natif correspondant. Cette méthode prend un VisualElementChangedEventArgs paramètre qui contient et NewElement des OldElement propriétés. Ces propriétés représentent l’élément Xamarin.Forms auquel le renderer a été attaché, et l’élément Xamarin.Forms auquel le renderer est attaché, respectivement. Dans l’exemple d’application, la propriété OldElement est null et la propriété NewElement contient une référence à l’instance HybridWebView.

Une version substituée de la OnElementChanged méthode, dans chaque classe de renderer de plateforme, est l’endroit où effectuer la personnalisation du contrôle web natif. Une référence au Xamarin.Forms contrôle en cours de rendu peut être obtenue via la Element propriété.

Chaque classe de renderer personnalisée est décorée avec un ExportRenderer attribut qui inscrit le renderer avec Xamarin.Forms. L’attribut prend deux paramètres : le nom de type du Xamarin.Forms contrôle personnalisé en cours de rendu et le nom de type du renderer personnalisé. Le préfixe assembly de l’attribut spécifie que l’attribut s’applique à la totalité de l’assembly.

Les sections suivantes décrivent la structure de la page web chargée par chaque contrôle web natif, le processus d’appel C# à partir de JavaScript et l’implémentation de celle-ci dans chaque classe de renderer personnalisée de plateforme.

Créez la page web.

L’exemple de code suivant montre la page web affichée par le contrôle personnalisé HybridWebView :

<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 page web permet à un utilisateur d’entrer son nom dans un élément input et fournit un élément button qui appelle du code C# quand l’utilisateur clique cet élément. Le processus pour y parvenir est le suivant :

  • Quand l’utilisateur clique sur l’élément button, la fonction JavaScript invokeCSCode est appelée et reçoit la valeur de l’élément input.
  • La fonction invokeCSCode appelle la fonction log pour afficher les données qu’elle envoie à l’Action C#. Elle appelle ensuite la méthode invokeCSharpAction pour appeler l’Action C#, en transmettant le paramètre reçu de l’élément input.

La fonction JavaScript invokeCSharpActionn’est pas définie dans la page web ; elle y est injectée par chaque renderer personnalisé.

Sur iOS, ce fichier HTML réside dans le dossier Contenu du projet de plateforme, avec une action de build de BundleResource. Sur Android, ce fichier HTML réside dans le dossier Ressources/Contenu du projet de plateforme, avec une action de build de AndroidAsset.

Appeler C# à partir de JavaScript

Le processus permettant d’appeler C# à partir de JavaScript est identique sur chaque plateforme :

  • Le renderer personnalisé crée un contrôle web natif et charge le fichier HTML spécifié par la propriété HybridWebView.Uri.
  • Une fois la page web chargée, le renderer personnalisé injecte la fonction JavaScript invokeCSharpAction dans la page web.
  • Quand l’utilisateur entre son nom et clique sur l’élément button HTML, la fonction invokeCSCode est appelée, qui appelle à son tour la fonction invokeCSharpAction.
  • La fonction invokeCSharpAction appelle une méthode dans le renderer personnalisé, qui à son tour appelle la méthode HybridWebView.InvokeAction.
  • La méthode HybridWebView.InvokeAction appelle l’Action inscrite.

Les sections suivantes expliquent comment ce processus est implémenté sur chaque plateforme.

Créer le renderer personnalisé sur iOS

L’exemple de code suivant illustre le renderer personnalisé pour la plateforme iOS :

[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 classe HybridWebViewRenderer charge la page web spécifiée dans la propriété HybridWebView.Uri dans un contrôle WKWebView natif, puis la fonction JavaScript invokeCSharpAction est injectée dans la page web. Une fois que l’utilisateur entre son nom et clique sur l’élément button HTML, la fonction JavaScript invokeCSharpAction est exécutée, puis la méthode DidReceiveScriptMessage est appelée après la réception d’un message de la page web. À son tour, cette méthode appelle la méthode HybridWebView.InvokeAction, qui appelle l’action inscrite pour afficher la fenêtre contextuelle.

Cette fonctionnalité est obtenue comme suit :

  • Le constructeur du convertisseur crée un WkWebViewConfiguration objet et récupère son WKUserContentController objet. L’objet WkUserContentController permet de publier des messages et d’injecter des scripts utilisateur dans une page web.
  • Le constructeur du convertisseur crée un WKUserScript objet, qui injecte la invokeCSharpAction fonction JavaScript dans la page web une fois la page web chargée.
  • Le constructeur du convertisseur appelle la WKUserContentController.AddUserScript méthode pour ajouter l’objet WKUserScript au contrôleur de contenu.
  • Le constructeur du convertisseur appelle la WKUserContentController.AddScriptMessageHandler méthode pour ajouter un gestionnaire de messages de script nommé invokeAction à l’objet, ce qui entraîne la définition de la WKUserContentController fonction window.webkit.messageHandlers.invokeAction.postMessage(data) JavaScript dans toutes les images de toutes les WebView instances qui utilisent l’objet WKUserContentController .
  • À condition que le renderer personnalisé soit attaché à un nouvel Xamarin.Forms élément :
    • La méthode WKWebView.LoadRequest charge le fichier HTML qui est spécifié par la propriété HybridWebView.Uri. Le code spécifie que le fichier est stocké dans le dossier Content du projet. Une fois la page web affichée, la fonction JavaScript invokeCSharpAction y est injectée.
  • Les ressources sont libérées lorsque l’élément du renderer est attaché aux modifications.
  • L’élément Xamarin.Forms est nettoyé lorsque le renderer est supprimé.

Remarque

La classe WKWebView est uniquement prise en charge dans iOS versions 8 et ultérieures.

De plus, Info.plist doit être mis à jour pour inclure les valeurs suivantes :

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

Créer le renderer personnalisé sur Android

L’exemple de code suivant illustre le renderer personnalisé pour la plateforme Android :

[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 classe HybridWebViewRenderer charge la page web spécifiée dans la propriété HybridWebView.Uri dans un contrôle WebView natif, puis la fonction JavaScript invokeCSharpAction est injectée dans la page web, avec la substitution de OnPageFinished dans la classe JavascriptWebViewClient :

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

Une fois que l’utilisateur entre son nom et clique sur l’élément button HTML, la fonction JavaScript invokeCSharpAction est exécutée. Cette fonctionnalité est obtenue comme suit :

  • À condition que le renderer personnalisé soit attaché à un nouvel Xamarin.Forms élément :
    • La SetWebViewClient méthode définit un nouvel JavascriptWebViewClient objet comme implémentation de WebViewClient.
    • La méthode WebView.AddJavascriptInterface injecte une nouvelle instance JSBridge, qu’elle nomme jsBridge, dans le cadre principal du contexte JavaScript de l’affichage web. Ainsi, les méthodes dans la classe JSBridge sont accessibles à partir de JavaScript.
    • La méthode WebView.LoadUrl charge le fichier HTML qui est spécifié par la propriété HybridWebView.Uri. Le code spécifie que le fichier est stocké dans le dossier Content du projet.
    • Dans la classe JavascriptWebViewClient, la fonction JavaScript invokeCSharpAction est injectée dans la page web une fois celle-ci chargée.
  • Les ressources sont libérées lorsque l’élément du renderer est attaché aux modifications.
  • L’élément Xamarin.Forms est nettoyé lorsque le renderer est supprimé.

Quand la fonction JavaScript invokeCSharpAction est exécutée, elle appelle à son tour la méthode JSBridge.InvokeAction, comme l’illustre l’exemple de code suivant :

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 classe doit dériver de Java.Lang.Object, tandis que les méthodes qui sont exposées à JavaScript doivent être décorées avec les attributs [JavascriptInterface] et [Export]. Ainsi, quand la fonction JavaScript invokeCSharpAction est injectée dans la page web et est exécutée, elle appelle la méthode JSBridge.InvokeAction, car celle-ci est décorée avec les attributs [JavascriptInterface] et [Export("invokeAction")]. À son tour, la InvokeAction méthode appelle la HybridWebView.InvokeAction méthode, qui appelle l’action inscrite pour afficher la fenêtre contextuelle.

Important

Les projets Android qui utilisent l’attribut [Export] doivent inclure une référence à Mono.Android.Export, ou une erreur du compilateur se produit.

Notez que la classe JSBridge conserve une WeakReference à la classe HybridWebViewRenderer, afin d’éviter la création d’une référence circulaire entre les deux classes. Pour plus d’informations, consultez Références faibles.

Créer le renderer personnalisé sur UWP

L’exemple de code suivant illustre le renderer personnalisé pour 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 classe HybridWebViewRenderer charge la page web spécifiée dans la propriété HybridWebView.Uri dans un contrôle WebView natif, puis la fonction JavaScript invokeCSharpAction est injectée dans la page web, avec la méthode WebView.InvokeScriptAsync. Une fois que l’utilisateur entre son nom et clique sur l’élément button HTML, la fonction JavaScript invokeCSharpAction est exécutée, puis la méthode OnWebViewScriptNotify est appelée après la réception d’une notification de la page web. À son tour, cette méthode appelle la méthode HybridWebView.InvokeAction, qui appelle l’action inscrite pour afficher la fenêtre contextuelle.

Cette fonctionnalité est obtenue comme suit :

  • À condition que le renderer personnalisé soit attaché à un nouvel Xamarin.Forms élément :
    • Des gestionnaires d’événements pour les événements NavigationCompleted et ScriptNotify sont inscrits. L’événement NavigationCompleted est déclenché quand le contrôle WebView natif a terminé de charger le contenu actuel ou si la navigation a échoué. L’événement ScriptNotify est déclenché quand le contenu du contrôle WebView natif utilise JavaScript pour passer une chaîne à l’application. La page web déclenche l’événement ScriptNotify en appelant window.external.notify tout en passant un paramètre string.
    • La propriété WebView.Source est définie sur l’URI du fichier HTML qui est spécifié par la propriété HybridWebView.Uri. Le code suppose que le fichier est stocké dans le dossier Content du projet. Une fois la page web affichée, l’événement NavigationCompleted est déclenché et la méthode OnWebViewNavigationCompleted est appelée. La fonction JavaScript invokeCSharpAction est ensuite injectée dans la page web avec la méthode WebView.InvokeScriptAsync, à condition que la navigation se soit déroulée correctement.
  • L’événement est annulé lorsque l’élément auquel le renderer est attaché aux modifications.
  • L’élément Xamarin.Forms est nettoyé lorsque le renderer est supprimé.