[] MainWebVC 완성, 기본적인 웹 뷰 기능 구현

This commit is contained in:
김선규 2024-10-24 02:06:23 +09:00
parent d9911cc30b
commit c9fb3cf50d
4 changed files with 333 additions and 4 deletions

View File

@ -7,7 +7,48 @@
- none - none
## 일지 ## 일지
### 24.10.21 ### 24.10.21
<details>
<summary><em>일지</em></summary>
- 프로젝트 시작 - 프로젝트 시작
- swift, UIKit 채택
- 코드베이스 프로젝트 생성
- 스토리보드 제거
- import snapKit
- Prefix 파일 추가
- 구현을 미리 해두어 자주 사용하거나 메서드들 정의된 코드 추가
- 싱글턴 클래스 추가
- 싱글턴 클래스를 하나 추가하여 전역적인 처리가 필요한 부분에 대한 코드들을 정의해둠
- IntroVC
- 실질적으로 MainWebVC로 넘어가기 전에 처리할 내용들을 정의함
1. 탈옥 기기 확인
2. 네트워크 이상 없는지 확인
3. 버전 체크 확인
- 버전 체크 같은 경우는 서버가 필수적으로 있어야 하므로 해당 부분에 대한 서버가 없다면 해당 메서드를 삭제해야 함
4. 위치정보확인
- 앱에서 기본적으로 자주 사용되는 위치, 푸시 등의 기능 중 일단 위치 정보를 받아옴
</details>
---
### 24.10.22
<details>
<summary><em>일지</em></summary>
- MainWebVC
- webView 설정
- 브릿지 방식으로 연결해서 사용하는 WebView 시스템 구축
- 위에 새로 openWeb으로 띄우는 경우 x 로 close 버튼까지 구현
- WebView Base 완료
</details>

View File

@ -45,12 +45,10 @@ class IntroVC: UIViewController {
private func checkAppVersion() { private func checkAppVersion() {
Task { Task {
do { do {
// VER_URL, VER_PARAM, VER_HEADERS
let result = try await CommonUtils.shared.afGET(url:VER_URL, let result = try await CommonUtils.shared.afGET(url:VER_URL,
param: VER_PARAM, param: VER_PARAM,
headers: VER_HEADERS) headers: VER_HEADERS)
// let result = """
// {"status":{"code":"000","message":""},"data":{"finalVer":"2.0.8","forceVer":"2.0.6","forceUpdtYn":"P","remark":"","checkVer":"9.9.9"}}
// """
printLog("Success : \(result)") printLog("Success : \(result)")
let response = CommonUtils.shared.jsonToType("\(result)", as: VersionResponse.self) let response = CommonUtils.shared.jsonToType("\(result)", as: VersionResponse.self)

View File

@ -11,9 +11,299 @@ import WebKit
import SnapKit import SnapKit
class MainWebVC: UIViewController { 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() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.view.backgroundColor = .white
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 {
}
} }
} }