490 lines
18 KiB
Swift
Executable File
490 lines
18 KiB
Swift
Executable File
//
|
||
// H5BaseViewController.swift
|
||
// E-Wow
|
||
//
|
||
// Created by dong on 2021/1/5.
|
||
//
|
||
|
||
// import Alamofire
|
||
// import CryptoSwift
|
||
import UIKit
|
||
import URLNavigator
|
||
import WebKit
|
||
|
||
class H5BaseViewController: CLBaseViewController {
|
||
public var navTitleShow: Bool = true
|
||
|
||
var targetUrl: URL! = URL(string: "")
|
||
var lastProgress: Double! = 0
|
||
|
||
private var observeRegister: Bool = false
|
||
|
||
private let schemes = ["route", "closeWebview", "getUserInfo", "setLoading", "request", "modal", "share", "init", "openBrowser", "getAppVersion", "sendTrack"]
|
||
|
||
lazy var webView: WKWebView! = {
|
||
let config = WKWebViewConfiguration()
|
||
let preferences = WKPreferences()
|
||
preferences.javaScriptCanOpenWindowsAutomatically = true
|
||
config.preferences = preferences
|
||
config.allowsInlineMediaPlayback = true
|
||
config.mediaTypesRequiringUserActionForPlayback = .video
|
||
|
||
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 320, height: 480), configuration: config)
|
||
// 以下3行修改webView颜色是有效的✅
|
||
webView.backgroundColor = .c.cbd
|
||
webView.scrollView.backgroundColor = .c.cbd
|
||
webView.isOpaque = false
|
||
let jsFilePath = Bundle.main.path(forResource: "webview", ofType: "js")
|
||
if let jshtml = try? String(contentsOfFile: jsFilePath!, encoding: .utf8) {
|
||
let script = WKUserScript(source: jshtml, injectionTime: .atDocumentStart, forMainFrameOnly: true)
|
||
webView.configuration.userContentController.addUserScript(script)
|
||
}
|
||
|
||
return webView
|
||
}()
|
||
|
||
lazy var progressView: UIProgressView = {
|
||
let progressView = UIProgressView(frame: .zero)
|
||
progressView.progress = 0
|
||
progressView.setProgress(0, animated: false)
|
||
navigationView.addSubview(progressView)
|
||
progressView.snp.makeConstraints { make in
|
||
make.left.right.bottom.equalToSuperview()
|
||
make.height.equalTo(1)
|
||
}
|
||
progressView.progressTintColor = .c.cpn//.purple
|
||
progressView.trackTintColor = .clear
|
||
return progressView
|
||
}()
|
||
|
||
lazy var closeButton: UIButton = {
|
||
let button = UIButton(type: .custom)
|
||
button.setImage(R.image.icon_close_20(), for: .normal)
|
||
button.addTarget(self, action: #selector(tapNaviCloseBtn), for: .touchUpInside)
|
||
button.isHidden = true
|
||
navigationView.leftStackH.addArrangedSubview(button)
|
||
button.snp.makeConstraints { make in
|
||
make.size.equalTo(CGSize(width: 44, height: 44))
|
||
}
|
||
return button
|
||
}()
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
// view.backgroundColor = .white
|
||
// webView.backgroundColor = .white
|
||
webView.backgroundColor = .c.cbd
|
||
webView.navigationDelegate = self
|
||
|
||
view.addSubview(webView)
|
||
webView.snp.makeConstraints { make in
|
||
make.bottom.right.left.equalTo(self.view)
|
||
make.top.equalTo(navigationView.snp.bottom)
|
||
}
|
||
|
||
navigationView.backButton.removeTarget(navigationView, action: .none, for: .touchUpInside)
|
||
navigationView.backButton.addTarget(self, action: #selector(tapNaviBackBtn), for: .touchUpInside)
|
||
|
||
setupBaseEvents()
|
||
}
|
||
|
||
override func viewWillAppear(_ animated: Bool) {
|
||
super.viewWillAppear(animated)
|
||
for (_, per) in schemes.enumerated() {
|
||
webView.configuration.userContentController.add(self, name: per)
|
||
}
|
||
}
|
||
|
||
override func viewWillDisappear(_ animated: Bool) {
|
||
super.viewWillDisappear(animated)
|
||
for (_, per) in schemes.enumerated() {
|
||
webView.configuration.userContentController.removeScriptMessageHandler(forName: per)
|
||
}
|
||
}
|
||
|
||
public func loadURL(url: URL) {
|
||
/*
|
||
if let lan = Languages.preferedLans.first {
|
||
let queryItem = URLQueryItem(name: "lang", value: lan.rawValue)
|
||
if var compoments = URLComponents(url: url, resolvingAgainstBaseURL: true){
|
||
if let items = compoments.queryItems, items.count > 0{
|
||
compoments.queryItems?.append(queryItem)
|
||
}else{
|
||
compoments.queryItems = [queryItem]
|
||
}
|
||
let afterUrl = compoments.url
|
||
targetUrl = afterUrl
|
||
}
|
||
dlog("h5 path with language: \(String(describing: targetUrl))")
|
||
} else {
|
||
targetUrl = url
|
||
}
|
||
*/
|
||
targetUrl = url
|
||
}
|
||
|
||
func setupBaseEvents() {
|
||
weak var wself = self
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||
guard let url = self.targetUrl else {
|
||
assert(false)
|
||
return
|
||
}
|
||
|
||
let request = NSMutableURLRequest(url: url)
|
||
wself?.webView.load(request as URLRequest)
|
||
}
|
||
|
||
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
|
||
webView.addObserver(self, forKeyPath: "title", options: .new, context: nil)
|
||
observeRegister = true
|
||
progressView.setProgress(0, animated: false)
|
||
}
|
||
|
||
deinit {
|
||
webView.configuration.userContentController.removeAllUserScripts()
|
||
if observeRegister {
|
||
webView.removeObserver(self, forKeyPath: "title")
|
||
webView.removeObserver(self, forKeyPath: "estimatedProgress")
|
||
}
|
||
}
|
||
|
||
func handleInit(msg: JSSDKMessage) {
|
||
// wait to override the func
|
||
}
|
||
}
|
||
|
||
extension H5BaseViewController {
|
||
@objc func tapNaviBackBtn() {
|
||
if webView.canGoBack {
|
||
webView.goBack()
|
||
} else {
|
||
close()
|
||
}
|
||
}
|
||
|
||
@objc func tapNaviCloseBtn() {
|
||
close()
|
||
}
|
||
}
|
||
|
||
// MARK: - Helper
|
||
|
||
extension H5BaseViewController {
|
||
func stopHtmlVoice() {
|
||
// ...
|
||
let jsaudio = "var vids = document.getElementsByTagName('audio'); for( var i = 0; i < vids.length; i++ ){vids.item(i).pause()}"
|
||
webView.evaluateJavaScript(jsaudio, completionHandler: nil)
|
||
}
|
||
|
||
static func clearCache() {
|
||
// let dataStore = WKWebsiteDataStore.default()
|
||
// dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), completionHandler: { records in
|
||
// for record in records {
|
||
// WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {
|
||
// print("♻️✅ Webview cache clear successfully\(record)")
|
||
// })
|
||
// }
|
||
// })
|
||
|
||
let websiteDataTypes: Set<String> = [
|
||
WKWebsiteDataTypeDiskCache,
|
||
WKWebsiteDataTypeMemoryCache,
|
||
WKWebsiteDataTypeLocalStorage,
|
||
WKWebsiteDataTypeWebSQLDatabases,
|
||
WKWebsiteDataTypeIndexedDBDatabases
|
||
]
|
||
|
||
// 从 1970 开始,意味着清除所有历史数据
|
||
let dateFrom = Date(timeIntervalSince1970: 0)
|
||
|
||
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: dateFrom) {
|
||
print("WebView data cleared")
|
||
}
|
||
}
|
||
|
||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||
if keyPath == "title" {
|
||
if navTitleShow {
|
||
navigationView.titleLabel.text = webView.title
|
||
}
|
||
} else if keyPath == "estimatedProgress" {
|
||
updateProgress(progress: webView.estimatedProgress)
|
||
} else {
|
||
}
|
||
}
|
||
|
||
func updateProgress(progress: Double) {
|
||
progressView.alpha = 1
|
||
// dlog("progress : \(progress)")
|
||
if progress > lastProgress {
|
||
progressView.setProgress(Float(progress), animated: true)
|
||
} else {
|
||
progressView.setProgress(Float(progress), animated: false)
|
||
}
|
||
|
||
lastProgress = progress
|
||
|
||
if progress >= 1 {
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||
self?.progressView.alpha = 0
|
||
self?.progressView.setProgress(0, animated: false)
|
||
self?.lastProgress = 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Handle Event
|
||
|
||
extension H5BaseViewController {
|
||
func handleRoute(msg: JSSDKMessage) {
|
||
if let uri = msg.uri?.urlValue {
|
||
navigator.open(uri)
|
||
}
|
||
}
|
||
|
||
func handleRequest(msg: JSSDKMessage) {
|
||
// let requestUri = msg.uri
|
||
// let params = msg.params
|
||
// for h5自主调用,静默请求...成功后执行成功回调,失败后执行error方法。
|
||
|
||
guard let uri = msg.uri else {
|
||
return
|
||
}
|
||
|
||
var requestAny: AnyCodable = AnyCodable(value: [:])
|
||
|
||
let token = UserCore.shared.token
|
||
if !token.isEmpty {
|
||
do {
|
||
// let body = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
||
// let str = String(data: body, encoding: .utf8)
|
||
var str = ""
|
||
if let dict = msg.params, let data2: Data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
|
||
let objcAnyCodable = try! JSONDecoder().decode(AnyCodable.self, from: data2)
|
||
let backToJson = try! JSONEncoder().encode(objcAnyCodable)
|
||
let jsonString = String(bytes: backToJson, encoding: .utf8)!
|
||
str = jsonString
|
||
}
|
||
|
||
// let key = (token + "AHkt5aUUtO6HZPid").md5().uppercased()
|
||
// let aes = try AES(key: key, iv: "HBB4UO5kEmM4169Z")
|
||
// let encrypted = try aes.encrypt(str.bytes)
|
||
// let result = encrypted.toBase64()
|
||
// let dic = ["key": result]
|
||
// dlog("⚠️ 加密前参数:\(str) \n⚠️ 加密结果:\(dic)")
|
||
// if let data3: Data = try? JSONSerialization.data(withJSONObject: dic, options: []) {
|
||
// let objcAnyCodable = try! JSONDecoder().decode(AnyCodable.self, from: data3)
|
||
// requestAny = objcAnyCodable
|
||
// }
|
||
} catch {
|
||
}
|
||
} else {
|
||
if let dict = msg.params, let data2: Data = try? JSONSerialization.data(withJSONObject: dict, options: []), let objcAnyCodable = try? JSONDecoder().decode(AnyCodable.self, from: data2) {
|
||
requestAny = objcAnyCodable
|
||
}
|
||
}
|
||
|
||
// let headers = HTTPHeaders(APIConfig.apiHeaders()!)
|
||
|
||
Hud.showIndicator()
|
||
dlog("☁️h5 request:\(uri) params: \(requestAny)")
|
||
// AF.request(uri, method: .post, parameters: requestAny, encoder: JSONParameterEncoder.default, headers: headers, interceptor: nil, requestModifier: nil).responseString { [weak self] response in
|
||
// // dlog("response: \(response)")
|
||
// self?.view.hideToastActivity()
|
||
// switch response.result {
|
||
// case let .success(model):
|
||
// guard let ltResponse: ResponseData = ResponseData<Dictionary<String, Any>>.deserialize(from: model) else {
|
||
// return
|
||
// }
|
||
//
|
||
// if ltResponse.status == "OK" {
|
||
// if let ltArrayResponse = ResponseData<Array<Dictionary<String, Any>>>.deserialize(from: model), let jsonDict = ltArrayResponse.content {
|
||
// if let data = try? JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted), let str = String(data: data, encoding: .utf8) {
|
||
// let jsonString = str // content2.toJSONString() ?? ""
|
||
// let js = "\(msg.success ?? "")((\(jsonString)))"
|
||
// dlog("✅ success call js:\(js)")
|
||
// self?.webView.evaluateJavaScript(js, completionHandler: { _, error in
|
||
// if error != nil {
|
||
// dlog("❌ exec js error: \(error?.localizedDescription ?? "")")
|
||
// }
|
||
// })
|
||
// return
|
||
// }
|
||
//
|
||
// } else if let content = ltResponse.content {
|
||
// let jsonString = content.toJSONString()
|
||
// let js = "\(msg.success ?? "")((\(jsonString ?? "")))"
|
||
// dlog("✅ success call js:\(js)")
|
||
// self?.webView.evaluateJavaScript(js, completionHandler: { _, error in
|
||
// if error != nil {
|
||
// dlog("❌ exec js error: \(error?.localizedDescription ?? "")")
|
||
// }
|
||
// })
|
||
// return
|
||
// }
|
||
//
|
||
// let js = "\(msg.success ?? "")()"
|
||
// dlog("✅ success call js no content:\(js)")
|
||
// self?.webView.evaluateJavaScript(js, completionHandler: { _, error in
|
||
// if error != nil {
|
||
// dlog("❌ exec js error: \(error?.localizedDescription ?? "")")
|
||
// }
|
||
// })
|
||
// } else {
|
||
// // --- 接口错误
|
||
// let js = "\(msg.error ?? "")((\(model)))"
|
||
// dlog("❌ api error: \(js)")
|
||
// self?.webView.evaluateJavaScript(js, completionHandler: nil)
|
||
// }
|
||
//
|
||
// break
|
||
// default:
|
||
// // --- 网络等错误
|
||
// UIWindow.key?.makeToast(R.string.localizable.internet_connect_failed.localized())
|
||
// let js = "\(msg.error ?? "")()"
|
||
// dlog("❌ api network error: \(js)")
|
||
// self?.webView.evaluateJavaScript(js, completionHandler: nil)
|
||
// break
|
||
// }
|
||
// }
|
||
}
|
||
|
||
func handleLoading(msg: JSSDKMessage) {
|
||
if msg.status {
|
||
UIWindow.key?.makeToastActivity()
|
||
|
||
} else {
|
||
UIWindow.key?.hideToastActivity()
|
||
}
|
||
}
|
||
|
||
func handleGetUserInfo(msg: JSSDKMessage) {
|
||
}
|
||
|
||
func handleModal(msg: JSSDKMessage) {
|
||
// 取决于msg.type. 暂无
|
||
}
|
||
|
||
func handleOpenBrowser(msg: JSSDKMessage) {
|
||
if let uri = msg.uri, uri.count > 0, let url = URL(string: uri) {
|
||
UIApplication.shared.open(url, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: false], completionHandler: nil)
|
||
}
|
||
}
|
||
|
||
func handleGetAppversion(msg: JSSDKMessage) {
|
||
let version = Bundle.appVersion
|
||
let dict = ["version": version]
|
||
let data1 = try? JSONSerialization.data(withJSONObject: dict, options: [])
|
||
let dictJs = String(data: data1!, encoding: .utf8) ?? ""
|
||
// dict.toJSONString() ?? ""
|
||
let js = "\(msg.success ?? "")((\(dictJs)))"
|
||
dlog("✅ success call js:\(js)")
|
||
webView.evaluateJavaScript(js, completionHandler: { _, error in
|
||
if error != nil {
|
||
dlog("❌ exec js error: \(error?.localizedDescription ?? "")")
|
||
}
|
||
})
|
||
}
|
||
|
||
private func handleDealSendTrack(msg: JSSDKMessage) {
|
||
if let trackName = msg.name {
|
||
var params = msg.params ?? [:]
|
||
if let uid = UserCore.shared.user?.userId {
|
||
// params.updateValue(uid, forKey: "userId")
|
||
}
|
||
// AppAnalytics.commonRecord(trackName, parameters: params)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 🔥 WKScriptMessageHandler
|
||
|
||
extension H5BaseViewController: WKScriptMessageHandler {
|
||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||
dlog("☁️☁️ h5 message name: \(message.name) message body: \(message.body)")
|
||
let msgName = message.name
|
||
guard let body = message.body as? String else {
|
||
// elog("h5 message failed! : \(msgName)")
|
||
return
|
||
}
|
||
|
||
guard let msg = CodableHelper.decode(JSSDKMessage.self, from: body) else {
|
||
return
|
||
}
|
||
|
||
if msgName == "route" {
|
||
msg.msgName = msgName
|
||
handleRoute(msg: msg)
|
||
} else if msgName == "request" {
|
||
msg.msgName = msgName
|
||
handleRequest(msg: msg)
|
||
|
||
} else if msgName == "setLoading" {
|
||
msg.msgName = msgName
|
||
handleLoading(msg: msg)
|
||
|
||
} else if msgName == "closeWebview" {
|
||
close()
|
||
} else if msgName == "getUserInfo" {
|
||
msg.msgName = msgName
|
||
handleGetUserInfo(msg: msg)
|
||
|
||
} else if msgName == "modal" {
|
||
msg.msgName = msgName
|
||
handleModal(msg: msg)
|
||
|
||
} else if msgName == "share" {
|
||
// to do.
|
||
} else if msgName == "init" {
|
||
handleInit(msg: msg)
|
||
|
||
} else if msgName == "openBrowser" {
|
||
msg.msgName = msgName
|
||
handleOpenBrowser(msg: msg)
|
||
|
||
} else if msgName == "getAppVersion" {
|
||
msg.msgName = msgName
|
||
handleGetAppversion(msg: msg)
|
||
|
||
} else if msgName == "sendTrack" {
|
||
msg.msgName = msgName
|
||
handleDealSendTrack(msg: msg)
|
||
|
||
} else {
|
||
// Please upgrade to the latest version
|
||
dlog("🛎Please upgrade to the latest version,Unsupported protocol.")
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - WKNavigationDelegate`
|
||
|
||
extension H5BaseViewController: WKNavigationDelegate {
|
||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||
guard let urlRequest = navigationAction.request.url?.absoluteString.removingPercentEncoding else {
|
||
decisionHandler(.cancel)
|
||
return
|
||
}
|
||
dlog("☁️ webview load: \(urlRequest) ☁️")
|
||
if urlRequest.hasPrefix(AppConst.schemePrefix) {
|
||
navigator.open(urlRequest)
|
||
|
||
} else if urlRequest.hasPrefix("mailto:") { // open system to send email.
|
||
if let url = URL(string: urlRequest) {
|
||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||
}
|
||
}
|
||
|
||
decisionHandler(.allow)
|
||
}
|
||
|
||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||
dlog("didFinish 🔥🔥🔥\(String(describing: webView.url))")
|
||
|
||
closeButton.isHidden = !webView.canGoBack
|
||
}
|
||
}
|