WKWebView javascript 警报、提示、确认不起作用

Posted

技术标签:

【中文标题】WKWebView javascript 警报、提示、确认不起作用【英文标题】:WKWebView javascript alert, prompt, confirm won't work 【发布时间】:2017-09-27 20:18:29 【问题描述】:

您好,我正在实现简单的 WKWebView 应用程序,我希望能够通过提示对话框询问用户输入,我尝试使用此处提到的解决方案

https://***.com/a/40157363/1665293

但我不确定它在实现时应该如何工作 - 这是否应该只是向 WKWebView 添加扩展以触发例如来自 javascript 的常规 alert() 还是我应该在 js 中传递一些不同的指令来触发这个原生警报?

所以我的问题是: 1)这在实施时应该如何工作 2)我在实施中缺少什么

这是我的控制器代码(给出整个控制器,因为我不知道这里有什么重要的)

提前致谢!

import UIKit
import WebKit

class ViewController:
    UIViewController
    , WKNavigationDelegate
    , UIScrollViewDelegate
    , WKUIDelegate


    @IBOutlet var webView: WKWebView!
    let getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
    let getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"

    override func loadView() 
        self.webView = WKWebView()
        self.webView.navigationDelegate = self

        //for prompt
        self.webView?.uiDelegate = self

        view = webView
    

    override func viewWillAppear(_ animated: Bool) //white status bar
        super.viewWillAppear(animated)
        webView.isOpaque = false //removes white flash on WKWebView load
        webView.backgroundColor = UIColor(red: 41/255, green: 45/255, blue: 91/255, alpha: 1)
        UIApplication.shared.statusBarStyle = .lightContent

        do 

            let paid = Bundle.main.infoDictionary?["paid"]  as? Bool;
            var fileName = "none"

            if(paid!)
                fileName = "index-ios-wvd-inlined--paid"
             else 
                fileName = "index-ios-wvd-inlined"
            
            guard let filePath = Bundle.main.path(forResource: fileName, ofType: "html")
                else 
                    print ("File reading error")
                    return
            


            let contents =  try String(contentsOfFile: filePath, encoding: .utf8)
            let baseUrl = URL(fileURLWithPath: filePath)
            webView.loadHTMLString(contents as String, baseURL: baseUrl)

        
        catch 
            print ("File HTML error")
        

    

    override var preferredStatusBarStyle : UIStatusBarStyle //white status bar
        return .lightContent
    

    override func viewDidLoad() 
        webView.scrollView.bounces = false;
        super.viewDidLoad()
        webView.scrollView.delegate = self //disable zoom


        //for haptics
        let config = WKWebViewConfiguration()
        config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentStartScript), scriptHandlerName:getUrlAtDocumentStartScript, scriptMessageHandler: self, injectionTime: .atDocumentStart)
        config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentEndScript), scriptHandlerName:getUrlAtDocumentEndScript, scriptMessageHandler: self, injectionTime: .atDocumentEnd)
        webView = WKWebView(frame:  UIScreen.main.bounds, configuration: config)
        webView.navigationDelegate = self
        view.addSubview(webView)
    

    //disable zoom        
    func viewForZooming(in: UIScrollView) -> UIView? 
        return nil;
    

    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    

    func tapped(i:Int) 
        print("Triggering haptic #\(i)")

        switch i 
        case 1:
            let generator = UINotificationFeedbackGenerator()
            generator.notificationOccurred(.error)

        case 2:
            let generator = UINotificationFeedbackGenerator()
            generator.notificationOccurred(.success)

        case 3:
            let generator = UINotificationFeedbackGenerator()
            generator.notificationOccurred(.warning)

        case 4:
            let generator = UIImpactFeedbackGenerator(style: .light)
            generator.impactOccurred()

        case 5:
            let generator = UIImpactFeedbackGenerator(style: .medium)
            generator.impactOccurred()

        case 6:
            let generator = UIImpactFeedbackGenerator(style: .heavy)
            generator.impactOccurred()

        default:
            let generator = UISelectionFeedbackGenerator()
            generator.selectionChanged()
        
    

    //alert/prompt/confirm dialogs
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping () -> Void) 

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler:  (action) in
            completionHandler()
        ))

        present(alertController, animated: true, completion: nil)
    


    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (Bool) -> Void) 

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)

        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler:  (action) in
            completionHandler(true)
        ))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler:  (action) in
            completionHandler(false)
        ))

        present(alertController, animated: true, completion: nil)
    


    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (String?) -> Void) 

        let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .actionSheet)

        alertController.addTextField  (textField) in
            textField.text = defaultText
        

        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler:  (action) in
            if let text = alertController.textFields?.first?.text 
                completionHandler(text)
             else 
                completionHandler(defaultText)
            
        ))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler:  (action) in
            completionHandler(nil)
        ))

        present(alertController, animated: true, completion: nil)
    



//sending scripts commands to JS and back
extension ViewController: WKScriptMessageHandler 
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 
        switch message.name 

        case getUrlAtDocumentStartScript:
            tapped(i: message.body as! Int)
            //print("start: \(message.body)")

        case getUrlAtDocumentEndScript:
            tapped(i: message.body as! Int)
            //print("tapped: \(message.body)")

        default:
            break;
        
    


extension WKUserScript 
    class func getUrlScript(scriptName: String) -> String 
        return "webkit.messageHandlers.\(scriptName).postMessage(1)"
    


extension WKWebView 
    func loadUrl(string: String) 
        if let url = URL(string: string) 
            load(URLRequest(url: url))
        
    


extension WKWebViewConfiguration 
    func addScript(script: String, scriptHandlerName:String, scriptMessageHandler: WKScriptMessageHandler, injectionTime:WKUserScriptInjectionTime) 
        let userScript = WKUserScript(source: script, injectionTime: injectionTime, forMainFrameOnly: false)
        userContentController.addUserScript(userScript)
        userContentController.add(scriptMessageHandler, name: scriptHandlerName)
    

【问题讨论】:

【参考方案1】:

好的,所以我找到了答案和解决方案,

1) 这将增加对原生 JS 方法的支持。 alert()prompt()confirm() 可以从 JS 或通过

调用
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 
        webView.evaluateJavaScript("confirm('Hello from evaluateJavascript()')", completionHandler: nil)
    

2) 这是我现在正在使用的方法的实现(我将其插入到ViewController 类的底部:

func webView(_ webView: WKWebView,
                 runJavaScriptAlertPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping () -> Void) 

        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        let title = NSLocalizedString("OK", comment: "OK Button")
        let ok = UIAlertAction(title: title, style: .default)  (action: UIAlertAction) -> Void in
            alert.dismiss(animated: true, completion: nil)
        
        alert.addAction(ok)
        present(alert, animated: true)
        completionHandler()
    

func webView(_ webView: WKWebView,
                 runJavaScriptTextInputPanelWithPrompt prompt: String,
                 defaultText: String?,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (String?) -> Void) 

        let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)

        alert.addTextField  (textField) in
            textField.text = defaultText
        

        alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:  (action) in
            if let text = alert.textFields?.first?.text 
                completionHandler(text)
             else 
                completionHandler(defaultText)
            

        ))

        alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler:  (action) in

            completionHandler(nil)

        ))

        self.present(alert, animated: true, completion: nil)

//        if ipad will crash on this do this (https://***.com/questions/42772973/ios-wkwebview-javascript-alert-crashing-on-ipad?noredirect=1&lq=1):
//        if let presenter = alertController.popoverPresentationController 
//            presenter.sourceView = self.view
//        
//        
//        self.present(alertController, animated: true, completion: nil)
    

func webView(_ webView: WKWebView,
                 runJavaScriptConfirmPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (Bool) -> Void) 

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)

        alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler:  (action) in
            completionHandler(true)
        ))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler:  (action) in
            completionHandler(false)
        ))

        self.present(alertController, animated: true, completion: nil)
    

也在viewDidLoad()的底部添加了这段代码:

webView.uiDelegate = self
webView.navigationDelegate = self
view.addSubview(webView!)

将完整代码添加到折叠的 sn-p 中 - 以防有人会混淆应该如何使用它:

import UIKit
import WebKit

class ViewController:
    UIViewController
    , WKNavigationDelegate
    , UIScrollViewDelegate
    , WKUIDelegate


    //wk webvew set

    @IBOutlet var webView: WKWebView!
    let getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
    let getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
//webkit.messageHandlers.GetUrlAtDocumentEnd.postMessage('1')
    
    override func loadView() 
        self.webView = WKWebView()
        self.webView.navigationDelegate = self
        
        //for prompt ??
        self.webView?.uiDelegate = self

        view = webView
    
    
    override func viewWillAppear(_ animated: Bool) //white status bar
        super.viewWillAppear(animated)
        webView.isOpaque = false //removes white flash on WKWebView load
        webView.backgroundColor = UIColor(red: 41/255, green: 45/255, blue: 91/255, alpha: 1)
        UIApplication.shared.statusBarStyle = .lightContent
        
        do 

            let paid = Bundle.main.infoDictionary?["paid"]  as? Bool;
            var fileName = "none"

            if(paid!)
                fileName = "index-ios-wvd-inlined--paid"
             else 
                fileName = "index-ios-wvd-inlined"
            
            guard let filePath = Bundle.main.path(forResource: fileName, ofType: "html")
                else 
                    // File Error
                    print ("File reading error")
                    return
            


            let contents =  try String(contentsOfFile: filePath, encoding: .utf8)
            let baseUrl = URL(fileURLWithPath: filePath)
            webView.loadHTMLString(contents as String, baseURL: baseUrl)
            
        
        catch 
            print ("File HTML error")
        
        
    
    
    override var preferredStatusBarStyle : UIStatusBarStyle //white status bar
        return .lightContent
    
    
    override func viewDidLoad() 
        webView.scrollView.bounces = false;
        
        super.viewDidLoad()
        
        //disable zoom
        webView.scrollView.delegate = self
        
        
        
        let config = WKWebViewConfiguration()
        config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentStartScript), scriptHandlerName:getUrlAtDocumentStartScript, scriptMessageHandler: self, injectionTime: .atDocumentStart)
        config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentEndScript), scriptHandlerName:getUrlAtDocumentEndScript, scriptMessageHandler: self, injectionTime: .atDocumentEnd)
        webView = WKWebView(frame:  UIScreen.main.bounds, configuration: config)
        webView.navigationDelegate = self
        view.addSubview(webView)

        
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view.addSubview(webView!)
         
        // Do any additional setup after loading the view, typically from a nib.
    
    
    func viewForZooming(in: UIScrollView) -> UIView? 
        return nil;
    

    //disable zoom
    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    

    func tapped(i:Int) 
        print("Running \(i)")
        
        switch i 
        case 1:
            let generator = UINotificationFeedbackGenerator()
            generator.notificationOccurred(.error)
            
        case 2:
            let generator = UINotificationFeedbackGenerator()
            generator.notificationOccurred(.success)
            
        case 3:
            let generator = UINotificationFeedbackGenerator()
            generator.notificationOccurred(.warning)
            
        case 4:
            let generator = UIImpactFeedbackGenerator(style: .light)
            generator.impactOccurred()
            
        case 5:
            let generator = UIImpactFeedbackGenerator(style: .medium)
            generator.impactOccurred()
            
        case 6:
            let generator = UIImpactFeedbackGenerator(style: .heavy)
            generator.impactOccurred()
            
        default:
            let generator = UISelectionFeedbackGenerator()
            generator.selectionChanged()
        
    
    
    
    //default alert/confirm/prompt dialogs
    func webView(_ webView: WKWebView,
                 runJavaScriptAlertPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping () -> Void) 
        
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        let title = NSLocalizedString("OK", comment: "OK Button")
        let ok = UIAlertAction(title: title, style: .default)  (action: UIAlertAction) -> Void in
            alert.dismiss(animated: true, completion: nil)
        
        alert.addAction(ok)
        present(alert, animated: true)
        completionHandler()
    
    
    func webView(_ webView: WKWebView,
                 runJavaScriptTextInputPanelWithPrompt prompt: String,
                 defaultText: String?,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (String?) -> Void) 
        
        let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
        
        alert.addTextField  (textField) in
            textField.text = defaultText
        
        
        alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:  (action) in
            if let text = alert.textFields?.first?.text 
                completionHandler(text)
             else 
                completionHandler(defaultText)
            
            
        ))
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler:  (action) in
            
            completionHandler(nil)
            
        ))
        
        self.present(alert, animated: true, completion: nil)
        
//        if ipad will crash on this try to uncomment (based on https://***.com/questions/42772973/ios-wkwebview-javascript-alert-crashing-on-ipad?noredirect=1&lq=1):
//        if let presenter = alertController.popoverPresentationController 
//            presenter.sourceView = self.view
//        
//        
//        self.present(alertController, animated: true, completion: nil)
    
    
    func webView(_ webView: WKWebView,
                 runJavaScriptConfirmPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (Bool) -> Void) 
        
        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
        
        alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler:  (action) in
            completionHandler(true)
        ))
        
        alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler:  (action) in
            completionHandler(false)
        ))
        
        self.present(alertController, animated: true, completion: nil)
    
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 
        webView.evaluateJavaScript("confirm('Hello from evaluateJavascript()')", completionHandler: nil)
    
    


extension ViewController: WKScriptMessageHandler 
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 
        switch message.name 
            
        case getUrlAtDocumentStartScript:
            tapped(i: message.body as! Int)
            //print("start: \(message.body)")
            
        case getUrlAtDocumentEndScript:
            tapped(i: message.body as! Int)
            //print("tapped: \(message.body)")
            
        default:
            break;
        
    


extension WKUserScript 
    class func getUrlScript(scriptName: String) -> String 
        return "webkit.messageHandlers.\(scriptName).postMessage(1)"
    


extension WKWebView 
    func loadUrl(string: String) 
        if let url = URL(string: string) 
            load(URLRequest(url: url))
        
    


extension WKWebViewConfiguration 
    func addScript(script: String, scriptHandlerName:String, scriptMessageHandler: WKScriptMessageHandler, injectionTime:WKUserScriptInjectionTime) 
        let userScript = WKUserScript(source: script, injectionTime: injectionTime, forMainFrameOnly: false)
        userContentController.addUserScript(userScript)
        userContentController.add(scriptMessageHandler, name: scriptHandlerName)
    

【讨论】:

以上是关于WKWebView javascript 警报、提示、确认不起作用的主要内容,如果未能解决你的问题,请参考以下文章

iOS WKWebView javascript警报在ipad上崩溃

WKWebView javascript 警报、提示、确认不起作用

嵌入在 html 中的 javascript 未在 wkwebview 中运行

iOS 13 SceneDelegate 上的 WKWebView 警报崩溃

WKWebView runJavaScriptAlertPanelWithMessage 从 iOS 9.3 崩溃

在 WKWebView 中处理 JavaScript 事件