SwiftUI:修改@ObservedObject的URL后,WKWebView未刷新的原因及解决方法
点击按钮修改URL后WKWebView未刷新的原因及解决办法
问题背景
现有以下SwiftUI+UIKit代码,点击"Switch URL"按钮修改WebSite的URL属性后,WebSiteView虽会重绘并调用webView.render()加载新URL,但界面上的WKWebView并未刷新。
基础模型
public final class WebSite: ObservableObject { @Published var URL: String // 假设该URL始终有效 }
WebView子类实现
class WebSiteWebView: WKWebView { @ObservedObject var webSite: WebSite init() { /* 初始化逻辑,此处无关*/ } func loadUrl(url: String) { self.load(URLRequest(url: URL(string: url)!)) } func render() -> some View { let representable = WebSiteWebView.Representable(webView: self) do { try representable.webView.loadUrl(url: self.webSite.URL) } catch { /* 错误处理*/ } return representable } }
父视图ContentView
struct ContentView: View { @StateObject var webSite: WebSite = WebSite(URL: "stackoverflow.com") var body: some View { WebSiteView(webSite: webSite) } }
展示WebView的WebSiteView
struct WebSiteView: View { @ObservedObject var webSite: WebSite var body: some View { let webView: WebSiteWebView = WebSiteWebView(webSite: webSite) VStack { webView.render() Button("Switch URL") { webSite.URL = "google.com" } } } }
UIViewRepresentable扩展
extension WebSiteWebView { struct Representable: UIViewRepresentable { let webView: WebSiteWebView func makeUIView(context: Context) -> WebSiteWebView { self.webView } func updateUIView(_ uiView: WebSiteWebView, context: Context) {} } }
原因分析
- WebView实例重复创建:每次
WebSiteView因webSite.URL变化重绘时,body中的let webView: WebSiteWebView = WebSiteWebView(webSite: webSite)会生成新的WebSiteWebView实例,但UIViewRepresentable返回的是之前已经添加到界面的旧实例,新实例的loadUrl调用对界面上的WKWebView没有任何影响。 - UIKit类无法使用SwiftUI的ObservedObject:
WebSiteWebView是WKWebView的子类(UIKit类),@ObservedObject属性包装器无法在这类非SwiftUI视图、非ObservableObject类中正常工作,无法自动监听WebSite的@Published属性变化。 - 未利用UIViewRepresentable的updateUIView回调:
updateUIView是SwiftUI通知UIKit视图更新状态的核心方法,当前实现为空,没有在URL变化时触发WKWebView的刷新逻辑。
解决方法
方案一:重构UIViewRepresentable,直接管理WKWebView
将WKWebView的逻辑整合到UIViewRepresentable中,利用SwiftUI的生命周期回调处理URL更新:
// 修正模型命名(符合Swift规范) public final class WebSite: ObservableObject { @Published var url: String init(url: String) { self.url = url } } // 直接封装WKWebView的UIViewRepresentable struct WebViewRepresentable: UIViewRepresentable { @ObservedObject var webSite: WebSite func makeUIView(context: Context) -> WKWebView { let webView = WKWebView() // 初始化加载初始URL if let url = URL(string: webSite.url) { webView.load(URLRequest(url: url)) } return webView } func updateUIView(_ uiView: WKWebView, context: Context) { // URL变化时自动重新加载 if let url = URL(string: webSite.url) { uiView.load(URLRequest(url: url)) } } } // 修改WebSiteView struct WebSiteView: View { @ObservedObject var webSite: WebSite var body: some View { VStack { WebViewRepresentable(webSite: webSite) Button("Switch URL") { webSite.url = "https://google.com" // 补充完整协议,避免加载失败 } } } } // 父ContentView保持不变 struct ContentView: View { @StateObject var webSite: WebSite = WebSite(url: "https://stackoverflow.com") var body: some View { WebSiteView(webSite: webSite) } }
方案二:保留WebSiteWebView子类,用Combine手动监听URL变化
如果必须保留WebSiteWebView子类,可通过Combine框架手动监听WebSite的属性变化:
import Combine public final class WebSite: ObservableObject { @Published var url: String init(url: String) { self.url = url } } class WebSiteWebView: WKWebView { private var cancellables = Set<AnyCancellable>() let webSite: WebSite init(webSite: WebSite) { self.webSite = webSite super.init(frame: .zero, configuration: WKWebViewConfiguration()) // 监听url变化,自动刷新 webSite.$url .sink { [weak self] newUrl in guard let self = self, let url = URL(string: newUrl) else { return } self.load(URLRequest(url: url)) } .store(in: &cancellables) // 初始加载URL if let url = URL(string: webSite.url) { self.load(URLRequest(url: url)) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension WebSiteWebView { struct Representable: UIViewRepresentable { @ObservedObject var webSite: WebSite func makeUIView(context: Context) -> WebSiteWebView { WebSiteWebView(webSite: webSite) } func updateUIView(_ uiView: WebSiteWebView, context: Context) { // 无需额外操作,Combine已处理监听 } } } struct WebSiteView: View { @ObservedObject var webSite: WebSite var body: some View { VStack { WebSiteWebView.Representable(webSite: webSite) Button("Switch URL") { webSite.url = "https://google.com" } } } } struct ContentView: View { @StateObject var webSite: WebSite = WebSite(url: "https://stackoverflow.com") var body: some View { WebSiteView(webSite: webSite) } }
关键改进说明
- 补充了URL的完整协议(
https://),避免WKWebView因无效URL无法加载。 - 确保界面上的WKWebView实例始终唯一,不会因视图重绘被替换。
- 通过SwiftUI的生命周期回调或Combine监听,实现URL变化时的自动刷新。
内容的提问来源于stack exchange,提问作者dopatraman
相关产品推荐
相关产品推荐

