从 SwiftUI 按钮调用 evaluateJavascript
Call evaluateJavascript from a SwiftUI button
假设您有这个 WKWebView 实现:
import Combine
import SwiftUI
import WebKit
class WebViewData: ObservableObject {
@Published var parsedText: NSAttributedString? = nil
var isInit = false
var shouldUpdateView = true
}
struct WebView: UIViewRepresentable {
let text: String
@ObservedObject var data: WebViewData
func makeUIView(context: Context) -> WKWebView {
context.coordinator.view.navigationDelegate = context.coordinator
return context.coordinator.view
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard data.shouldUpdateView else {
data.shouldUpdateView = false
return
}
let html = """
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
\(text)
<script>
let isScrolling = false;
let timer;
function toggleScrolling() {
if(!isScrolling) {
timer = setInterval(function() {
window.scrollBy(0, 1);
}, \(80 / autoScrollVelocity));
} else {
clearInterval(timer)
}
isScrolling = !isScrolling;
}
</script>
</body>
</html>
"""
uiView.loadHTMLString(html, baseURL: nil)
}
func makeCoordinator() -> WebViewCoordinator {
return WebViewCoordinator(view: self)
}
}
class WebViewCoordinator: NSObject, WKNavigationDelegate {
let view: WebView
init(view: WebView) {
self.view = view
super.init()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
if !self.view.data.isInit {
self.view.data.isInit = true
// useless text parsing here...
}
}
}
}
在这个视图中
import SwiftUI
struct ReadingView: View {
@ObservedObject var webViewData = WebViewData()
private let text: String
init(text: String?) {
self.text = text ?? "Sorry, this reading is empty"
}
var body: some View {
VStack {
Button("Auto scroll") {
??????
}
WebView(title: self.title, text: self.text, data: self.webViewData)
}
.onReceive(self.webViewData.$parsedText, perform: { parsedText in
if let parsedText = parsedText {
print(parsedText)
}
})
}
}
现在,在带有标签 Auto scroll
的按钮中,如何在 html toggleScrolling()
中调用 javascript(或在 WKUserScript 中移动此代码如有必要)?我在这里迷路了。
提前感谢您的任何建议
我将解决问题本身(从 SwiftUI 按钮调用 evaluateJavascript
)而不一定是 javascript 本身(您的 toggleScrolling
函数) ,我还没有测试过。
我认为这是使用 Combine(这意味着您必须确保 import Combine
在文件顶部)通过 ObservableObject
在视图之间传递消息的好机会已设置。
这是最终代码(我不得不更改一些无法编译的原始代码):
class WebViewData: ObservableObject {
@Published var parsedText: NSAttributedString? = nil
var functionCaller = PassthroughSubject<Void,Never>()
var isInit = false
var shouldUpdateView = true
}
struct WebView: UIViewRepresentable {
let text: String
@StateObject var data: WebViewData
func makeUIView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.navigationDelegate = context.coordinator
return webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard data.shouldUpdateView else {
data.shouldUpdateView = false
return
}
context.coordinator.tieFunctionCaller(data: data)
context.coordinator.webView = uiView
let html = """
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
\(text)
<script>
function doAlert() { document.body.innerHTML += "hi"; }
</script>
</body>
</html>
"""
uiView.loadHTMLString(html, baseURL: nil)
}
func makeCoordinator() -> WebViewCoordinator {
return WebViewCoordinator(view: self)
}
}
class WebViewCoordinator: NSObject, WKNavigationDelegate {
var parent: WebView
var webView: WKWebView? = nil
private var cancellable : AnyCancellable?
init(view: WebView) {
self.parent = view
super.init()
}
func tieFunctionCaller(data: WebViewData) {
cancellable = data.functionCaller.sink(receiveValue: { _ in
self.webView?.evaluateJavaScript("doAlert()")
})
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
if !self.parent.data.isInit {
self.parent.data.isInit = true
// useless text parsing here...
}
}
}
}
struct ReadingView: View {
@StateObject var webViewData = WebViewData()
var text : String
init(text: String?) {
self.text = text ?? "Sorry, this reading is empty"
}
var body: some View {
VStack {
Button("Call javascript") {
webViewData.functionCaller.send()
}
WebView(text: text, data: webViewData)
}
.onReceive(webViewData.$parsedText, perform: { parsedText in
if let parsedText = parsedText {
print(parsedText)
}
})
}
}
会发生什么?
WebViewData
上有一个 PassthroughSubject
不采用实际值(它只采用 Void
),用于从WebViewCoordinator
.
的 SwiftUI 视图
WebViewCoordinator
订阅该发布者并运行 evaluateJavasscript
。为此,它必须引用 WKWebView
,您可以看到我在 updateUIView
中传递了它
你实际上并没有在 makeUIView
中返回一个 WKWebView
(或者你可能是,但是这个问题的简化代码把它弄乱了一点)
假设您有这个 WKWebView 实现:
import Combine
import SwiftUI
import WebKit
class WebViewData: ObservableObject {
@Published var parsedText: NSAttributedString? = nil
var isInit = false
var shouldUpdateView = true
}
struct WebView: UIViewRepresentable {
let text: String
@ObservedObject var data: WebViewData
func makeUIView(context: Context) -> WKWebView {
context.coordinator.view.navigationDelegate = context.coordinator
return context.coordinator.view
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard data.shouldUpdateView else {
data.shouldUpdateView = false
return
}
let html = """
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
\(text)
<script>
let isScrolling = false;
let timer;
function toggleScrolling() {
if(!isScrolling) {
timer = setInterval(function() {
window.scrollBy(0, 1);
}, \(80 / autoScrollVelocity));
} else {
clearInterval(timer)
}
isScrolling = !isScrolling;
}
</script>
</body>
</html>
"""
uiView.loadHTMLString(html, baseURL: nil)
}
func makeCoordinator() -> WebViewCoordinator {
return WebViewCoordinator(view: self)
}
}
class WebViewCoordinator: NSObject, WKNavigationDelegate {
let view: WebView
init(view: WebView) {
self.view = view
super.init()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
if !self.view.data.isInit {
self.view.data.isInit = true
// useless text parsing here...
}
}
}
}
在这个视图中
import SwiftUI
struct ReadingView: View {
@ObservedObject var webViewData = WebViewData()
private let text: String
init(text: String?) {
self.text = text ?? "Sorry, this reading is empty"
}
var body: some View {
VStack {
Button("Auto scroll") {
??????
}
WebView(title: self.title, text: self.text, data: self.webViewData)
}
.onReceive(self.webViewData.$parsedText, perform: { parsedText in
if let parsedText = parsedText {
print(parsedText)
}
})
}
}
现在,在带有标签 Auto scroll
的按钮中,如何在 html toggleScrolling()
中调用 javascript(或在 WKUserScript 中移动此代码如有必要)?我在这里迷路了。
提前感谢您的任何建议
我将解决问题本身(从 SwiftUI 按钮调用 evaluateJavascript
)而不一定是 javascript 本身(您的 toggleScrolling
函数) ,我还没有测试过。
我认为这是使用 Combine(这意味着您必须确保 import Combine
在文件顶部)通过 ObservableObject
在视图之间传递消息的好机会已设置。
这是最终代码(我不得不更改一些无法编译的原始代码):
class WebViewData: ObservableObject {
@Published var parsedText: NSAttributedString? = nil
var functionCaller = PassthroughSubject<Void,Never>()
var isInit = false
var shouldUpdateView = true
}
struct WebView: UIViewRepresentable {
let text: String
@StateObject var data: WebViewData
func makeUIView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.navigationDelegate = context.coordinator
return webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard data.shouldUpdateView else {
data.shouldUpdateView = false
return
}
context.coordinator.tieFunctionCaller(data: data)
context.coordinator.webView = uiView
let html = """
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
\(text)
<script>
function doAlert() { document.body.innerHTML += "hi"; }
</script>
</body>
</html>
"""
uiView.loadHTMLString(html, baseURL: nil)
}
func makeCoordinator() -> WebViewCoordinator {
return WebViewCoordinator(view: self)
}
}
class WebViewCoordinator: NSObject, WKNavigationDelegate {
var parent: WebView
var webView: WKWebView? = nil
private var cancellable : AnyCancellable?
init(view: WebView) {
self.parent = view
super.init()
}
func tieFunctionCaller(data: WebViewData) {
cancellable = data.functionCaller.sink(receiveValue: { _ in
self.webView?.evaluateJavaScript("doAlert()")
})
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
if !self.parent.data.isInit {
self.parent.data.isInit = true
// useless text parsing here...
}
}
}
}
struct ReadingView: View {
@StateObject var webViewData = WebViewData()
var text : String
init(text: String?) {
self.text = text ?? "Sorry, this reading is empty"
}
var body: some View {
VStack {
Button("Call javascript") {
webViewData.functionCaller.send()
}
WebView(text: text, data: webViewData)
}
.onReceive(webViewData.$parsedText, perform: { parsedText in
if let parsedText = parsedText {
print(parsedText)
}
})
}
}
会发生什么?
的 SwiftUI 视图WebViewData
上有一个PassthroughSubject
不采用实际值(它只采用Void
),用于从WebViewCoordinator
.
中传递了它WebViewCoordinator
订阅该发布者并运行evaluateJavasscript
。为此,它必须引用WKWebView
,您可以看到我在updateUIView
你实际上并没有在
makeUIView
中返回一个WKWebView
(或者你可能是,但是这个问题的简化代码把它弄乱了一点)