利用 WebKit for SwiftUI 在 SwiftUI 中嵌入并控制 Web 内容,涵盖 WebView、WebPage、导航策略、JavaScript 执行、可观察的页面状态、链接拦截、本地 HTML 或数据加载以及自定义 URL Scheme。适用于构建 iOS 26+ 的文章/详情页视图、帮助中心、应用内文档或其它基于 HTML、CSS 和 JavaScript 的嵌入式 Web 体验。
SwiftUI WebKit
使用为 iOS 26、iPadOS 26、macOS 26 和 visionOS 26 引入的原生 WebKit-for-SwiftUI API 在 SwiftUI 中嵌入并管理 Web 内容。当应用需要集成 Web 界面、应用自带的 HTML 内容、基于 JavaScript 的页面交互或自定义导航策略控制时,即可使用此 Skill。
目录
- 选择合适的 Web 容器
- 显示 Web 内容
- 使用 WebPage 加载与监听
- 导航策略
- JavaScript 集成
- 本地内容与自定义 URL Scheme
- WebView 自定义
- 常见误区
- 审查清单
- 参考资料
选择合适的 Web 容器
根据需求选择最精准轻量级的工具。
| 需求场景 | 默认首选 |
|---|---|
| 在 SwiftUI 中嵌入应用自有的 Web 内容 | WebView + WebPage |
| iOS/iPadOS 上具备 Safari 行为的模态浏览 | SFSafariViewController |
| macOS 或 visionOS 上的跳转外部浏览器行为 | openURL / 默认浏览器 |
| OAuth 或第三方登录 | ASWebAuthenticationSession |
| 向下兼容 iOS 26 以下版本,或使用缺失的旧版 WebKit 特性 | WKWebView 回退方案 |
当针对 iOS 26+ 的现代 SwiftUI 应用且新 API 覆盖所需功能时,优先使用 WebView 和 WebPage。Apple 在 WWDC25 给出的指导建议是:可以尝试将 SwiftUI 应用中现有的 UIKit/AppKit WebKit 封装进行迁移,但并非要求盲目删除所有旧版回退代码。
切勿将嵌入式 Web View 用于 OAuth 流程。OAuth 应当始终保持使用 ASWebAuthenticationSession 流程。
显示 Web 内容
当应用只需要渲染某个 URL,且由 SwiftUI 状态驱动导航时,使用简洁的 WebView(url:) 即可。
import SwiftUI
import WebKit
struct ArticleView: View {
let url: URL
var body: some View {
WebView(url: url)
}
}
当应用需要直接加载 Request、监听状态、调用 JavaScript 或自定义导航行为时,请创建 WebPage。
一个 WebPage 实例同一时间只能与一个 WebView 关联。若有多个可见的 Web View,需要分别为其创建独立的 WebPage 实例。
@Observable
@MainActor
final class ArticleModel {
let page = WebPage()
func load(_ url: URL) async throws {
for try await _ in page.load(URLRequest(url: url)) {
}
}
}
struct ArticleDetailView: View {
@State private var model = ArticleModel()
let url: URL
var body: some View {
WebView(model.page)
.task {
try? await model.load(url)
}
}
}
完整示例请参阅 references/loading-and-observation.md。
使用 WebPage 加载与监听
WebPage 是一个标注了 @MainActor 的可观察(observable)类型。当需要在 SwiftUI 中获取页面状态时请使用它。
常用的加载入口:
load(URLRequest)load(URL)load(html:baseURL:)load(_:mimeType:characterEncoding:baseURL:)
常用的可观察属性:
titleurlisLoadingestimatedProgresscurrentNavigationEventbackForwardList
struct ReaderView: View {
@State private var page = WebPage()
var body: some View {
WebView(page)
.navigationTitle(page.title ?? "Loading")
.overlay {
if page.isLoading {
ProgressView(value: page.estimatedProgress)
}
}
.task {
do {
for try await _ in page.load(URLRequest(url: URL(string: "https://example.com")!)) {
}
} catch {
// 处理加载失败
}
}
}
}
如果需要对每一次导航做出响应,请监听导航序列(navigation sequence),而不是仅检查单一属性。
Task {
do {
for try await event in page.navigations {
// 处理 started、redirect、committed 或 finished 事件
}
} catch {
// 处理 WebPage.NavigationError 或取消操作
}
}
更稳健的模式和加载序列示例请参阅 references/loading-and-observation.md。
导航策略
使用 WebPage.NavigationDeciding 可以根据 Request 或 Response 来允许、取消或自定义导航。
典型用途:
- 将应用自有的域名保留在嵌入式 Web View 内
- 取消外部域名的导航,并交由
openURL打开 - 拦截特殊的回调 URL
- 调优
NavigationPreferences
@MainActor
final class ArticleNavigationDecider: WebPage.NavigationDeciding {
var urlToOpenExternally: URL?
func decidePolicy(
for action: WebPage.NavigationAction,
preferences: inout WebPage.NavigationPreferences
) async -> WKNavigationActionPolicy {
guard let url = action.request.url else { return .allow }
if url.host == "example.com" {
return .allow
}
urlToOpenExternally = url
return .cancel
}
}
应用层面的深度链接(deep-link)路由请保留在导航相关的 Skill 中。本 Skill 仅负责嵌入式 Web 内容内部发生的导航。
完整模式请参阅 references/navigation-and-javascript.md。
JavaScript 集成
使用 callJavaScript(_:arguments:in:contentWorld:) 对页面执行 JavaScript 函数评估。
请传入 JavaScript 函数体,而不是包裹好的函数声明或调用表达式。推荐通过 arguments 传递 Swift 提供的数值,避免将不可信的字符串拼接到脚本中。
let script = """
const headings = [...document.querySelectorAll('h1, h2')];
return headings.map(node => ({
id: node.id,
text: node.textContent?.trim()
}));
"""
let result = try await page.callJavaScript(script)
let headings = result as? [[String: Any]] ?? []
可以通过 arguments 字典传递参数,并将返回的 Any 转换为你实际需要的 Swift 类型。
let result = try await page.callJavaScript(
"return document.getElementById(sectionID)?.getBoundingClientRect().top ?? null;",
arguments: ["sectionID": selectedSectionID]
)
需要审慎处理空值和 JavaScript 的 null 结果:没有显式 return 会产生 nil,而显式的 JavaScript null 会返回 NSNull。
重要边界:原生的 SwiftUI WebKit API 明确支持 Swift 到 JavaScript 的调用,但并未提供针对 WKScriptMessageHandler 的直接替代方案。如果你需要粗粒度的 JS 到原生的信号通信,可以使用自定义导航或回调 URL 模式作为变通方案(workaround),但请将其记录为变通模式,而非保证一比一对齐的替代品。
详见 references/navigation-and-javascript.md。
本地内容与自定义 URL Scheme
当应用需要加载打包进 App 的 HTML、离线文档或通过自定义 Scheme 提供资源时,使用 WebPage.Configuration 和 URLSchemeHandler。
var configuration = WebPage.Configuration()
configuration.urlSchemeHandlers[URLScheme("docs")!] = DocsSchemeHandler(bundle: .main)
let page = WebPage(configuration: configuration)
for try await _ in page.load(URL(string: "docs://article/welcome")!) {
}
适用于:
- 应用内置的文档或文章内容
- 离线 HTML/CSS/JS 资源
- 在自定义 Scheme 下加载应用自有的资源
对于常规的远程内容,切勿滥用自定义 Scheme。服务器托管的页面请优先使用标准 HTTPS。
详见 references/local-content-and-custom-schemes.md。
WebView 自定义
使用 WebView 修饰符(modifiers)来契合预期的浏览体验。
实用的修饰符和相关 API:
webViewBackForwardNavigationGestures(_:)findNavigator(isPresented:)webViewScrollPosition(_:)webViewOnScrollGeometryChange(...)
仅在用户体验确实需要时才应用它们。
- 当用户可能会浏览多个页面时,开启前进/后退手势。
- 当内容类似于文档时,添加页内查找(Find in Page)。
- 仅当应用拥有侧边栏、目录或其它显式导航控件时,才同步滚动位置。
Apple 的 HIG(人机界面指南)在此同样适用:在合适的时候支持前进/后退导航,但不要将应用内的 Web View 变成一个通用浏览器。
常见误区
- 在 iOS 26+ SwiftUI 应用中默认使用
WKWebView封装,而不是优先从WebView和WebPage开始 - 将嵌入式 Web View 用于 OAuth,而不是使用
ASWebAuthenticationSession - 构建了纯
WebView(url:)路径后,在需要状态、JS 或导航控制时才临时补救使用WebPage - 把
callJavaScript当作WKScriptMessageHandler的直接替代品 - 向
callJavaScript传递可调用的 JS 函数封装,而不是仅传函数体 - 遍历
page.navigations时未包含try/catch,即使导航失败会抛出异常并终止序列 - 将同一个
WebPage绑定到多个可见的WebView上 - 在应当跳转到外部浏览器时,仍把所有链接保留在应用内
- 在 macOS 或 visionOS 上把
SFSafariViewController当作跨平台跳转外部浏览器的方案,而不是使用默认浏览器/openURL 行为 - 围绕 WebView 构建类似浏览器的 App 外壳,而不是聚焦于嵌入式体验
- 对本应通过 HTTPS 加载的内容使用自定义 URL Scheme
- 遗忘了
WebPage是受主线程隔离(main-actor-isolated)的
审查清单
- [ ]
WebView和WebPage是 iOS 26+ SwiftUI Web 内容的首选路径 - [ ] 认证流程使用
ASWebAuthenticationSession而非嵌入式 Web View - [ ] 只要应用需要状态监听、JS 调用或策略控制,就使用
WebPage - [ ] 导航策略仅拦截应用真正拥有或需要重定向的 URL
- [ ] 外部域名在适当时跳转到外部打开
- [ ] JavaScript 返回值防御性地转换为具体的 Swift 类型
- [ ]
callJavaScript使用函数体并通过arguments传递 Swift 数值 - [ ]
page.navigations循环使用for try await并处理抛出的导航错误 - [ ] 每个可见的
WebView(page)都拥有独立的WebPage - [ ] 自定义 URL Scheme 仅用于真正的应用自有资源
- [ ] 在预期有多页浏览时,开启前进/后退手势或控件
- [ ]
SFSafariViewController仅限 iOS/iPadOS 的 Safari 风格模态浏览;macOS 和 visionOS 的外跳流程使用平台默认浏览器行为 - [ ] Web 体验增加了聚焦的原生价值,而非表现得像一个简陋的浏览器外壳
- [ ] 使用
WKWebView回退方案有明确的部署目标版本或缺失 API 需求的合理依据
参考资料
- 加载与监听:references/loading-and-observation.md
- 导航与 JavaScript:references/navigation-and-javascript.md
- 本地内容与自定义 Scheme:references/local-content-and-custom-schemes.md
- 迁移与回退:references/migration-and-fallbacks.md




