Xamarin WebView

Автор:

github.com

Xamarin WebView – это представление для отображения веб- и HTML-контента в вашем приложении. Как это выглядит на примере различных платформ:

Содержимое (Content)

Xamarin.Forms WebView поддерживает следующие типы содержимого:

  • Веб-сайты HTML и CSS – WebView имеет полную поддержку веб-сайтов, написанных с использованием HTML и CSS, включая поддержку JavaScript.
  • Документы – поскольку WebView реализован с использованием собственных компонентов на каждой платформе, WebView способен показывать документы в форматах, поддерживаемых базовой платформой.
  • Строки HTML – WebView может отображать строки HTML из памяти.
  • Локальные файлы – WebView может отображать любой из перечисленных выше типов содержимого, встроенного в приложение.

Примечание! WebView на Windows не поддерживает Silverlight, Flash или любые элементы управления ActiveX, даже если они поддерживаются Internet Explorer на этой платформе.

Веб-сайты (Websites)

Чтобы отобразить веб-сайт из Интернета, установите свойство Source WebView в строку URL:

var browser = new WebView
{
 Source = "https://dotnet.microsoft.com/apps/xamarin"
};

Примечание! URL-адреса должны быть полностью сформированы с указанием протокола (т.е. к ним должны быть добавлены "http://" или "https://").

iOS и ATS

Начиная с версии 9, iOS по умолчанию позволяет вашему приложению взаимодействовать только с теми серверами, которые реализуют наилучшие методы обеспечения безопасности. Чтобы разрешить связь с небезопасными серверами, необходимо установить значения в Info.plist.

Примечание! Если вашему приложению требуется соединение с небезопасным веб-сайтом, вы всегда должны вводить домен как исключение с помощью NSExceptionDomains вместо полного отключения ATS с помощью NSAllowsArbitraryLoads. NSAllowsArbitraryLoads следует использовать только в чрезвычайных ситуациях.

Ниже показано, как включить определенный домен (в данном случае xamarin.com), чтобы обойти требования ATS:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>xamarin.com</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSTemporaryExceptionMinimumTLSVersion</key>
                <string>TLSv1.1</string>
            </dict>
        </dict>
    </dict>
    ...
</key>

Лучше всего разрешить обход ATS только для некоторых доменов, что позволит вам использовать доверенные сайты, получая преимущества от дополнительной безопасности на ненадежных доменах. Ниже показан менее безопасный метод отключения ATS для приложения:

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

Строки HTML (HTML Strings)

Если вы хотите динамически представить строку HTML, заданную в коде, вам нужно создать экземпляр HtmlWebViewSource:

var browser = new WebView();
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = @"
Xamarin.Forms
Welcome to WebView.
";
browser.Source = htmlSource;

В приведенном выше коде символ @ используется для обозначения HTML как дословная строковая константа, что означает, что большинство управляющих символов игнорируется.

Примечание! Может потребоваться установить свойства WidthRequest и HeightRequest для WebView, чтобы увидеть содержимое HTML, в зависимости от макета, дочерним элементом которого является WebView. Например, это требуется в макете StackLayout.

Локальное содержимое HTML

WebView может отображать содержимое HTML, CSS и JavaScript, встроенных в приложение. Например:

<html>
  <head>
    <title>Xamarin Forms</title>
  </head>
  <body>
    <h1>Xamarin.Forms</h1>
    <p>This is an iOS web page.</p>
    <img src="XamarinLogo.png" />
  </body>
</html>

CSS:

html,body {
  margin:0;
  padding:10;
}
body,p,h1 {
  font-family: Chalkduster;
}

Следует учесть тот факт, что шрифты, приведенные выше в CSS, необходимо будет настроить для каждой платформы, поскольку не на всех платформах используются одинаковые шрифты.

Для отображения локального содержимого с помощью WebView, необходимо открыть HTML-файл, как любой другой, а затем загрузить содержимое в виде строки в свойство Html источника HtmlWebViewSource.

На следующих примерах демонстрируется результат отображения локального содержимого для каждой платформы:

Мы видим, что, хотя первая страница загружена, WebView не знает, откуда пришел HTML. Это является проблемой при работе со страницами, которые ссылаются на локальные ресурсы. Примеры, когда это может произойти: локальные страницы ссылаются друг на друга, страница использует отдельный файл JavaScript или страница ссылается на таблицу стилей CSS.

Для решения этой проблемы необходимо указать WebView, где найти файлы в файловой системе. Для этого установите свойство BaseUrl в HtmlWebViewSource, используемом WebView.

Поскольку файловая система на каждой из операционных систем отличается, вам нужно определить этот URL на каждой платформе. Xamarin.Forms предоставляет DependencyService для разрешения зависимостей во время выполнения на каждой платформе.

Для использования DependencyService, необходимо определить интерфейс, который может быть реализован на каждой платформе:

public interface IBaseUrl { string Get(); }

Обратите внимание, что пока интерфейс не будет реализован на каждой платформе, приложение не будет работать. В общем проекте убедитесь, что вы не забыли установить BaseUrl с помощью DependencyService:

var source = new HtmlWebViewSource();
source.BaseUrl = DependencyService.Get().Get();

Затем необходимо предоставить реализацию интерфейса для каждой платформы.

iOS

На iOS веб-контент должен быть расположен в корневом каталоге проекта или в каталоге Resources с действием сборки BundleResource, как показано ниже:

Visual Studio

Visual Studio for Mac

Необходимо BaseUrl задать путь к основному пакету:

[assembly: Dependency (typeof (BaseUrl_iOS))]
namespace WorkingWithWebview.iOS
{
  public class BaseUrl_iOS : IBaseUrl
  {
    public string Get()
    {
      return NSBundle.MainBundle.BundlePath;
    }
  }
}

Android

Для Android необходимо поместить HTML, CSS и изображения в папку Assets с действием сборки AndroidAsset, как показано ниже:

Visual Studio

Visual Studio for Mac

В Android для BaseUrl должен быть установлен "file:///android_asset/":

[assembly: Dependency (typeof(BaseUrl_Android))]
namespace WorkingWithWebview.Android
{
  public class BaseUrl_Android : IBaseUrl
  {
    public string Get()
    {
      return "file:///android_asset/";
    }
  }
}

В Android получить доступ к файлам в папке Assets также можно через текущий контекст Android, который отображается свойством MainActivity.Instance:

var assetManager = MainActivity.Instance.Assets;
using (var streamReader = new StreamReader (assetManager.Open ("local.html")))
{
  var html = streamReader.ReadToEnd ();
}

Универсальная платформа Windows

В проектах Universal Windows Platform (UWP) поместите HTML, CSS и изображения в корень проекта с действием сборки, установленным на Content.

Для BaseUrl необходимо задать значение "ms-appx-web:///":

[assembly: Dependency(typeof(BaseUrl))]
namespace WorkingWithWebview.UWP
{
    public class BaseUrl : IBaseUrl
    {
        public string Get()
        {
            return "ms-appx-web:///";
        }
    }
}

Навигация (Navigation)

WebView поддерживает навигацию с помощью нескольких методов и свойств:

  • GoForward() – если CanGoForward равен true, вызов GoForward переводит вперед на следующую посещенную страницу.
  • GoBack() – если CanGoBack равен true, вызов GoBack переводит на последнюю посещенную страницу.
  • CanGoBack – true, если есть страницы для перехода назад, false, если браузер находится на начальном URL.
  • CanGoForward – true, если пользователь перешел назад и может перейти вперед на уже посещенную страницу.

Внутри страниц WebView не поддерживает мультисенсорные жесты. Важно убедиться, что содержимое оптимизировано для мобильных устройств и отображается без необходимости масштабирования.

Обычно приложения показывают ссылку в WebView, а не в браузере устройства. В таких ситуациях полезно разрешить обычную навигацию, но, когда пользователь нажимает кнопку «Назад» находясь на начальной ссылке, приложение должно вернуться к обычному виду приложения.

Для реализации этого сценария используйте встроенные методы и свойства навигации.

Начните с создания страницы для просмотра в браузере:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="WebViewSample.InAppBrowserXaml"
             Title="Browser">
    <StackLayout Margin="20">
        <StackLayout Orientation="Horizontal">
            <Button Text="Back" HorizontalOptions="StartAndExpand" Clicked="OnBackButtonClicked" />
            <Button Text="Forward" HorizontalOptions="EndAndExpand" Clicked="OnForwardButtonClicked" />
        </StackLayout>
        <!-- WebView needs to be given height and width request within layouts to render. -->
        <WebView x:Name="webView" WidthRequest="1000" HeightRequest="1000" />
    </StackLayout>
</ContentPage>

В коде программной части:

public partial class InAppBrowserXaml : ContentPage
{
    public InAppBrowserXaml(string URL)
    {
        InitializeComponent();
        webView.Source = URL;
    }
 
    async void OnBackButtonClicked(object sender, EventArgs e)
    {
        if (webView.CanGoBack)
        {
            webView.GoBack();
        }
        else
        {
            await Navigation.PopAsync();
        }
    }
 
    void OnForwardButtonClicked(object sender, EventArgs e)
    {
        if (webView.CanGoForward)
        {
            webView.GoForward();
        }
    }
}

Получается

События (Events)

  • WebView вызывает следующие события, чтобы помочь вам реагировать на изменения:
  • Navigating – событие, возникающее, когда WebView начинает загрузку новой страницы.
  • Navigated – событие, возникающее, когда страница загружена, а навигация остановлена.
  • ReloadRequested – событие, возникающее при поступлении запроса на перезагрузку текущего содержимого.
  • Объект WebNavigatingEventArgs, сопровождающий событие Navigating, имеет четыре свойства:
  • Cancel – указывает, следует ли отменить навигацию.
  • NavigationEvent – событие навигации, которое было вызвано.
  • Source – элемент, который выполнил навигацию.
  • Url – место назначения навигации.

Объект WebNavigatedEventArgs, сопровождающий событие Navigated, имеет четыре свойства:

  • NavigationEvent – событие навигации, которое было вызвано.
  • Result – описывает результат навигации, используя член перечисления WebNavigationResult. Допустимые значения: Cancel (отмена), Failure (отказ), Success (успех) и Timeout (таймаут).
  • Источник – элемент, который выполнил навигацию.
  • Url – место назначения навигации.

Если вы планируете использовать веб-страницы, загрузка которых занимает много времени, рассмотрите возможность использования событий Navigating и Navigated для реализации индикатора состояния. Например:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="WebViewSample.LoadingLabelXaml"
             Title="Loading Demo">
    <StackLayout>
        <!--Loading label should not render by default.-->
        <Label x:Name="labelLoading" Text="Loading..." IsVisible="false" />
        <WebView HeightRequest="1000" WidthRequest="1000" Source="https://dotnet.microsoft.com/apps/xamarin" Navigated="webviewNavigated" Navigating="webviewNavigating" />
    </StackLayout>
</ContentPage>

Два обработчика событий:

void webviewNavigating(object sender, WebNavigatingEventArgs e)
{
    labelLoading.IsVisible = true;
}
 
void webviewNavigated(object sender, WebNavigatedEventArgs e)
{
    labelLoading.IsVisible = false;
}

Это приводит к следующему результату (загрузка):

Готовая загрузка:

Перезагрузка содержимого (Reloading content)

WebView имеет метод Reload, который можно использовать для перезагрузки текущего содержимого:

var webView = new WebView();
...
webView.Reload();

Когда вызывается метод Reload, происходит событие ReloadRequested, указывающее на то, что был сделан запрос на перезагрузку текущего содержимого.

Производительность (Performance)

Популярные веб-браузеры используют такие технологии, как аппаратное ускорение рендеринга и компиляция JavaScript. До Xamarin.Forms 4.4 Xamarin.Forms WebView был реализован на iOS с помощью класса UIWebView. Однако многие из этих технологий были недоступны в данной реализации. Поэтому, начиная с Xamarin.Forms 4.4, Xamarin.Forms WebView реализован на iOS классом WkWebView, который поддерживает более быстрый просмотр.

Примечание! На iOS у WkWebViewRenderer есть перегрузка конструктора, который принимает аргумент WkWebViewConfiguration. Это позволяет конфигурировать рендерер при создании.

Приложение может вернуться к использованию класса iOS UIWebView для реализации Xamarin.Forms WebView по причинам совместимости. Этого можно добиться, добавив следующий код в файл AssemblyInfo.cs в проекте приложения для платформы iOS:

// Отказ от использования UIWebView вместо WkWebView
 
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(Xamarin.Forms.Platform.iOS.WebViewRenderer))]
 
Примечание! В Xamarin.Forms 5.0 класс WebViewRenderer был удален. Поэтому Xamarin.Forms 5.0 не содержит ссылки на элемент управления UIWebView.
 

WebView на Android по умолчанию работает примерно так же быстро, как встроенный браузер.

UWP WebView использует движок рендеринга Microsoft Edge. На настольных и планшетных устройствах производительность должна быть такой же, как при использовании самого браузера Edge.

Разрешения (Permissions)

Для того, чтобы WebView работал, необходимо убедиться, что разрешения установлены для каждой платформы. Обратите внимание, что на некоторых платформах WebView будет работать в режиме отладки, но не при сборке для релиза. Это связано с тем, что некоторые разрешения, например, для доступа в интернет на Android, по умолчанию устанавливаются Visual Studio для Mac в режиме отладки.

  • UWP – требует разрешения Internet (Client & Server) при отображении сетевого содержимого.
  • Android – требует INTERNET только при отображении содержимого из сети. Локальное содержимое не требует специальных разрешений.
  • iOS – не требует специальных разрешений.

Макет (Layout)

В отличие от большинства других представлений Xamarin.Forms, WebView требует указания HeightRequest и WidthRequest, если содержится в StackLayout или RelativeLayout. Если вы не укажете эти свойства, WebView не будет отображаться.

В следующих примерах показаны макеты, которые приводят к работе и рендерингу WebView:

StackLayout с WidthRequest и HeightRequest:

<StackLayout>
    <Label Text="test" />
    <WebView Source="https://dotnet.microsoft.com/apps/xamarin"
        HeightRequest="1000"
        WidthRequest="1000" />
</StackLayout>
 

RelativeLayout с WidthRequest и HeightRequest:

<RelativeLayout>
    <Label Text="test"
        RelativeLayout.XConstraint= "{ConstraintExpression
                                      Type=Constant, Constant=10}"
        RelativeLayout.YConstraint= "{ConstraintExpression
                                      Type=Constant, Constant=20}" />
    <WebView Source="https://dotnet.microsoft.com/apps/xamarin"
        RelativeLayout.XConstraint="{ConstraintExpression Type=Constant,
                                     Constant=10}"
        RelativeLayout.YConstraint="{ConstraintExpression Type=Constant,
                                     Constant=50}"
        WidthRequest="1000" HeightRequest="1000" />
</RelativeLayout>
 

AbsoluteLayout без WidthRequest & HeightRequest:

<AbsoluteLayout>
    <Label Text="test" AbsoluteLayout.LayoutBounds="0,0,100,100" />
    <WebView Source="https://dotnet.microsoft.com/apps/xamarin"
      AbsoluteLayout.LayoutBounds="0,150,500,500" />
</AbsoluteLayout>

Grid без WidthRequest и HeightRequest. Grid – это один из немногих макетов, который не требует указания запрашиваемых высот и ширин:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="100" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Label Text="test" Grid.Row="0" />
    <WebView Source="https://dotnet.microsoft.com/apps/xamarin" Grid.Row="1" />
</Grid>

Вызов JavaScript (Invoking JavaScript)

WebView включает возможность вызывать функцию JavaScript из C# и возвращать любой результат вызывающему коду C#. Это достигается с помощью метода WebView.EvaluateJavaScriptAsync, который показан в следующем примере:

var numberEntry = new Entry { Text = "5" };
var resultLabel = new Label();
var webView = new WebView();
...
 
int number = int.Parse(numberEntry.Text);
string result = await webView.EvaluateJavaScriptAsync($"factorial({number})");
resultLabel.Text = $"Factorial of {number} is {result}.";

Метод WebView.EvaluateJavaScriptAsync оценивает JavaScript, указанный в качестве аргумента, и возвращает любой результат в виде строки. В этом примере вызывается функция factorial JavaScript, которая возвращает факториал числа в качестве результата. Эта функция JavaScript определена в локальном HTML-файле, который загружает WebView, и показана в следующем примере:

<html>
<body>
<script type="text/javascript">
function factorial(num) {
        if (num === 0 || num === 1)
            return 1;
        for (var i = num - 1; i >= 1; i--) {
            num *= i;
        }
        return num;
}
</script>
</body>
</html>

Cookies

На WebView можно установить Cookies, которые затем отправляются вместе с веб-запросом на указанный URL. Это достигается путем добавления объектов Cookie в CookieContainer, который затем устанавливается в качестве значения связываемого свойства WebView.Cookies. В следующем коде показан пример:

using System.Net;
using Xamarin.Forms;
// ...
 
CookieContainer cookieContainer = new CookieContainer();
Uri uri = new Uri("https://dotnet.microsoft.com/apps/xamarin", UriKind.RelativeOrAbsolute);
 
Cookie cookie = new Cookie
{
    Name = "XamarinCookie",
    Expires = DateTime.Now.AddDays(1),
    Value = "My cookie",
    Domain = uri.Host,
    Path = "/"
};
cookieContainer.Add(uri, cookie);
webView.Cookies = cookieContainer;
webView.Source = new UrlWebViewSource { Url = uri.ToString() };

В этом примере в объект CookieContainer добавляется один Cookie, который затем устанавливается в качестве значения свойства WebView.Cookies. Когда WebView отправляет веб-запрос на указанный URL, cookie отправляется вместе с запросом.

Устаревший UIWebView и отказ от App Store (ITMS-90809)

Начиная с апреля 2020 года, Apple будет отклонять приложения, которые все еще используют устаревший API UIWebView. Хотя Xamarin.Forms перешел на WKWebView по умолчанию, в двоичных файлах Xamarin.Forms все еще есть ссылки на старый SDK. Текущее поведение компоновщика iOS не устраняет это, и в результате устаревший UIWebView API будет по-прежнему ссылаться на ваше приложение при отправке в App Store.

Важно! В Xamarin.Forms 5.0 класс WebViewRenderer был удален. Поэтому Xamarin.Forms 5.0 не содержит ссылки на элемент управления UIWebView.

Для устранения этой проблемы доступна предварительная версия компоновщика. Чтобы ее включить, необходимо задать компоновщику дополнительный аргумент --optimize=experimental-xforms-product-type.

Необходимыми требования:

  • Xamarin.Forms 4.5 или выше. Xamarin.Forms 4.6 или выше требуется, если ваше приложение использует Material Visual.
  • Xamarin.iOS 13.10.0.17 или выше. Проверьте версию Xamarin.iOS в Visual Studio. Эта версия Xamarin.iOS включена в Visual Studio for Mac 8.4.1 и Visual Studio 16.4.3.
  • Удаление ссылок на UIWebView. В вашем коде не должно быть ссылок на UIWebView или классы, которые используют UIWebView.

Настройка компоновщика

Visual Studio

Выполните следующие действия, чтобы компоновщик удалил ссылки на UIWebView:

  • Откройте свойства проекта iOS - Щелкните правой кнопкой мыши проект iOS и выберите Properties.
  • Перейдите к разделу iOS Build - Выберите раздел iOS Build.
  • Обновите дополнительные аргументы mtouch - В дополнительных аргументах mtouch добавьте этот флаг --optimize=experimental-xforms-product-type (в дополнение к любому значению, которое там уже есть). Примечание: этот флаг работает вместе с параметром Linker Behavior, установленным в SDK Only или Link All. Если по какой-либо причине вы видите ошибки при установке Linker Behavior в значение All, это, скорее всего, проблема в коде приложения или в сторонней библиотеке, которая не является безопасной для компоновщика. Для получения дополнительной информации о компоновщике см. раздел Компоновка приложений Xamarin.iOS.
  • Обновление всех конфигураций сборки - Используйте списки Configuration и Platform в верхней части окна для обновления всех конфигураций сборки. Наиболее важной конфигурацией для обновления является конфигурация Release/iPhone, поскольку она обычно используется для создания сборок для отправки в App Store.

На этом снимке экрана вы можете видеть окно с новым флагом:

Visual Studio for Mac

Выполните следующие шаги, чтобы компоновщик удалил ссылки UIWebView:

  • Откройте опции проекта iOS - Щелкните правой кнопкой мыши проект iOS и выберите Options.
  • Перейдите в раздел iOS Build - Выберите раздел iOS Build.
  • Обновите дополнительные аргументы mtouch - В дополнительных аргументах mtouch добавьте этот флаг --optimize=experimental-xforms-product-type (в дополнение к любому значению, которое там уже есть). Примечание: этот флаг работает вместе с параметром Linker Behavior, установленным в SDK Only или Link All. Если по какой-либо причине вы видите ошибки при установке Linker Behavior в значение All, это, скорее всего, проблема в коде приложения или в сторонней библиотеке, которая не является безопасной для компоновщика. Для получения дополнительной информации о компоновщике см. раздел Компоновка приложений Xamarin.iOS.
  • Обновление всех конфигураций сборки - Используйте списки Configuration и Platform в верхней части окна для обновления всех конфигураций сборки. Наиболее важной конфигурацией для обновления является конфигурация Release/iPhone, поскольку она обычно используется для создания сборок для отправки в App Store.

На этом снимке экрана вы можете видеть окно с новым флагом:

Оглавление

Материалы по теме