[✨] MainWebVC 완성, 기본적인 웹 뷰 기능 구현
This commit is contained in:
parent
d9911cc30b
commit
c9fb3cf50d
41
README.md
41
README.md
|
@ -7,7 +7,48 @@
|
|||
- none
|
||||
|
||||
## 일지
|
||||
|
||||
### 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>
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user