You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 23:25:27