关于 swiftui-webkit
SwiftUI WebKit 是面向开发者的指南,介绍如何在 SwiftUI 中使用 iOS 26 等平台提供的原生 WebKit API 嵌入和管理网页内容。它涵盖页面状态、导航策略、JavaScript 交互及应用自有本地内容。
主要功能
- 01SwiftUI 原生 WebView 与 WebPage
- 02页面加载和状态观察
- 03导航策略控制
- 04调用 JavaScript 与参数传递
- 05通过自定义 URL Scheme 加载本地内容
适用场景
- 01在应用内展示网页内容
- 02观察页面加载与导航状态
- 03拦截链接并打开外部域名
- 04呈现离线 HTML 文档
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swiftui-webkit安装后在 Claude Code 中输入/swiftui-webkit确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/loading-and-observation.mdlocal-content-and-custom-schemes.mdmigration-and-fallbacks.mdnavigation-and-javascript.md详细介绍
SwiftUI WebKit
使用 iOS 26、iPadOS 26、macOS 26 和 visionOS 26 推出的原生 WebKit-for-SwiftUI API,在 SwiftUI 中嵌入和管理网页内容。当应用需要集成式网页视图、应用自有的 HTML 内容、基于 JavaScript 的页面交互或自定义导航策略控制时,请使用此技能。
目录
- 选择合适的网页容器
- 显示网页内容
- [使用 WebPage 加载和观察](#使用 WebPage 加载和观察)
- 导航策略
- JavaScript 集成
- 本地内容和自定义 URL 方案
- WebView 自定义
- 常见错误
- 审核清单
- 参考资料
选择合适的网页容器
选择最符合需求的工具。
| 需求 | 默认选择 |
|---|---|
| 在 SwiftUI 中嵌入应用自有的网页内容 | 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 封装值得尝试迁移,但这并不意味着必须删除所有备用方案。
OAuth 不要使用嵌入式 WebView,而应使用 ASWebAuthenticationSession 流程。
显示网页内容
如果应用只需呈现 URL,且由 SwiftUI 状态驱动导航,请使用简单的 WebView(url:) 形式。
import SwiftUI
import WebKit
struct ArticleView: View {
let url: URL
var body: some View {
WebView(url: url)
}
}
如果应用需要直接加载请求、观察状态、调用 JavaScript 或自定义导航行为,请创建 WebPage。
WebPage 一次只能关联一个 WebView。对于多个可见的 WebView,请分别创建 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 的可观察类型。需要在 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 {
// Handle load failure.
}
}
}
}
如果需要响应每次导航,请观察导航序列,而不是只检查单个属性。
Task {
do {
for try await event in page.navigations {
// Handle started, redirect, committed, or finished events.
}
} catch {
// Handle WebPage.NavigationError or cancellation.
}
}
更完善的模式和加载序列示例,请参阅 references/loading-and-observation.md。
导航策略
使用 WebPage.NavigationDeciding,根据请求或响应来允许、取消或自定义导航。
常见用途:
- 让应用自有域名保留在嵌入式 WebView 中
- 取消对外部域名的导航,并通过
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
}
}
应用级深层链接路由请参阅导航技能。本技能负责嵌入式网页内容中的导航。
完整模式请参阅 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 结果:没有显式返回值会产生 nil,而显式返回 JavaScript null 会返回 NSNull。
重要限制:原生 SwiftUI WebKit API 明确支持从 Swift 调用 JavaScript,但没有提供 WKScriptMessageHandler 的明显直接替代方案。如果需要较粗略的 JS 到原生信号传递,自定义导航或回调 URL 模式可以奏效,但应将其记录为一种变通模式,而不是保证可一对一替代的方案。
参见 references/navigation-and-javascript.md。
本地内容和自定义 URL 方案
如果应用需要捆绑的 HTML、离线文档或通过自定义方案提供的应用资源,请使用 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 资源
- 通过自定义方案加载应用自有资源
不要在普通远程内容中滥用自定义方案。对于由服务器托管的页面,优先使用标准 HTTPS。
参见 references/local-content-and-custom-schemes.md。
WebView 自定义
使用 WebView 修饰符来匹配预期的浏览体验。
实用修饰符和相关 API:
webViewBackForwardNavigationGestures(_:)findNavigator(isPresented:)webViewScrollPosition(_:)webViewOnScrollGeometryChange(...)
仅在用户体验需要时应用这些修饰符。
- 如果用户可能会访问多个页面,请启用前进/后退手势。
- 如果内容类似文档,请添加页面内查找功能。
- 仅当应用具有侧边栏、目录或其他明确的导航方式时,才同步滚动位置。
Apple 的 HIG 同样适用于此处:在适当情况下支持前进/后退导航,但不要将应用内的 WebView 变成通用浏览器。
常见错误
- 在 iOS 26+ SwiftUI 应用中默认使用
WKWebView封装,而不是从WebView和WebPage开始 - 使用嵌入式 WebView 进行 OAuth,而不是使用
ASWebAuthenticationSession - 先采用普通的
WebView(url:)方案,等到需要状态、JS 或导航控制时才改用WebPage - 将
callJavaScript视为WKScriptMessageHandler的直接替代方案 - 向
callJavaScript传入可调用的 JavaScript 包装函数,而不是只传入函数体 - 遍历
page.navigations时不使用try/catch,尽管导航失败会以抛出异常的方式终止序列 - 将同一个
WebPage绑定到多个可见的WebView值 - 当外部域名应在嵌入界面之外打开时,仍让所有链接留在应用内
- 在 macOS 或 visionOS 上,将
SFSafariViewController视为跨平台的外部浏览方案,而不使用默认浏览器/openURL行为 - 围绕 WebView 构建浏览器风格的应用外壳,而不是打造专注的嵌入式体验
- 对本应通过 HTTPS 加载的内容使用自定义 URL 方案
- 忘记
WebPage隔离于主执行器
审查清单
- 对于 iOS 26+ 上的 SwiftUI 网页内容,默认使用
WebView和WebPage - 身份验证流程使用
ASWebAuthenticationSession,而非嵌入式网页视图 - 只要应用需要观察状态、调用 JS 或控制策略,就使用
WebPage - 导航策略只拦截应用实际拥有或需要重新路由的 URL
- 适当情况下,在外部打开外部域名
- 将 JavaScript 返回值防御性地转换为具体的 Swift 类型
-
callJavaScript使用函数体,并通过arguments传递 Swift 值 -
page.navigations循环使用for try await,并处理抛出的导航错误 - 每个可见的
WebView(page)都拥有独立的WebPage - 仅对应用实际拥有的资源使用自定义 URL 方案
- 预期支持多页面浏览时,启用前进/后退手势或控件
-
SFSafariViewController仅用于 iOS/iPadOS 上类似 Safari 的模态浏览;macOS 和 visionOS 的外部浏览流程使用平台默认浏览器行为 - 网页体验应提供有针对性的原生功能价值,而不是表现得像一个薄薄的浏览器外壳
- 仅在部署目标或缺少所需 API 有充分理由时,才回退到
WKWebView
参考资料
- 加载与观察:references/loading-and-observation.md
- 导航与 JavaScript:references/navigation-and-javascript.md
- 本地内容与自定义方案:references/local-content-and-custom-schemes.md
- 迁移与回退方案:references/migration-and-fallbacks.md