WebApp_UIKit_Base/WebAppUIKitBase/Scene/MainWebVC.swift

310 lines
13 KiB
Swift

//
// MainWebVC.swift
// WebAppUIKitBase
//
// Created by Sean Kim on 10/21/24.
//
import UIKit
import WebKit
import SnapKit
class MainWebVC: UIViewController {
var webView, openView: WKWebView?
// iOS
private lazy var closeBtnView: UIView = {
let view = UIView()
var xBtn: UIButton = {
let btn = UIButton()
btn.setImage(UIImage(systemName: "xmark"), for: .normal)
btn.addTarget(self, action: #selector(tappedCloseBtn), for: .touchUpInside)
return btn
}()
view.backgroundColor = .white
[
xBtn
].forEach{view.addSubview($0)}
xBtn.snp.makeConstraints {
$0.top.equalToSuperview().offset(8)
$0.bottom.equalToSuperview().offset(-8)
$0.trailing.equalToSuperview().offset(16)
$0.height.equalTo(32)
}
return view
}()
// close
@objc func tappedCloseBtn() {
if openView != nil {
self.closeBtnView.removeFromSuperview()
self.openView?.removeFromSuperview()
self.openView = nil
}
}
override func viewDidLoad() {
super.viewDidLoad()
let contentController = self.bridgeWebKit()
let configuration = WKWebViewConfiguration()
URLCache.shared.removeAllCachedResponses()
URLCache.shared.diskCapacity = 0
URLCache.shared.memoryCapacity = 0
configuration.userContentController = contentController
self.webView = WKWebView(frame: .zero, configuration: configuration)
guard let webView = self.webView else { return }
// , ,
// WKNavigationDelegate
webView.navigationDelegate = self
//
webView.allowsLinkPreview = false
// - true =
webView.clipsToBounds = true
// , ,
// WKUIDelegate
webView.uiDelegate = self
//
webView.scrollView.bounces = false
// ( )
webView.scrollView.alwaysBounceVertical = false
// .
// / .
webView.configuration.userContentController.addUserScript(self.getZoomDisableScript())
// JS
webView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
#if DEBUG
// webview inspector :
webView.isInspectable = true
#endif
// WEB_SERVER URL
if let url = URL(string: WEB_SERVER) {
let request = URLRequest(url: url)
webView.load(request)
}
self.view.addSubview(webView)
webView.snp.makeConstraints {
$0.edges.equalTo(self.view.safeAreaLayoutGuide)
}
// (foreground
NotificationCenter.default.addObserver(self, selector: #selector(backForeground(noti:)), name: UIApplication.didBecomeActiveNotification, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.disableDragAndDropInteraction()
}
// MARK: - 릿
// 릿
private func bridgeWebKit() -> WKUserContentController {
let contentController = WKUserContentController()
contentController.add(self, name: "name")
return contentController
}
@objc func backForeground(noti: Notification) {
if noti.name.rawValue == UIApplication.didBecomeActiveNotification.rawValue {
// foreground
} else {
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
//
preferences.allowsContentJavaScript = true
decisionHandler(.allow, preferences)
}
private func getZoomDisableScript() -> WKUserScript {
let source: String = "var meta = document.createElement('meta');" +
"meta.name = 'viewport';" +
"meta.content = 'width=device-width, initial-scale=1.0, maximum- scale=1.0, user-scalable=no';" +
"var head = document.getElementsByTagName('head')[0];" + "head.appendChild(meta);"
return WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
}
//
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
guard let noDragWebView = webView else { return }
webScrollView = noDragWebView.subviews.compactMap { $0 as? UIScrollView }.first
contentView = webScrollView?.subviews.first(where: { $0.interactions.count > 1 })
guard let dragInteraction = (contentView?.interactions.compactMap { $0 as? UIDragInteraction }.first) else { return }
contentView?.removeInteraction(dragInteraction)
}
}
// MARK: - WKUIDelegate
extension MainWebVC : WKUIDelegate{
// JS alert()
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
makeUIAlert(title: message, message: "", okTitle: "확인"){ _ in
completionHandler()
}
}
//JS confirm() .
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
makeUITwoAlert(title: message, message: "",
okTitle: "취소",okAction: {_ in
completionHandler(false)
},
cancelTitle: "확인"){ _ in
completionHandler(true)
}
}
// URL .
func openExternalLink(urlStr: String, _ handler: (() -> Void)? = nil) {
guard let url = URL(string: urlStr) else { return }
UIApplication.shared.open(url, options: [:]) { _ in handler?() }
}
// . e.g. JS window.open()
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
// view
let frame = UIScreen.main.bounds
self.openView = WKWebView(frame: frame, configuration: configuration)
guard let openView = self.openView else { return nil }
openView.navigationDelegate = self
openView.uiDelegate = self
//
[
self.closeBtnView,
openView
].forEach{view.addSubview($0)}
self.closeBtnView.snp.makeConstraints {
$0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
}
openView.snp.makeConstraints{
$0.top.equalTo(self.closeBtnView.snp.bottom)
$0.bottom.leading.trailing.equalToSuperview()
}
return openView
}
//
func webViewDidClose(_ webView: WKWebView) {
if webView == self.openView {
self.openView?.removeFromSuperview()
self.openView = nil
}
view.subviews.forEach {
if $0 == self.closeBtnView {
self.closeBtnView.removeFromSuperview()
}
}
}
}
// MARK: - WKNavigationDelegate
extension MainWebVC: WKNavigationDelegate {
// ,
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let scheme = navigationAction.request.url?.scheme,
scheme != "http" && scheme != "https" {
printLog("SCHEME: \(scheme)")
// .
if let openApp = navigationAction.request.url,
UIApplication.shared.canOpenURL(openApp){
UIApplication.shared.open(openApp, options: [:], completionHandler: nil)
} else {
//
}
decisionHandler(WKNavigationActionPolicy.cancel)
return
} else {
decisionHandler(WKNavigationActionPolicy.allow)
}
return
}
// -> ,
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
printLog("WebView = \(webView), navigation = \(navigation)")
}
// ->
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: any Error) {
printLog("WebView Load fail = \(error)")
}
// -> ,
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
printLog("WebView Content start loading")
}
// ->
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
printLog("WebView Page Loaded")
//
let javascriptStyle = "var css = '*:not(input, textarea){-webkit-touch-callout:none;-webkit-user-select:none}'; var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); head.appendChild(style);"
webView.evaluateJavaScript(javascriptStyle)
}
// ->
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: any Error) {
makeUIAlert(title: "서비스 연결 문제 발생",
message: """
일시적인 장애 또는 네트워크 문제로 서비스에 연결하지 못하였습니다.
문제가 계속될 경우 고객센터로 문의해 주세요.
""",
okTitle: "확인") { _ in
exit(1)
}
}
}
// MARK: - WKScriptMessageHandler
// 릿
extension MainWebVC: WKScriptMessageHandler{
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
printLog("Message name: \(message.name)")
printLog("Message body type: \(type(of: message.body))")
printLog("Message body: \(message.body as? String)")
// MARK: name - Description / Parameter: JSON String / Script: O
if message.name == "name", let body = message.body as? String {
}
}
}