如何通过WkWebView从网站获取信息

问题描述 投票:0回答:1

每当加载新页面时,我想检查新的网址,然后从页面中提取一些信息。这是基本设置:

struct MyWebView: UIViewRepresentable {
    @State var someInfo: String?

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

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

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: MyWebView

        init(_ parent: MyWebView) {
            self.parent = parent
        }
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            // do something here (see below)
        }
    }
}

我将“严格并发检查”设置为“完成”,但我不知道如何消除

func webView(...)
中的警告。这是我尝试过的两个选项。

选项 1:

@MainActor
// v Main actor-isolated instance method 'webView(_:didFinish:)' cannot 
//   be used to satisfy nonisolated protocol requirement
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    let url = webView.url
    if url?.path.starts(with: "https://stackoverflow.com") != true {
        return
    }
    
    webView.evaluateJavaScript("document.body.innerHTML") { (result, error) in
        if let someInfo = result as? String {
            self.parent.someInfo = someInfo
        }
    }
}

选项2:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    Task {
        let url = await webView.url
        if url?.path.starts(with: "https://stackoverflow.com") != true {
            return
        }
        
        // v Consider using asynchronous alternative function
        // v Passing argument of non-sendable type '(Any?, (any Error)?) -> Void' 
        //   into main actor-isolated context may introduce data races
        await webView.evaluateJavaScript("document.body.innerHTML") { (result, error) in
            if let someInfo = result as? String {
                // v Capture of 'self' with non-sendable type 
                //   'MyWebView.Coordinator' in a `@Sendable` closure
                self.parent.someInfo = someInfo
            }
        }
    }
}
swift swiftui concurrency wkwebview
1个回答
0
投票

不确定是否有人会遇到同样的问题,因为发生了多种事情,但这就是我到目前为止解决它的方法(基于选项 2):

我不是将状态存储在某处,而是存储一个

Task
,然后从父级内部获取值。这解决了
Capture of 'self' with non-sendable type 'MyWebView.Coordinator' in a '@Sendable' closure
-警告:

struct MyWebView: UIViewRepresentable {
    @Binding var getSomeInfoTask: Task<String?, Never>?

    // ...
}

在父母的某个地方:

SomeView()
.task(id: getSomeInfoTask) {
    if let task = getSomeInfoTask {
        let someInfo = await task.value
        // ...
    }
}

另外两个警告

Consider using asynchronous alternative function
Passing argument of non-sendable type '(Any?, (any Error)?) -> Void' into main actor-isolated context may introduce data races
正在通过使用此扩展来解决。它将完成处理程序转换为
async
函数:

extension WKWebView {
    func evaluateJavaScriptAsync<T: Sendable>(_ javaScriptString: String) async throws -> T {
        return try await withCheckedThrowingContinuation { continuation in
            self.evaluateJavaScript(javaScriptString) { result, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else if let result = result as? T {
                    continuation.resume(returning: result)
                } else {
                    continuation.resume(throwing: NSError(
                        domain: "WKWebView",
                        code: 0,
                        userInfo: [NSLocalizedDescriptionKey: "JavaScript evaluation returned a non-object result"]
                    ))
                }
            }
        }
    }
}

最后调用它看起来像这样:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    parent.getSomeInfoTask?.cancel()
    parent.getSomeInfoTask = Task {
        let url = await webView.url
        if url?.path.starts(with: "https://stackoverflow.com") != true {
            return nil as String?
        }
        do {
            return try await webView.evaluateJavaScriptAsync("document.body.innerHTML")
        } catch {
            print("Failed loading js: \(error)")
        }
        return nil as String?
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.