Xamarin.Forms WebView

WebView 是用于在应用中显示 Web 和 HTML 内容的视图:

在应用浏览器中

内容

WebView 支持以下类型的内容:

  • HTML 和 CSS 网站 – WebView 完全支持使用 HTML 和 CSS 编写的网站,包括 JavaScript 支持。
  • 文档 – 由于 WebView 是使用每个平台上的本机组件实现的,因此 WebView 能够以基础平台支持的格式显示文档。
  • HTML 字符串 – WebView 可以显示内存中的 HTML 字符串。
  • 本地文件 – WebView 可以呈现应用中嵌入的上述任何内容类型。

注意

Windows 上的 WebView 不支持 Silverlight、Flash 或任何 ActiveX 控件,即使该平台上的 Internet Explorer 支持这些控件。

网站

若要显示 Internet 上的网站,请将 WebViewSource 属性设置为字符串 URL:

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

注意

URL 必须完全按照指定的协议构成(即,必须在其前面添加“http://”或“https://”)。

iOS 和 ATS

从版本 9 开始,iOS 仅允许应用程序与默认实施最佳安全实践的服务器通信。 必须在 Info.plist 中设置值才能实现与不安全服务器的通信。

注意

如果你的应用程序需要连接到不安全的网站,则应始终使用 NSExceptionDomains 输入域作为例外,而不要使用 NSAllowsArbitraryLoads 完全关闭 ATS。 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>

有关 iOS 9 中此新功能的详细信息,请参阅应用传输安全性

HTML 字符串

如果你要呈现代码中动态定义的 HTML 字符串,则需要创建 HtmlWebViewSource 的实例:

var browser = new WebView();
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = @"<html><body>
  <h1>Xamarin.Forms</h1>
  <p>Welcome to WebView.</p>
  </body></html>";
browser.Source = htmlSource;

显示 HTML 字符串的 WebView

在上面的代码中,@ 用于将 HTML 标记为逐字字符串字面量,这意味着大多数转义字符都会被忽略。

注意

可能需要设置 WebViewWidthRequestHeightRequest 属性才能查看 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 文件,然后将内容作为字符串加载到 HtmlWebViewSourceHtml 属性中。 有关打开文件的详细信息,请参阅处理文件

以下屏幕截图显示了在每个平台上显示本地内容的结果:

显示本地内容的 WebView

尽管第一个页面已加载,但 WebView 并不知道 HTML 来自何处。 在处理引用本地资源的页面时,这是一个问题。 可能发生这种情况的示例包括本地页面相互链接、页面使用单独的 JavaScript 文件或页面链接到 CSS 样式表。

若要解决此问题,需要告知 WebView 在文件系统上的何处查找文件。 为此,请在 WebView 使用的 HtmlWebViewSource 上设置 BaseUrl 属性。

由于每个操作系统上的文件系统不同,因此需要确定每个平台上的 URL。 Xamarin.Forms 公开 DependencyService,用于在每个平台上运行时解析依赖关系。

若要使用 DependencyService,首先请定义一个可以在每个平台上实现的接口:

public interface IBaseUrl { string Get(); }

请注意,在每个平台上实现该接口之前,应用不会运行。 在通用项目中,请务必记得使用 DependencyService 设置 BaseUrl

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

然后必须提供每个平台的接口实现。

iOS

在 iOS 上,应使用生成操作 BundleResource 将 Web 内容放置在项目根目录或 Resources 目录中,如下所示

BaseUrl 应设置为主捆绑包的路径:

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

Android

在 Android 上,使用生成操作 AndroidAsset 将 HTML、CSS 和图像放入 Assets 文件夹中,如下所示

在 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 上,还可以通过 MainActivity.Instance 属性公开的当前 Android 上下文来访问 Assets 文件夹中的文件

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

通用 Windows 平台

在通用 Windows 平台 (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:///";
        }
    }
}

WebView 支持通过它提供的多种方法和属性进行导航:

  • GoForward() – 如果 CanGoForward 为 true,则调用 GoForward 将向前导航到下一个访问的页面。
  • GoBack() – 如果 CanGoBack 为 true,则调用 GoBack 将导航到最后一个访问的页面。
  • CanGoBack – 如果存在可导航回到的页面,则为 true;如果浏览器位于起始 URL,则为 false
  • 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();
        }
    }
}

就这么简单!

WebView 导航按钮

事件

WebView 会引发以下事件来帮助你响应状态更改:

  • Navigating – WebView 开始加载新页面时引发的事件。
  • Navigated – 页面已加载且导航已停止时引发的事件。
  • ReloadRequested – 发出重新加载当前内容的请求时引发的事件。

附带 Navigating 事件的 WebNavigatingEventArgs 对象有四个属性:

  • Cancel – 指示是否取消导航。
  • NavigationEvent – 引发的导航事件。
  • Source – 执行导航的元素。
  • Url – 导航目标。

附带 Navigated 事件的 WebNavigatedEventArgs 对象有四个属性:

  • NavigationEvent – 引发的导航事件。
  • Result – 使用 WebNavigationResult 枚举成员描述导航结果。 有效值为 CancelFailureSuccessTimeout
  • Source – 执行导航的元素。
  • Url – 导航目标。

如果你预期使用的网页需要很长时间才能加载,请考虑使用 NavigatingNavigated 事件来实现状态指示器。 例如:

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

这会生成以下输出(正在加载):

屏幕截图显示了加载时的 WebView 导航事件。

已完成加载:

屏幕截图显示了加载后的 WebView 导航事件。

正在重新加载内容

WebView 有一个可用于重新加载当前内容的 Reload 方法:

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

调用 Reload 方法时,将触发 ReloadRequested 事件,指示已发出重新加载当前内容的请求。

性能

流行 Web 浏览器采用硬件加速渲染和 JavaScript 编译等技术。 在 Xamarin.Forms 4.4 之前,Xamarin.FormsWebView 在 iOS 上由 UIWebView 类实现。 但是,其中许多技术在此实现中不可用。 因此,从 Xamarin.Forms 4.4 开始,Xamarin.FormsWebView 在 iOS 上由 WkWebView 类实现,该类支持更快的浏览。

注意

在 iOS 上,WkWebViewRenderer 具有接受 WkWebViewConfiguration 参数的构造函数重载。 这样,可以在创建时配置渲染器。

出于兼容性原因,应用程序可以重新使用 iOS UIWebView 类来实现 Xamarin.FormsWebView。 为此,可将以下代码添加到应用程序的 iOS 平台项目中的 AssemblyInfo.cs 文件

// Opt-in to using UIWebView instead of WkWebView.
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(Xamarin.Forms.Platform.iOS.WebViewRenderer))]

注意

在 Xamarin.Forms 5.0 中,已删除 WebViewRenderer 类。 因此,Xamarin.Forms 5.0 不包含对 UIWebView 控件的引用。

默认情况下,Android 上的 WebView 与内置浏览器的速度差不多。

UWP WebView 使用 Microsoft Edge 渲染引擎。 桌面和平板电脑设备应该具有与使用 Edge 浏览器本身相同的性能。

权限

为使 WebView 正常工作,必须确保为每个平台设置权限。 请注意,在某些平台上,WebView 将在调试模式下工作,但在为发布而生成时则无法工作。 这是因为某些权限(例如 Android 上的 Internet 访问权限)是在调试模式下由 Visual Studio for Mac 默认设置的。

  • UWP – 显示网络内容时需要 Internet(客户端和服务器)功能
  • Android – 仅当显示来自网络的内容时才需要 INTERNET。 本地内容不需要特殊权限。
  • iOS – 不需要特殊权限

Layout

与大多数其他 Xamarin.Forms 视图不同,WebView 要求在 StackLayout 或 relativelayout 中包含时指定 HeightRequestWidthRequest。 如果无法指定这些属性,则 WebView 不会渲染。

以下示例演示了可以正常渲染 WebView 的布局:

具有 WidthRequest 和 HeightRequest 的 StackLayout:

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

具有 WidthRequest 和 HeightRequest 的 RelativeLayout:

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

没有 WidthRequest 和 HeightRequest 的 AbsoluteLayout

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

没有 WidthRequest 和 HeightRequest 的网格。 网格是少数不需要指定请求高度和宽度的布局之一:

<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

WebView 包括从 C# 调用 JavaScript 函数并将任何结果返回到调用 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,并返回任何结果作为 string。 在此示例中,将调用 factorial JavaScript 函数,该函数将返回 number 的阶乘作为结果。 此 JavaScript 函数在 WebView 加载的本地 HTML 文件中定义,如以下示例所示:

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

Cookie

可以在 WebView 上设置 Cookie,然后将其与 Web 请求一起发送到指定的 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 发送 Web 请求时,将随请求一起发送 Cookie。

UIWebView 弃用和 App Store 拒绝 (ITMS-90809)

从 2020 年 4 月开始,Apple 将拒绝仍在使用已弃用 UIWebView API 的应用。 虽然 Xamarin.Forms 已改用 WKWebView 作为默认设置,但 Xamarin.Forms 二进制文件中仍然引用了旧版 SDK。 当前的 iOS 链接器行为不会消除此问题,因此,当提交到 App Store 时,应用仍会引用已弃用的 UIWebView API。

重要

在 Xamarin.Forms 5.0 中,已删除 WebViewRenderer 类。 因此,Xamarin.Forms 5.0 不包含对 UIWebView 控件的引用。

可以使用链接器预览版来修复此问题。 若要启用预览版,需要向链接器提供附加参数 --optimize=experimental-xforms-product-type

此操作的先决条件包括:

  • Xamarin.Forms 4.5 或更高版本。 如果你的应用使用 Material Visual,则需要 Xamarin.Forms 4.6 或更高版本。
  • Xamarin.iOS 13.10.0.17 或更高版本在 Visual Studio 中检查你的 Xamarin.iOS 版本。 此版本的 Xamarin.iOS 包含在 Visual Studio for Mac 8.4.1 和 Visual Studio 16.4.3 中。
  • 删除对 UIWebView 的引用。 代码不应引用任何 UIWebView 或任何使用 UIWebView 的类。

有关检测和删除 UIWebView 引用的详细信息,请参阅 UIWebView 弃用

配置链接器

请按照链接器的以下步骤删除 UIWebView 引用:

  1. 打开 iOS 项目属性 – 右键单击 iOS 项目并选择“属性”
  2. 导航到“iOS 生成”部分 – 选择“iOS 生成”部分
  3. 更新其他 mtouch 参数 – 在“其他 mtouch 参数”中添加此标志 --optimize=experimental-xforms-product-type(以及其中可能已存在的任何值)。 注意:此标志与设置为“仅限 SDK”或“链接所有”的“链接器行为”结合使用。 如果出于任何原因,在将“链接器行为”设置为“全部”时出现错误,则很可能是应用代码或链接器不安全的第三方库中存在问题。 有关链接器的详细信息,请参阅链接 Xamarin.iOS 应用
  4. 更新所有生成配置 – 使用窗口顶部的“配置”和“平台”列表来更新所有生成配置。 要更新的最重要配置是 Release/iPhone 配置,因为它通常用于创建所需的版本进行 App Store 提交

可以在此屏幕截图中看到包含新标志的窗口:

在“iOS 生成”部分中设置标志

现在,当你创建新的(发布)版本并将其提交到 App Store 时,应该不会出现有关已弃用 API 的警告。