// // SPWebView.swift // Thimra // // Created by 曾觉新 on 2025/4/14. // import UIKit @preconcurrency import WebKit class SPWebView: WKWebView { weak var delegate: SPWebViewDelegate? private(set) var scriptMessageHandlerArray: [SPWebViewMessageName] = [ WebMessageAPP, ] deinit { self.removeObserver(self, forKeyPath: "estimatedProgress") self.removeObserver(self, forKeyPath: "title") } override init(frame: CGRect, configuration: WKWebViewConfiguration) { super.init(frame: frame, configuration: configuration) addScriptMessageHandler() _setupInit() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func _setupInit() { self.isOpaque = false self.uiDelegate = self self.navigationDelegate = self self.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil) self.addObserver(self, forKeyPath: "title", options: .new, context: nil) // var userAgent = (self.value(forKey: "userAgent") as? String) ?? "" // if !userAgent.contains(";jxdbBrowser") { // userAgent = userAgent + ";jxdbBrowser|V\(kYDAPPVersion)" // self.customUserAgent = userAgent // } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if object as? SPWebView == self { if keyPath == "estimatedProgress", let progress = change?[NSKeyValueChangeKey.newKey] as? CGFloat { self.delegate?.webView?(webView: self, didChangeProgress: progress) } else if keyPath == "title", let title = change?[NSKeyValueChangeKey.newKey] as? String { self.delegate?.webView?(webView: self, didChangeTitle: title) } } } func load(urlStr: String) { guard let url = URL(string: urlStr) else { return } var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30) self.load(request) } func removeScriptMessageHandler() { self.scriptMessageHandlerArray.forEach{ configuration.userContentController.removeScriptMessageHandler(forName: $0) } } func addScriptMessageHandler() { self.scriptMessageHandlerArray.forEach{ configuration.userContentController.add(YYWeakProxy(target: self) as! WKScriptMessageHandler, name: $0) } } } //MARK:-------------- WKUIDelegate -------------- extension SPWebView: WKUIDelegate { func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let alertController = UIAlertController(title: "提示", message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "确认", style: .default, handler: { (action) in completionHandler() })) self.viewController?.present(alertController, animated: true, completion: nil) } func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let alertController = UIAlertController(title: "提示", message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (action) in completionHandler(false) })) alertController.addAction(UIAlertAction(title: "确认", style: .default, handler: { (action) in completionHandler(true) })) self.viewController?.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: prompt, message: "", preferredStyle: .alert) alertController.addTextField { (textField) in textField.text = defaultText } alertController.addAction(UIAlertAction(title: "完成", style: .default, handler: { (action) in completionHandler(alertController.textFields?.first?.text) })) self.viewController?.present(alertController, animated: true, completion: nil) } } //MARK:-------------- WKNavigationDelegate -------------- extension SPWebView: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { /*同步cookie*/ // let cookies = JXCookiesManager.getAllCookies() // for cookie in cookies { // setCookie(cookie: cookie) // } decisionHandler(.allow); } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { spLog(message: navigationAction.request.url) // if navigationAction.request.url?.scheme == "tel" { // UIApplication.shared.openURL(navigationAction.request.url!) // decisionHandler(.cancel) // return // } if let url = navigationAction.request.url, url.scheme != "http", url.scheme != "https" { UIApplication.shared.open(url) decisionHandler(.cancel) return } //防止抓包 // if JXRequestHttpProxy.isIntercept() { // decisionHandler(.cancel) // return // } if let result = self.delegate?.webView?(self, shouldStartLoadWith: navigationAction) { if result { decisionHandler(.allow) } else { decisionHandler(.cancel) } } else { decisionHandler(.allow) } } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { self.delegate?.webViewDidStartLoad?(self) } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // ///禁用长按展示长图 // webView.evaluateJavaScript("document.documentElement.style.webkitTouchCallout='none';", completionHandler: nil) // ///禁用长按选择图片文字 // webView.evaluateJavaScript("document.documentElement.style.webkitUserSelect='none';", completionHandler: nil) self.delegate?.webViewDidFinishLoad?(self) } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { self.delegate?.webView?(self, didFailLoadWithError: error) } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { self.delegate?.webView?(self, didFailLoadWithError: error) } } //MARK:-------------- WKScriptMessageHandler -------------- extension SPWebView: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { self.delegate?.userContentController?(userContentController, didReceive: message) } } //MARK:-------------- YDWebViewDelegate -------------- @objc protocol SPWebViewDelegate: NSObjectProtocol { @objc optional func webView(_ webView: SPWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool @objc optional func webViewDidStartLoad(_ webView: SPWebView) @objc optional func webViewDidFinishLoad(_ webView: SPWebView) @objc optional func webView(_ webView: SPWebView, didFailLoadWithError error: Error) ///进度 @objc optional func webView(webView: SPWebView, didChangeProgress progress: CGFloat) ///标题 @objc optional func webView(webView: SPWebView, didChangeTitle title: String) ///web交互用 @objc optional func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) }