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 :
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 :
- Créez le contrôle personnalisé
HybridWebView
. - Consommer à partir de
HybridWebView
Xamarin.Forms. - 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 uneAction
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
CleanUp
qui 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 DisplayAlert
pour 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 :
- Créez une sous-classe de la
WkWebViewRenderer
classe sur iOS et laWebViewRenderer
classe sur Android et UWP, qui restitue le contrôle personnalisé. - Remplacez la
OnElementChanged
méthode qui restitue et écrit la logique pour laWebView
personnaliser. Cette méthode est appelée lorsqu’unHybridWebView
objet est créé. - 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 :
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 :
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 JavaScriptinvokeCSCode
est appelée et reçoit la valeur de l’élémentinput
. - La fonction
invokeCSCode
appelle la fonctionlog
pour afficher les données qu’elle envoie à l’Action
C#. Elle appelle ensuite la méthodeinvokeCSharpAction
pour appeler l’Action
C#, en transmettant le paramètre reçu de l’élémentinput
.
La fonction JavaScript invokeCSharpAction
n’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 fonctioninvokeCSCode
est appelée, qui appelle à son tour la fonctioninvokeCSharpAction
. - La fonction
invokeCSharpAction
appelle une méthode dans le renderer personnalisé, qui à son tour appelle la méthodeHybridWebView.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 sonWKUserContentController
objet. L’objetWkUserContentController
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 lainvokeCSharpAction
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’objetWKUserScript
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 laWKUserContentController
fonctionwindow.webkit.messageHandlers.invokeAction.postMessage(data)
JavaScript dans toutes les images de toutes lesWebView
instances qui utilisent l’objetWKUserContentController
. - À 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 dossierContent
du projet. Une fois la page web affichée, la fonction JavaScriptinvokeCSharpAction
y est injectée.
- La méthode
- 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 nouvelJavascriptWebViewClient
objet comme implémentation deWebViewClient
. - La méthode
WebView.AddJavascriptInterface
injecte une nouvelle instanceJSBridge
, qu’elle nommejsBridge
, dans le cadre principal du contexte JavaScript de l’affichage web. Ainsi, les méthodes dans la classeJSBridge
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 dossierContent
du projet. - Dans la classe
JavascriptWebViewClient
, la fonction JavaScriptinvokeCSharpAction
est injectée dans la page web une fois celle-ci chargée.
- La
- 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
etScriptNotify
sont inscrits. L’événementNavigationCompleted
est déclenché quand le contrôleWebView
natif a terminé de charger le contenu actuel ou si la navigation a échoué. L’événementScriptNotify
est déclenché quand le contenu du contrôleWebView
natif utilise JavaScript pour passer une chaîne à l’application. La page web déclenche l’événementScriptNotify
en appelantwindow.external.notify
tout en passant un paramètrestring
. - 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 dossierContent
du projet. Une fois la page web affichée, l’événementNavigationCompleted
est déclenché et la méthodeOnWebViewNavigationCompleted
est appelée. La fonction JavaScriptinvokeCSharpAction
est ensuite injectée dans la page web avec la méthodeWebView.InvokeScriptAsync
, à condition que la navigation se soit déroulée correctement.
- Des gestionnaires d’événements pour les événements
- 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é.