在 SwiftUI 中使用專為 SwiftUI 設計的 WebKit 內嵌並控制網頁內容,包含 WebView、WebPage、導覽策略、JavaScript 執行、可觀察的頁面狀態、連結攔截、本地 HTML 或資料載入,以及自訂 URL Scheme。適用於建構 iOS 26+ 的文章/詳情頁面、說明中心、App 內文件,或是其他由 HTML、CSS 與 JavaScript 支援的嵌入式網頁體驗。
SwiftUI WebKit
使用針對 iOS 26、iPadOS 26、macOS 26 與 visionOS 26 引入的原生 WebKit-for-SwiftUI API,在 SwiftUI 中嵌入並管理網頁內容。當 App 需要整合式網頁介面、App 專屬的 HTML 內容、基於 JavaScript 的頁面互動,或自訂導覽策略控制時,請使用此 Skill。
目錄
選擇合適的網頁容器
請選擇能滿足需求的最精準工具。
| 需求 | 預設選擇 |
|---|---|
| 在 SwiftUI 中嵌入 App 專屬網頁內容 | WebView + WebPage |
| 具備 Safari 行為的 iOS/iPadOS 彈窗瀏覽 | SFSafariViewController |
| macOS 或 visionOS 的外跳瀏覽行為 | openURL / 預設瀏覽器 |
| OAuth 或第三方登入 | ASWebAuthenticationSession |
| 相容 iOS 26 以下版本或使用缺少的舊版 WebKit 功能 | 退回使用 WKWebView |
當新 API 功能可滿足需求時,針對 iOS 26+ 的現代 SwiftUI App 應優先使用 WebView 與 WebPage。Apple 在 WWDC25 的指引中指出,SwiftUI App 中現有的 UIKit/AppKit WebKit 包裝器適合嘗試遷移,但並非要求一律刪除所有備用方案。
切勿將內嵌網頁視圖用於 OAuth。OAuth 應始終保持使用 ASWebAuthenticationSession 流程。
顯示網頁內容
當 App 只需要渲染 URL 且導覽由 SwiftUI 狀態驅動時,請使用簡短形式 WebView(url:)。
import SwiftUI
import WebKit
struct ArticleView: View {
let url: URL
var body: some View {
WebView(url: url)
}
}
當 App 需要直接載入請求、觀察狀態、呼叫 JavaScript 或自訂導覽行為時,請建立 WebPage。
一個 WebPage 一次只能與一個 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 的可觀察(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 {
// 處理載入失敗。
}
}
}
}
當需要對每次導覽做出回應時,請觀察導覽序列,而非僅檢查單一屬性。
Task {
do {
for try await event in page.navigations {
// 處理已開始(started)、轉址(redirect)、已提交(committed)或已完成(finished)事件。
}
} catch {
// 處理 WebPage.NavigationError 或取消。
}
}
請參閱 references/loading-and-observation.md 瞭解更穩健的模式與載入序列範例。
導覽策略
使用 WebPage.NavigationDeciding 根據請求或回應允許、取消或自訂導覽。
典型用途:
- 將 App 專屬網域保留在嵌入式網頁視圖內
- 取消外部網域並透過
openURL交由外部開啟 - 攔截特殊的 CallBack 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
}
}
請將 App 層級的 Deep Link 路由保留在導覽 Skill 中。本 Skill 負責處理嵌入式網頁內容內發生的導覽。
請參閱 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 到原生的訊號傳遞,可以使用自訂導覽或 CallBack URL 模式作為替代方案,但應將其標記為變通做法,而非保證一比一替代的方案。
請參閱 references/navigation-and-javascript.md。
本地內容與自訂 URL Scheme
當 App 需要在自訂 Scheme 下載入打包的 HTML、離線文件或 App 提供的資源時,請使用 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 下的 App 專屬資源載入
請勿在一般遠端內容過度使用自訂 Scheme。伺服器代管的頁面請優先使用標準 HTTPS。
請參閱 references/local-content-and-custom-schemes.md。
自訂 WebView
使用 WebView 修飾符(Modifiers)以符合預期的瀏覽體驗。
實用的修飾符與相關 API:
webViewBackForwardNavigationGestures(_:)findNavigator(isPresented:)webViewScrollPosition(_:)webViewOnScrollGeometryChange(...)
僅在使用者體驗有需要時套用:
- 當使用者有可能存取多個頁面時,啟用上一頁/下一頁手勢。
- 當內容屬於文件類型時,加入「頁面內尋找(Find in Page)」。
- 僅在 App 擁有側邊欄、目錄或其他明確的導覽輔助 UI 時,同步捲動位置。
Apple 的 HIG(人機介面指南)在此同樣適用:在適當時候支援上一頁/下一頁導覽,但切勿將 App 的網頁視圖變成通用型的瀏覽器。
常見錯誤
- 在 iOS 26+ SwiftUI App 中預設使用
WKWebView包裝器,而不是先從WebView與WebPage開始 - 將內嵌網頁視圖用於 OAuth,而不是使用
ASWebAuthenticationSession - 建構好缺乏狀態、JS 或導覽控制的純
WebView(url:)路徑後,才回頭改用WebPage - 將
callJavaScript視為WKScriptMessageHandler的直接替代方案 - 向
callJavaScript傳入可呼叫的 JavaScript 包裝函式,而非僅傳入函式主體 - 疊代
page.navigations時未加上try/catch,即使導覽失敗會拋出例外並終止序列 - 將同一個
WebPage綁定至多個可見的WebView實例 - 在外部網域應於內嵌視圖外開啟時,仍將所有連結保留在 App 內部
- 在 macOS 或 visionOS 上將
SFSafariViewController視為跨平台外跳瀏覽的解答,而不是使用預設瀏覽器/openURL 行為 - 圍繞 WebView 打造瀏覽器風格的 App 外殼,而非專注的嵌入式體驗
- 對原本應透過 HTTPS 載入的內容使用自訂 URL Scheme
- 忘記
WebPage是隔離在 Main Actor(@MainActor)上的
審查清單
- [ ]
WebView與WebPage為 iOS 26+ SwiftUI 網頁內容的預設路徑 - [ ] 認證流程使用
ASWebAuthenticationSession,而非內嵌網頁視圖 - [ ] 每當 App 需要狀態觀察、JS 呼叫或策略控制時,均使用
WebPage - [ ] 導覽策略僅攔截 App 真正擁有或需要重新路由的 URL
- [ ] 外部網域在適當時於外部開啟
- [ ] JavaScript 回傳值會防禦性地轉型為具體的 Swift 類型
- [ ]
callJavaScript使用函式主體並透過arguments傳遞 Swift 數值 - [ ]
page.navigations迴圈使用for try await並處理拋出的導覽錯誤 - [ ] 每個可見的
WebView(page)擁有獨立的WebPage - [ ] 自訂 URL Scheme 僅用於真正的 App 專屬資源
- [ ] 預期會有多頁面瀏覽時,已啟用上一頁/下一頁手勢或控制項
- [ ]
SFSafariViewController僅限於 iOS/iPadOS 上 Safari 風格的彈窗瀏覽;macOS 與 visionOS 的外跳流程使用平台預設瀏覽器行為 - [ ] 網頁體驗提供專注的原生價值,而非像個薄弱的瀏覽器外殼
- [ ] 退回使用
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




