Visual_Novel_iOS/crush/Crush/Src/Components/Base/H5BaseViewController.swift

490 lines
18 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// 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)
// 3webView
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
}
}