VisionOS问题:关闭WindowGroup后WKWebView停止渲染
场景说明
开发visionOS应用时,3D环境中的画布基于RealityKit实体,通过定期对WKWebView做快照,将图像作为画布纹理。用户点击画布会打开WindowGroup,显示同一个共享WKWebView实例,此时3D画布与WindowGroup的内容同步正常。但关闭WindowGroup后,3D画布的快照更新完全停止:快照循环任务仍在运行,但WKWebView.takeSnapshot(...)的continuation永久挂起,既不返回图像也不抛出错误;重新打开WindowGroup后,快照功能恢复正常。
快照核心代码:
@MainActor private func getSnapshot() async -> UIImage? { guard !webView.isLoading else { return nil } let config = WKSnapshotConfiguration() config.rect = webView.bounds config.afterScreenUpdates = true return await withCheckedContinuation { continuation in webView.takeSnapshot(with: config) { image, _ in continuation.resume(returning: image) } } }
已验证的关键信息
- WKWebView实例始终在内存中,未被释放
- 快照循环的Task持续运行,仅卡在
takeSnapshot调用环节 - 尝试将WKWebView放入隐藏UIWindow(设置alpha=0.01、windowLevel=.alert+1、isHidden=false),问题仍存在
推测原因
一旦将共享WKWebView传入SwiftUI的UIViewRepresentable并渲染,SwiftUI会接管其生命周期。关闭WindowGroup时,SwiftUI会通知WebKit暂停该WKWebView,即便画布实体仍持有强引用,WebKit也会停止其渲染与快照能力,直到该WebView再次被SwiftUI视图加载。
解决方案建议
1. 隔离SwiftUI对WKWebView生命周期的控制
不要直接将共享WKWebView传入UIViewRepresentable,而是创建代理视图作为中间层,让代理视图嵌入SwiftUI层级,共享WKWebView则作为代理的子视图存在,避免SwiftUI直接操作WebView的生命周期:
class WebViewProxy: UIView { weak var targetWebView: WKWebView? override func layoutSubviews() { super.layoutSubviews() targetWebView?.frame = bounds } } struct WebViewContainer: UIViewRepresentable { let sharedWebView: WKWebView func makeUIView(context: Context) -> WebViewProxy { let proxy = WebViewProxy() proxy.targetWebView = sharedWebView proxy.addSubview(sharedWebView) return proxy } func updateUIView(_ uiView: WebViewProxy, context: Context) { uiView.targetWebView = sharedWebView uiView.setNeedsLayout() } }
2. 用全局进程池保持WebKit进程活跃
为共享WKWebView配置全局共享的WKProcessPool,确保WebKit底层进程不会因SwiftUI视图销毁而被回收;同时定期触发轻量操作唤醒WebView:
// 全局共享进程池 let globalWebProcessPool = WKProcessPool() // 创建共享WKWebView时使用 let webConfig = WKWebViewConfiguration() webConfig.processPool = globalWebProcessPool let sharedWebView = WKWebView(frame: .zero, configuration: webConfig) // 定期唤醒WebView(每30秒执行一次轻量JS) Task { while true { _ = try? await sharedWebView.evaluateJavaScript("void 0") try? await Task.sleep(nanoseconds: 30 * 1_000_000_000) } }
3. 替换快照方法为图层绘制
如果takeSnapshot因WebView暂停无法返回,可尝试用drawHierarchy直接从WebView图层绘制图像,绕开WebKit的快照回调机制:
@MainActor private func getSnapshot() async -> UIImage? { guard !webView.isLoading else { return nil } UIGraphicsBeginImageContextWithOptions(webView.bounds.size, false, UIScreen.main.scale) webView.drawHierarchy(in: webView.bounds, afterScreenUpdates: true) let snapshot = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return snapshot }
4. 将WKWebView托管在独立UIWindow
创建一个永久存在的、不可见的UIWindow,将共享WKWebView添加到这个window的根视图中,完全脱离SwiftUI的视图层级控制,这样WindowGroup的关闭不会影响WebView的运行:
class PersistentWebViewWindow: UIWindow { static let shared = PersistentWebViewWindow() private override init(frame: CGRect) { super.init(frame: UIScreen.main.bounds) self.windowLevel = .lowest self.isHidden = false self.alpha = 0.001 self.rootViewController = UIViewController() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func attachWebView(_ webView: WKWebView) { rootViewController?.view.addSubview(webView) webView.frame = rootViewController?.view.bounds ?? .zero } } // 初始化共享WebView时绑定到独立window let sharedWebView = WKWebView(...) PersistentWebViewWindow.shared.attachWebView(sharedWebView)
内容的提问来源于stack exchange,提问作者Perazim

