发布于 2026-01-12
在 SwiftUI 中封装 WKWebView,构建一个可用于 Hybrid 应用的最小架构,包括 JS → Native 通信、Promise 风格的 JSBridge 实现、安全区处理以及 WKWebView 的调试方法。
将 UIKit 的 WKWebView 包装成一个 SwiftUI View,并通过 Coordinator 建立 JS → Native 的通信桥梁。
WKWebView 是 UIKit 组件,SwiftUI 不能直接使用 UIKit View,必须通过 UIViewRepresentable 进行桥接。
WKWebView,负责一次性初始化(config、delegate、load)import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
let config = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = context.coordinator
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
在创建 WKWebView 时,通过 WKWebViewConfiguration 注册 JS 调用点,"native" 是 JS 调用的通道名:
let contentController = WKUserContentController()
contentController.add(context.coordinator, name: "native")
config.userContentController = contentController
对应 JS 侧调用方式:
window.webkit.messageHandlers.native.postMessage(data)
在 Coordinator 实现 WKScriptMessageHandler 协议,接收 JS 消息:
message.name:通道名(对应注册的 "native")message.body:JS 传入的数据(可为字符串 / 对象 / 数组等)class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
if message.name == "native" {
print("JS message:", message.body)
}
}
}
跟其他 View 组件用法类似, url 传入网址:
WebView(
url: URL(string: "https://example.com")!
)
.ignoresSafeArea()
ignoresSafeArea 是 SwiftUI 的布局修饰符,告诉 SwiftUI 这个 View 是“无视系统安全区域 Safe Area ”,即让 Web 容器覆盖到这些区域:
Safe Area 由 Web 使用 CSS env 变量自行处理:
html, body {
height: 100%;
}
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
env(safe-area-inset-*) 是 Web 标准(CSS), 但数值是由原生 WebView / 浏览器运行环境注入的。
XCode 的控制台只能查看原生的 print 输出。
通过 Mac Safari 的 Develop → Web Inspector 查看。
WKWebView 需开启调试;
webView.isInspectable = true
然后打开 Mac Safari 的开发菜单,在开发菜单找到模拟器,点击WebView 页面 URL,即可打开控制台,直接可用,不需要额外设置。
通过 JSON 格式通信:
JS → Native
{
"id": "uuid",
"type": "getDeviceInfo",
"payload": {}
}
Native → JS
{
"id": "uuid",
"success": true,
"data": {...}
}
把 webView 实例绑定到 Coordinator 中:
context.coordinator.bind(webView: webView)
解析请求的格式
class Coordinator: NSObject, WKScriptMessageHandler {
weak var webView: WKWebView?
func bind(webView: WKWebView) {
self.webView = webView
}
}
解析请求的格式:
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard
let body = message.body as? [String: Any],
let id = body["id"] as? String,
let type = body["type"] as? String
else { return }
handle(type: type, id: id, payload: body["payload"])
}
处理不同 Bridge 类型:
func handle(type: String, id: String, payload: Any?) {
switch type {
case "getDeviceInfo":
let data: [String: Any] = [
"platform": "iOS",
"version": UIDevice.current.systemVersion
]
respond(id: id, success: true, data: data)
default:
respond(id: id, success: false, error: "Unknown type")
}
}
回调 JS ,即执行给 window 注入的 __handleNativeResponse 方法:
func respond(
id: String,
success: Bool,
data: Any? = nil,
error: String? = nil
) {
let response: [String: Any] = [
"id": id,
"success": success,
"data": data ?? NSNull(),
"error": error ?? NSNull()
]
guard
let jsonData = try? JSONSerialization.data(withJSONObject: response),
let json = String(data: jsonData, encoding: .utf8)
else { return }
let js = "window.__handleNativeResponse(\(json))"
webView?.evaluateJavaScript(js)
}
JS 发送消息到 Native,给每个请求创建一个 id ,把回调存起来。
const callbacks = new Map<string, Callback>();
export function callNative<T = any, P = any>(type: string, payload?: P): Promise<T> {
return new Promise((resolve, reject) => {
const id = generateId();
callbacks.set(id, { resolve, reject });
const message: NativeRequest<P> = {
id,
type,
payload,
};
if (!window.webkit || !window.webkit.messageHandlers || !window.webkit.messageHandlers.native) {
callbacks.delete(id);
reject(new Error("Native bridge not available"));
return;
}
window.webkit.messageHandlers.native.postMessage(message);
});
}
Native 响应后,JS 接收消息,并根据 id 拿到存起来的 resolve 或 reject 执行,实现 Promise 的效果。
window.__handleNativeResponse = function (response: NativeResponse) {
const { id, success, data, error } = response;
const cb = callbacks.get(id);
if (!cb) return;
if (success) {
cb.resolve(data);
} else {
cb.reject(error);
}
callbacks.delete(id);
};