【问题标题】:Call evaluateJavascript from a SwiftUI button从 SwiftUI 按钮调用 evaluateJavascript
【发布时间】:2021-03-11 11:39:01
【问题描述】:

假设你有这个 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 中)?我在这里迷路了。

提前感谢您的任何建议

【问题讨论】:

    标签: javascript swift swiftui wkwebview


    【解决方案1】:

    我将解决问题本身(从 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)
                }
            })
        }
    }
    

    会发生什么?

    1. WebViewData 上有一个 PassthroughSubject,它不采用实际值(它只采用 Void),用于将信号从 SwiftUI 视图发送到 WebViewCoordinator

    2. WebViewCoordinator 订阅该发布者并运行 evaluateJavasscript。为此,它必须引用WKWebView,您可以看到我在updateUIView 中传递了它

    3. 您实际上并没有在makeUIView 中返回WKWebView(或者您可能是,但问题的简化代码有点搞砸了)

    【讨论】:

    • 谢谢,谢谢,谢谢。为了兼容 IOS13,我不得不放弃 StateObject 转而使用 ObservableObject。它就像一个魅力。
    猜你喜欢
    • 1970-01-01
    • 2020-10-06
    • 1970-01-01
    • 2017-05-10
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 2021-09-25
    • 2021-10-08
    相关资源
    最近更新 更多