Настройка WebView
Это Xamarin.FormsWebView
представление, отображающее веб-содержимое и HTML в приложении. В этой статье рассказывается, как создать пользовательский отрисовщик, расширяющий возможности WebView
и позволяющий вызывать код C# из JavaScript.
Каждое представление Xamarin.Forms имеет сопутствующий отрисовщик для каждой платформы, который создает экземпляр собственного элемента управления. Когда WebView
отображается в приложении Xamarin.Forms, на платформе iOS создается класс WkWebViewRenderer
, который, в свою очередь, создает собственный элемент управления WkWebView
. На платформе Android класс WebViewRenderer
создает собственный элемент управления WebView
. На универсальной платформе Windows (UWP) класс WebViewRenderer
создает экземпляр собственного элемента управления WebView
. Дополнительные сведения об отрисовщике и классах собственных элементов управления, с которыми сопоставляются элементы управления Xamarin.Forms, см. в статье Базовые классы и собственные элементы управления отрисовщика.
На следующей схеме показана связь между классом View
и соответствующими собственными элементами управления, которые его реализуют:
Процесс отрисовки можно использовать для реализации настроек для конкретных платформ путем создания пользовательского отрисовщика для WebView
на каждой платформе. Этот процесс выглядит следующим образом:
- Создание пользовательского элемента управления
HybridWebView
. - Использование
HybridWebView
из Xamarin.Forms. - Создание настраиваемого отрисовщика для
HybridWebView
на каждой платформе.
Теперь каждый элемент будет обсуждаться в свою очередь для реализации HybridWebView
отрисовщика, который улучшает Xamarin.FormsWebView
возможность вызова кода C# из JavaScript. Экземпляр HybridWebView
будет использоваться для отображения HTML-страницы, которая предлагает пользователю ввести свое имя. Затем, когда пользователь нажимает кнопку HTML, функция JavaScript вызывает C# Action
, который отображает всплывающее окно, содержащее имя пользователя.
Дополнительные сведения о процессе вызова C# из JavaScript см. в разделе Вызов C# из JavaScript. Дополнительные сведения о странице HTML см. в разделе Создание веб-страницы.
Примечание.
WebView
может вызывать функцию JavaScript из C# и возвращать любой результат в вызывающий код C#. Дополнительные сведения см. в разделе Вызов JavaScript.
Создание HybridWebView
Пользовательский элемент управления HybridWebView
можно создать путем создания подкласса класса WebView
:
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);
}
}
Пользовательский элемент управления HybridWebView
создается в проекте библиотеки .NET Standard и определяет следующий API для элемента управления:
- Свойство
Uri
, которое указывает адрес веб-страницы для загрузки. - Метод
RegisterAction
, который регистрируетAction
с элементом управления. Зарегистрированное действие будет вызываться из JavaScript, содержащегося в файле HTML, на который можно сослаться через свойствоUri
. - Метод
CleanUp
, который удаляет ссылку на зарегистрированный объектAction
. - Метод
InvokeAction
, который вызывает зарегистрированный объектAction
. Этот метод будет вызываться из пользовательского отрисовщика в проекте для каждой платформы.
Использование HybridWebView
На пользовательский элемент управления HybridWebView
можно ссылаться в XAML в проекте библиотеки .NET Standard, объявив пространство имен для его расположения и используя префикс пространства имен в пользовательском элементе управления. В следующем примере кода показано, как пользовательский элемент управления HybridWebView
может использоваться страницей 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>
Префикс пространства имен local
может иметь любое имя. Тем не менее значения clr-namespace
и assembly
должны соответствовать данным пользовательского элемента управления. После объявления пространства имен префикс используется для ссылки на пользовательский элемент управления.
В следующем примере кода показано, как пользовательский элемент управления HybridWebView
может использоваться страницей C#:
public HybridWebViewPageCS()
{
var hybridWebView = new HybridWebView
{
Uri = "index.html"
};
// ...
Padding = new Thickness(0, 40, 0, 0);
Content = hybridWebView;
}
Экземпляр HybridWebView
будет использоваться для отображения собственного веб-элемента управления на каждой платформе. Для его свойства Uri
задан HTML-файл, который хранится в проекте для каждой платформы и будет отображаться собственным веб-элементом управления. Отрисованный HTML просит пользователя ввести свое имя с помощью JavaScript, вызывая C# Action
в ответ на нажатие кнопки HTML.
HybridWebViewPage
регистрирует действие, которое необходимо вызывать из JavaScript, как показано в следующем примере кода:
public partial class HybridWebViewPage : ContentPage
{
public HybridWebViewPage()
{
// ...
hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
}
}
Это действие вызывает метод DisplayAlert
для отображения модального всплывающего окна, которое представляет имя, введенное на странице HTML, отображаемой экземпляром HybridWebView
.
Настраиваемый отрисовщик теперь можно добавить в каждый проект приложения для улучшения зависящих от платформы веб-элементов управления благодаря возможности вызова кода C# из JavaScript.
Создание пользовательского отрисовщика на каждой платформе
Процесс создания класса пользовательского отрисовщика выглядит следующим образом:
- Создайте подкласс класса
WkWebViewRenderer
в iOS и классаWebViewRenderer
в Android и UWP, который отрисовывает пользовательский элемент управления. - Переопределите метод
OnElementChanged
, который отрисовываетWebView
, и напишите логику для его настройки. Этот метод вызывается при создании объектаHybridWebView
. - Добавьте атрибут
ExportRenderer
в класс пользовательского отрисовщика или AssemblyInfo.cs, чтобы указать, что он будет использоваться для отрисовки пользовательского элемента управления Xamarin.Forms. Этот атрибут используется для регистрации пользовательского отрисовщика в Xamarin.Forms.
Примечание.
Для большинства элементов Xamarin.Forms предоставлять пользовательский отрисовщик в проекте для каждой платформы необязательно. Если пользовательский отрисовщик не зарегистрирован, будет использоваться отрисовщик по умолчанию для базового класса элемента управления. Однако настраиваемые отрисовщики необходимы в каждом зависящем от платформы проекте при отрисовке элемента View.
На следующей схеме показаны обязанности каждого проекта в примере приложения, а также связи между ними:
Пользовательский элемент управления HybridWebView
отрисовывается с помощью зависящих от платформы классов отрисовщика, которые являются производными от класса WkWebViewRenderer
в iOS и класса WebViewRenderer
в Android и UWP. Это приводит к тому, что каждый пользовательский элемент управления HybridWebView
отрисовывается с помощью собственных веб-элементов управления, как показано на следующих снимках экрана:
Классы WkWebViewRenderer
и WebViewRenderer
предоставляют метод OnElementChanged
, который вызывается при создании пользовательского элемента управления Xamarin.Forms для отрисовки соответствующего собственного веб-элемента управления. Этот метод принимает параметр VisualElementChangedEventArgs
, содержащий свойства OldElement
и NewElement
. Эти свойства представляют элемент Xamarin.Forms, к которому был присоединен отрисовщик, и элемент Xamarin.Forms, к которому присоединен отрисовщик, соответственно. В примере приложения свойство OldElement
будет иметь значение null
, а свойство NewElement
будет содержать ссылку на экземпляр HybridWebView
.
Настройка собственного веб-элемента управления выполняется в переопределенной версии метода OnElementChanged
в классе отрисовщика для каждой платформы. Ссылку на отрисовываемый элемент управления Xamarin.Forms можно получить с помощью свойства Element
.
Каждый класс пользовательского отрисовщика дополняется атрибутом ExportRenderer
, который регистрирует отрисовщик в Xamarin.Forms. Атрибут принимает два параметра — имя типа отрисовываемого пользовательского элемента управления Xamarin.Forms и имя типа пользовательского отрисовщика. Префикс assembly
в атрибуте указывает, что атрибут применяется ко всей сборке.
В следующих разделах рассматриваются структура веб-страницы, загружаемой каждым собственным веб-элементом управления, процесс вызова C# из JavaScript и реализация этого в классах пользовательского отрисовщика для каждой платформы.
Создание веб-страницы
В следующем примере кода показана веб-страница, которая будет отображаться пользовательским элементом управления 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>
Веб-страница позволяет пользователю ввести свое имя в элементе input
и предоставляет элемент button
, который будет вызывать код C# при нажатии. Этот процесс выглядит следующим образом:
- Когда пользователь нажимает на элемент
button
, вызывается функция JavaScriptinvokeCSCode
, и значение элементаinput
передается в функцию. - Функция
invokeCSCode
вызывает функциюlog
для отображения данных, которые она отправляет в C#Action
. Затем она вызывает методinvokeCSharpAction
, чтобы вызывать C#Action
, передавая параметр, полученный от элементаinput
.
Функция JavaScript invokeCSharpAction
не определена на веб-странице и внедряется в нее каждым настраиваемым отрисовщиком.
На iOS этот HTML-файл находится в папке Content проекта платформы с действием сборки BundleResource. На Android этот HTML-файл находится в папке Assets/Content проекта платформы с действием сборки AndroidAsset.
Вызов C# из JavaScript
Процесс вызова C# из JavaScript идентичен на каждой платформе:
- Настраиваемый отрисовщик создает собственный веб-элемент управления и загружает файл HTML, заданный свойством
HybridWebView.Uri
. - После загрузки веб-страницы настраиваемый отрисовщик внедряет функцию JavaScript
invokeCSharpAction
в веб-страницу. - Когда пользователь вводит свое имя и нажимает на элемент HTML
button
, вызывается функцияinvokeCSCode
, которая, в свою очередь, вызывает функциюinvokeCSharpAction
. - Функция
invokeCSharpAction
вызывает метод в настраиваемом отрисовщике, который, свою очередь, вызывает методHybridWebView.InvokeAction
. - Метод
HybridWebView.InvokeAction
вызывает зарегистрированныйAction
.
В следующих разделах мы рассмотрим, как этот процесс реализуется на каждой платформе.
Создание пользовательского отрисовщика в iOS
В следующем примере кода показан пользовательский отрисовщик для платформы 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);
}
}
}
Класс HybridWebViewRenderer
загружает веб-страницу, указанную в свойстве HybridWebView.Uri
, в собственный элемент управления WKWebView
, а функция JavaScript invokeCSharpAction
внедряется в веб-страницы. Когда пользователь вводит свое имя и нажимает элемент HTML button
, выполняется функция JavaScript invokeCSharpAction
, и метод DidReceiveScriptMessage
вызывается после получения сообщения от веб-страницы. В свою очередь, этот метод вызывает метод HybridWebView.InvokeAction
, который будет вызывать зарегистрированное действие для отображения всплывающего окна.
Это достигается следующим образом:
- Конструктор отрисовщика создает объект
WkWebViewConfiguration
и получает его объектWKUserContentController
. ОбъектWkUserContentController
позволяет размещать сообщения и внедрять скрипты пользователя в веб-страницу. - Конструктор отрисовщика создает объект
WKUserScript
, который внедряет функцию JavaScriptinvokeCSharpAction
в веб-страницу после загрузки веб-страницы. - Конструктор отрисовщика вызывает метод
WKUserContentController.AddUserScript
для добавления объектаWKUserScript
в контроллер содержимого. - Конструктор отрисовщика вызывает метод
WKUserContentController.AddScriptMessageHandler
для добавления обработчика сообщений скрипта с именемinvokeAction
в объектWKUserContentController
, который приводит к определению функции JavaScriptwindow.webkit.messageHandlers.invokeAction.postMessage(data)
во всех фреймах в экземплярахWebView
, которые используют объектWKUserContentController
. - При условии, что пользовательский отрисовщик присоединен к новому Xamarin.Forms элементу:
- Метод
WKWebView.LoadRequest
загружает HTML-файл, указанный свойствомHybridWebView.Uri
. Код указывает, что файл сохранен в папкеContent
проекта. Когда веб-страница отображается, функция JavaScriptinvokeCSharpAction
внедряется в веб-страницу.
- Метод
- Ресурсы освобождаются, когда элемент, к которому присоединен отрисовщик, меняется.
- При удалении отрисовщика элемент Xamarin.Forms очищается.
Примечание.
Класс WKWebView
поддерживается только в iOS 8 и более поздних версий.
Кроме того, необходимо изменить файл Info.plist, включив в него следующие значения:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Создание пользовательского отрисовщика в Android
В следующем примере кода показан пользовательский отрисовщик для платформы 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);
}
}
}
Класс HybridWebViewRenderer
загружает веб-страницу, указанную в свойстве HybridWebView.Uri
, в собственный элемент управления WebView
, а функция JavaScript invokeCSharpAction
внедряется в веб-страницы после окончания загрузки веб-страницы с переопределением OnPageFinished
в классе 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);
}
}
Когда пользователь вводит свое имя и нажимает элемент HTML button
, выполняется функция JavaScript invokeCSharpAction
. Это достигается следующим образом:
- При условии, что пользовательский отрисовщик присоединен к новому Xamarin.Forms элементу:
- Метод
SetWebViewClient
задает новый объектJavascriptWebViewClient
в качестве реализацииWebViewClient
. - Метод
WebView.AddJavascriptInterface
вставляет новый экземплярJSBridge
в главный фрейм контекста JavaScript веб-преставления, называя егоjsBridge
. Это открывает доступ к методам в классеJSBridge
из JavaScript. - Метод
WebView.LoadUrl
загружает HTML-файл, указанный свойствомHybridWebView.Uri
. Код указывает, что файл сохранен в папкеContent
проекта. - В классе
JavascriptWebViewClient
функция JavaScriptinvokeCSharpAction
внедряется в веб-страницу после завершения загрузки страницы.
- Метод
- Ресурсы освобождаются, когда элемент, к которому присоединен отрисовщик, меняется.
- При удалении отрисовщика элемент Xamarin.Forms очищается.
Когда выполняется функция JavaScript invokeCSharpAction
, она, в свою очередь, вызывает метод JSBridge.InvokeAction
, который показан в следующем примере кода:
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);
}
}
}
Класс должен быть производным от Java.Lang.Object
, и методы, предоставляемые для JavaScript, должны быть снабжены атрибутами [JavascriptInterface]
и [Export]
. Таким образом, когда функция JavaScript invokeCSharpAction
внедряется в веб-страницу и выполняется, она вызывает метод JSBridge.InvokeAction
, поскольку имеет атрибуты [JavascriptInterface]
и [Export("invokeAction")]
. В свою очередь, метод InvokeAction
вызывает метод HybridWebView.InvokeAction
, который будет вызывать зарегистрированное действие для отображения всплывающего окна.
Внимание
Проекты Android, в которых используется атрибут [Export]
, должны содержать ссылку на Mono.Android.Export
, иначе возникнет ошибка компилятора.
Обратите внимание, что класс JSBridge
поддерживает WeakReference
для класса HybridWebViewRenderer
. Это позволяет избежать создания циклической ссылки между двумя классами. Дополнительные сведения см. в разделе Слабые ссылки.
Создание пользовательского отрисовщика в UWP
В следующем примере кода показан пользовательский отрисовщик для платформы 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);
}
}
}
Класс HybridWebViewRenderer
загружает веб-страницу, указанную в свойстве HybridWebView.Uri
в собственном элементе управления WebView
, а функция JavaScript invokeCSharpAction
внедряется в веб-страницы после загрузки веб-страницы с методом WebView.InvokeScriptAsync
. Когда пользователь вводит свое имя и нажимает элемент HTML button
, выполняется функция JavaScript invokeCSharpAction
, и метод OnWebViewScriptNotify
вызывается после получения уведомления от веб-страницы. В свою очередь, этот метод вызывает метод HybridWebView.InvokeAction
, который будет вызывать зарегистрированное действие для отображения всплывающего окна.
Это достигается следующим образом:
- При условии, что пользовательский отрисовщик присоединен к новому Xamarin.Forms элементу:
- Обработчики событий для событий
NavigationCompleted
иScriptNotify
регистрируются. СобытиеNavigationCompleted
возникает, когда собственный элемент управленияWebView
завершил загрузку текущего содержимого или произошел сбой навигации. СобытиеScriptNotify
возникает, когда содержимое в собственном элементе управленияWebView
использует JavaScript для передачи строки в приложение. Веб-страница активирует событиеScriptNotify
путем вызоваwindow.external.notify
с передачей параметраstring
. - Свойство
WebView.Source
получает значение URI HTML-файла, который задается свойствомHybridWebView.Uri
. Код предполагает, что файл сохранен в папкеContent
проекта. Когда веб-страница отобразится, событиеNavigationCompleted
сработает и методOnWebViewNavigationCompleted
будет вызван. Функция JavaScriptinvokeCSharpAction
будет внедрена в веб-страницу с помощью методаWebView.InvokeScriptAsync
, если навигация успешно завершена.
- Обработчики событий для событий
- Подписка на это событие отменяется, если элемент Xamarin.Forms, к которому присоединен отрисовщик, изменяется.
- При удалении отрисовщика элемент Xamarin.Forms очищается.