How do I refresh a WKWebView via @StateObject stored in another class after altering a property on an @ObservedObject?

huangapple go评论67阅读模式
英文:

How do I refresh a WKWebView via @StateObject stored in another class after altering a property on an @ObservedObject?

问题

我有一个基本的模型,像这样:
```swift
public final class WebSite: ObservableObject {
    @Published
    var URL: String
    // 让我们假设这将是一个有效的 URL
}

和一个 webview。它使用站点 URL 来呈现一个 UIViewRepresentable(我的站点视图必须是一个类,因为 WKWebView 协议):

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 { ... }
    }
}

现在,我有一个父 ContentView 如下:

struct ContentView: View {
    @StateObject var webSite: WebSite = WebSite(URL: "stackoverflow.com")

    var body: some View {
        WebSiteView(webSite: webSite)
    }
}

和一个显示 web 视图的 WebSiteView

struct WebSiteView: View {
    @ObservedObject
    var webSite: WebSite
    
    var body: some View {
        let webView: WebSiteWebView = WebView(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) {}
    }
}

简而言之,发生了以下情况:

  1. 父 ContentView 初始化一个包含名为 URL 的发布的 String@StateObject
  2. ContentView 有一个名为 WebSiteView 的子视图,将网站类作为 @ObservedObject
  3. 该子视图从 @ObservedObject 加载 URL
  4. 该子视图还有一个更改 URL 的按钮。

我期望在按下按钮后,WebSiteView 将重新渲染,因为它将 webSite 声明为 @ObservedObject。而且它确实(从技术上)做到了。webView.render() 使用新的 URL 调用,但 WKWebView 不会刷新。有人知道这是为什么吗?


<details>
<summary>英文:</summary>

I have a basic model like this&quot;

public final class WebSite: ObservableObject {
@Published
var URL: String
// Let us please assume that this will be a valid URL
}


And a webview. It uses the site url to render a `UIViewRepresentable` (my site view must be a class bc of the `WKWebView` protocol):

class WebSiteWebView: WKWebView {
@ObservedObject
var webSite: WebSite

init() { /* initialization. Not relevant here*/ }

loadUrl(url: String) {
    self.load(URLRequest(url: url))
}

render() -&gt; some View {
    let representable = WebSiteWebView.Representable(webView: self)
    
    do {
        try representable.webView.loadUrl(url: self.webSite.url)
    } catch { ... }
}

}


Now, I have a parent ContentView as follows:

struct ContentView: View {
@StateObject var webSite: WebSite = WebSite(URL: "stackoverflow.com")

var body: some View {
    WebSiteView(webSite: webSite)
}

}


and a `WebSiteView` that displays a web view:

struct WebSiteView: View {
@ObservedObject
var webSite: WebSite

var body: some View {
    let webView: WebSiteWebView = WebView(webSite: webSite)

    VStack {
        webView.render()

        Button(&quot;Switch URL&quot;) {
            webSite.URL = &quot;google.com&quot;
        }
    }
}

}


The `UIViewRepresentable` impl for completeness

extension WebSiteWebView {
struct Representable: UIViewRepresentable {
let webView: WebSiteWebView

    func makeUIView(context: Context) -&gt; WebSiteWebView {
        self.webView
    }

    func updateUIView(_ uiView: WebSiteWebView, context: Context) {}
}

}


In a nutshell, this is what&#39;s going on:
1. The parent ContentView initializes a `@StateObject` that contains a published `String` named `URL`.
2. The ContentView has a child view named `WebSiteView` that takes the web site class as an `@ObservedObject`
3. That child view loads the `URL` from the `@ObservedObject`
4. That child view also has a button that changes the `URL`

I would expect that after the button is pressed, the `WebSiteView` would re-render, since it declares the `webSite` as an `@ObservedObject`. And, it does (technically). `webView.render()` is called with the new `URL` but the `WKWebView` does not refresh. Does anyone know why this is happening?

</details>


# 答案1
**得分**: 1

代码部分如下:

```swift
struct WebSiteWebView: UIViewRepresentable {
    let url: URL

    func makeUIView(context: Context) -> WKWebView {
        WKWebView()
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        if uiView.url != url {
            uiView.load(url)
        }
    }
}
英文:

things are a bit backwards, it needs to be like this:

    struct WebSiteWebView: UIViewRepresentable {
        let url: URL

        func makeUIView(context: Context) -&gt; WKWebView {
            WKWebView()
        }

        func updateUIView(_ uiView: WKWebView, context: Context) {
            if uiView.url != url {
                uiView.load(url)
            }
        }
    }

Basically the WebSiteWebView struct can be recreated many times and your job is to update the WKWebView object with any change to the properties. updateUIView is called the first time and also any time the url changes.

huangapple
  • 本文由 发表于 2023年2月16日 07:11:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75466284.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定