-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Custom WebView 작성 및 토큰 재발급 로직 작성, MDS 컬러 마이페이지 하위에 적용 #309
Changes from all commits
8029d91
a5efc65
ebde4ce
75859a8
d5932b8
ffe3826
3e49968
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// AppLifecycleAdapter.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by Ian on 12/2/23. | ||
// Copyright © 2023 SOPT-iOS. All rights reserved. | ||
// | ||
|
||
import Core | ||
import Networks | ||
|
||
import UIKit | ||
|
||
final public class AppLifecycleAdapter { | ||
private let authService: AuthService | ||
private let cancelBag = CancelBag() | ||
|
||
public init() { | ||
self.authService = DefaultAuthService() | ||
} | ||
} | ||
|
||
// MARK: - Public functions | ||
extension AppLifecycleAdapter { | ||
public func prepare() { | ||
self.onWillEnterForeground() | ||
self.onWillEnterBackground() | ||
} | ||
} | ||
|
||
// MARK: - Lifecycle Usecases | ||
extension AppLifecycleAdapter { | ||
private func onWillEnterForeground() { | ||
NotificationCenter.default | ||
.publisher(for: UIApplication.willEnterForegroundNotification) | ||
.subscribe(on: DispatchQueue.global()) | ||
.receive(on: DispatchQueue.main) | ||
.sink(receiveValue: { [weak self] _ in | ||
self?.reissureTokens() | ||
}).store(in: self.cancelBag) | ||
} | ||
|
||
private func onWillEnterBackground() { } | ||
} | ||
|
||
// MARK: - Private functions | ||
extension AppLifecycleAdapter { | ||
private func reissureTokens() { | ||
guard UserDefaultKeyList.Auth.appAccessToken != nil else { return } | ||
|
||
self.authService.reissuance { _ in } | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// | ||
// SOPTWebView.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by Ian on 11/30/23. | ||
// Copyright © 2023 SOPT-iOS. All rights reserved. | ||
// | ||
|
||
import Core | ||
import DSKit | ||
|
||
import UIKit | ||
import WebKit | ||
|
||
import SnapKit | ||
|
||
public final class SOPTWebView: UIViewController { | ||
private enum Metric { | ||
static let navigationBarHeight = 44.f | ||
} | ||
|
||
private lazy var navigationBar = WebViewNavigationBar(frame: self.view.frame) | ||
private let wkwebView: WKWebView | ||
|
||
// MARK: Variables | ||
private let cancelbag = CancelBag() | ||
private var barrier = false | ||
|
||
public init( | ||
config: WebViewConfig = WebViewConfig(), | ||
startWith url: URL | ||
) { | ||
let configuration = WKWebViewConfiguration().then { | ||
$0.allowsInlineMediaPlayback = config.allowsInlineMediaPlayback | ||
$0.mediaTypesRequiringUserActionForPlayback = config.mediaTypesRequiringUserActionForPlayback | ||
} | ||
|
||
self.wkwebView = WKWebView(frame: .zero, configuration: configuration).then { | ||
$0.allowsBackForwardNavigationGestures = config.allowsBackForwardNavigationGestures | ||
} | ||
|
||
super.init(nibName: nil, bundle: nil) | ||
|
||
DispatchQueue.main.async { | ||
let request = URLRequest(url: url) | ||
self.wkwebView.load(request) | ||
} | ||
} | ||
|
||
public required init?(coder: NSCoder) { | ||
fatalError("coder initializer doesn't implemented.") | ||
} | ||
|
||
public override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
self.view.backgroundColor = DSKitAsset.Colors.black100.color | ||
|
||
self.wkwebView.scrollView.delegate = self | ||
self.wkwebView.navigationDelegate = self | ||
self.wkwebView.uiDelegate = self | ||
|
||
self.initializeViews() | ||
self.setupConstraints() | ||
self.setupNavigationButtonActions() | ||
} | ||
} | ||
|
||
extension SOPTWebView { | ||
private func initializeViews() { | ||
self.view.addSubviews(self.navigationBar, self.wkwebView) | ||
} | ||
|
||
private func setupConstraints() { | ||
self.navigationBar.snp.makeConstraints { | ||
$0.top.equalTo(self.view.safeAreaLayoutGuide.snp.top) | ||
$0.leading.trailing.equalToSuperview() | ||
$0.height.equalTo(Metric.navigationBarHeight) | ||
} | ||
|
||
self.wkwebView.snp.makeConstraints { | ||
$0.top.equalTo(self.navigationBar.snp.bottom) | ||
$0.leading.trailing.bottom.equalToSuperview() | ||
} | ||
} | ||
|
||
private func setupNavigationButtonActions() { | ||
self.navigationBar | ||
.signalForClickLeftButton() | ||
.sink { [weak self] _ in | ||
guard self?.wkwebView.canGoBack == true else { | ||
self?.navigationController?.popViewController(animated: true) | ||
return | ||
} | ||
|
||
self?.wkwebView.goBack() | ||
}.store(in: self.cancelbag) | ||
|
||
self.navigationBar | ||
.signalForClickRightButton() | ||
.sink { [weak self] _ in | ||
self?.navigationController?.popViewController(animated: true) | ||
}.store(in: self.cancelbag) | ||
} | ||
} | ||
|
||
extension SOPTWebView: WKNavigationDelegate { | ||
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { | ||
guard !self.barrier else { return } | ||
|
||
self.barrier = true | ||
self.wkwebView.evaluateJavaScript( | ||
"localStorage.setItem(\"serviceAccessToken\", \"\(UserDefaultKeyList.Auth.playgroundToken!)\")" | ||
) | ||
self.wkwebView.reload() | ||
} | ||
} | ||
|
||
extension SOPTWebView: WKUIDelegate { | ||
public func webView( | ||
_ webView: WKWebView, | ||
createWebViewWith configuration: WKWebViewConfiguration, | ||
for navigationAction: WKNavigationAction, | ||
windowFeatures: WKWindowFeatures | ||
) -> WKWebView? { | ||
if navigationAction.targetFrame == nil { | ||
webView.load(navigationAction.request) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
extension SOPTWebView: UIScrollViewDelegate { | ||
public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { | ||
scrollView.pinchGestureRecognizer?.isEnabled = false | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// | ||
// WebViewConfiguration.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by Ian on 11/30/23. | ||
// Copyright © 2023 SOPT-iOS. All rights reserved. | ||
// | ||
|
||
import WebKit | ||
|
||
public struct WebViewConfig { | ||
let javaScriptEnabled: Bool | ||
let allowsBackForwardNavigationGestures: Bool | ||
let allowsInlineMediaPlayback: Bool | ||
let mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes | ||
let isScrollEnabled: Bool | ||
|
||
public init( | ||
javaScriptEnabled: Bool = true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wkwebview javaScriptEnabled 속성에 대응 - 미사용 |
||
allowsBackForwardNavigationGestures: Bool = true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. navigation backswipe가 가능하도록 하는 프로퍼티. wkwebview allowsBackForward~ 에 대응 |
||
allowsInlineMediaPlayback: Bool = true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. default : false. false인 경우 동영상이 있으면 전체 화면으로 재생 됨. inline으로 재생할 수 있도록 하는 프로퍼티. |
||
mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes = [], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자 제스쳐가 필요한 미디어 타입. 없어도 됨 |
||
isScrollEnabled: Bool = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 웹뷰의 스크롤 가능 여부. |
||
) { | ||
self.javaScriptEnabled = javaScriptEnabled | ||
self.allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures | ||
self.allowsInlineMediaPlayback = allowsInlineMediaPlayback | ||
self.mediaTypesRequiringUserActionForPlayback = mediaTypesRequiringUserActionForPlayback | ||
self.isScrollEnabled = isScrollEnabled | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
barrier는 didFinish에 구현된 내용(토큰 주입)이 1회만 발생하기 위해 플래그를 생성한 걸까요~?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
맞아요~ 리액트가 실행되는 타이밍이 제각각이고 무한 리로드가 되는 경우가 생겨서 1회만 되도록 방지해 두었어요