diff --git a/README.md b/README.md
index 7b934a3..7ab95c4 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,48 @@
- none
## 일지
+
### 24.10.21
+
+
+일지
+
- 프로젝트 시작
+ - swift, UIKit 채택
+ - 코드베이스 프로젝트 생성
+ - 스토리보드 제거
+ - import snapKit
+- Prefix 파일 추가
+ - 구현을 미리 해두어 자주 사용하거나 메서드들 정의된 코드 추가
+
+- 싱글턴 클래스 추가
+ - 싱글턴 클래스를 하나 추가하여 전역적인 처리가 필요한 부분에 대한 코드들을 정의해둠
+
+- IntroVC
+ - 실질적으로 MainWebVC로 넘어가기 전에 처리할 내용들을 정의함
+ 1. 탈옥 기기 확인
+ 2. 네트워크 이상 없는지 확인
+ 3. 버전 체크 확인
+ - 버전 체크 같은 경우는 서버가 필수적으로 있어야 하므로 해당 부분에 대한 서버가 없다면 해당 메서드를 삭제해야 함
+ 4. 위치정보확인
+ - 앱에서 기본적으로 자주 사용되는 위치, 푸시 등의 기능 중 일단 위치 정보를 받아옴
+
+
+
+---
+
+### 24.10.22
+
+
+일지
+
+- MainWebVC
+ - webView 설정
+ - 브릿지 방식으로 연결해서 사용하는 WebView 시스템 구축
+ - 위에 새로 openWeb으로 띄우는 경우 x 로 close 버튼까지 구현
+
+- WebView Base 완료
+
+
diff --git a/WebAppUIKitBase.xcodeproj/project.xcworkspace/xcuserdata/seankim.xcuserdatad/UserInterfaceState.xcuserstate b/WebAppUIKitBase.xcodeproj/project.xcworkspace/xcuserdata/seankim.xcuserdatad/UserInterfaceState.xcuserstate
index a0b0982..1f94ee0 100644
Binary files a/WebAppUIKitBase.xcodeproj/project.xcworkspace/xcuserdata/seankim.xcuserdatad/UserInterfaceState.xcuserstate and b/WebAppUIKitBase.xcodeproj/project.xcworkspace/xcuserdata/seankim.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/WebAppUIKitBase/Scene/IntroVC.swift b/WebAppUIKitBase/Scene/IntroVC.swift
index 35836a3..065313a 100644
--- a/WebAppUIKitBase/Scene/IntroVC.swift
+++ b/WebAppUIKitBase/Scene/IntroVC.swift
@@ -45,12 +45,10 @@ class IntroVC: UIViewController {
private func checkAppVersion() {
Task {
do {
+ // VER_URL, VER_PARAM, VER_HEADERS는 사용자의 설정에 맞춰야 함
let result = try await CommonUtils.shared.afGET(url:VER_URL,
param: VER_PARAM,
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)")
let response = CommonUtils.shared.jsonToType("\(result)", as: VersionResponse.self)
diff --git a/WebAppUIKitBase/Scene/MainWebVC.swift b/WebAppUIKitBase/Scene/MainWebVC.swift
index 5e19d60..83fd2dc 100644
--- a/WebAppUIKitBase/Scene/MainWebVC.swift
+++ b/WebAppUIKitBase/Scene/MainWebVC.swift
@@ -11,9 +11,299 @@ 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()
- 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 {
+ }
+
+
}
}