diff --git a/iBox/Sources/Error/ErrorPageView.swift b/iBox/Sources/Error/ErrorPageView.swift new file mode 100644 index 0000000..bfa39bd --- /dev/null +++ b/iBox/Sources/Error/ErrorPageView.swift @@ -0,0 +1,111 @@ +// +// ErrorPageView.swift +// iBox +// +// Created by Chan on 4/18/24. +// + +import UIKit + +import SnapKit + +class ErrorPageView: UIView { + private var imageViews: [UIImageView] = [] + private let images = ["fox_page0", "fox_page1", "fox_page2", "fox_page3", "fox_page4"] + private var timer: Timer? + + let messageLabel = UILabel() + + let backButton = UIButton() + let retryButton = UIButton() + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + setupLayout() + changeImages() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + backgroundColor = .clear + + messageLabel.textAlignment = .center + messageLabel.numberOfLines = 0 + + retryButton.setTitle("무시하기", for: .normal) + retryButton.backgroundColor = .systemBlue + retryButton.setTitleColor(.white, for: .normal) + retryButton.layer.cornerRadius = 10 + + addSubview(messageLabel) + addSubview(retryButton) + + for imageName in images { + let imageView = UIImageView(image: UIImage(named: imageName)) + imageView.contentMode = .scaleAspectFit + imageView.isHidden = true + imageView.tintColor = .box2 + addSubview(imageView) + imageViews.append(imageView) + } + + changeImages() + + } + + private func setupLayout() { + + imageViews.forEach { imageView in + imageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview() + make.leading.greaterThanOrEqualToSuperview().offset(20) + make.trailing.lessThanOrEqualToSuperview().offset(-20) + make.width.height.equalTo(32) + } + } + + messageLabel.snp.makeConstraints { make in + make.top.equalTo(imageViews[0].snp.bottom).offset(20) + make.centerX.equalToSuperview() + make.leading.greaterThanOrEqualToSuperview().offset(20) + make.trailing.lessThanOrEqualToSuperview().offset(-20) + } + + retryButton.snp.makeConstraints { make in + make.top.equalTo(messageLabel.snp.bottom).offset(20) + make.centerX.equalToSuperview() + make.width.equalTo(100) + make.height.equalTo(44) + } + } + + func configure(with error: Error, url: String) { + messageLabel.text = "\(url): \n해당 주소를 불러오는데 실패했어요!" + print(error.localizedDescription) + } + + private func changeImages() { + var currentIndex = 0 + + timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] timer in + guard let self = self else { return } + + let state = AppStateManager.shared.isVersionCheckCompleted + if state == .success || state == .later || state == .maxRetryReached { + timer.invalidate() + self.timer = nil + return + } + + self.imageViews.forEach { $0.isHidden = true } + self.imageViews[currentIndex].isHidden = false + + currentIndex = (currentIndex + 1) % self.imageViews.count + } + } +} diff --git a/iBox/Sources/Error/ErrorPageViewController.swift b/iBox/Sources/Error/ErrorPageViewController.swift new file mode 100644 index 0000000..04837c5 --- /dev/null +++ b/iBox/Sources/Error/ErrorPageViewController.swift @@ -0,0 +1,67 @@ +// +// ErrorPageViewController.swift +// iBox +// +// Created by Chan on 4/18/24. +// + +import UIKit +import WebKit + +protocol ErrorPageControllerDelegate: AnyObject { + func presentErrorPage(_ errorPage: ErrorPageViewController) +} + +protocol WebViewErrorDelegate { + func webView(_ webView: WebView, didFailWithError error: Error, url: URL?) +} + +class ErrorPageViewController: UIViewController { + weak var delegate: ErrorPageControllerDelegate? + var webView: WebView? + + init(webView: WebView) { + super.init(nibName: nil, bundle: nil) + self.webView = webView + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + self.view = ErrorPageView() + } + + override func viewDidLoad() { + super.viewDidLoad() + if let errorPageView = view as? ErrorPageView { + errorPageView.retryButton.addTarget(self, action: #selector(retryButtonTapped), for: .touchUpInside) + } + } + + func configureWithError(_ error: Error, url: String) { + if let errorPageView = view as? ErrorPageView { + errorPageView.configure(with: error, url: url) + } + } + + @objc private func retryButtonTapped() { + webView?.retryLoading() + dismiss(animated: true) + } + + func handleError(_ error: Error, _ url: URL?) { + self.modalPresentationStyle = .overFullScreen + self.configureWithError(error, url: url?.absoluteString ?? "") + delegate?.presentErrorPage(self) + } +} + +extension ErrorPageViewController: WebViewErrorDelegate { + + func webView(_ webView: WebView, didFailWithError error: Error, url: URL?) { + handleError(error, url) + } + +} diff --git a/iBox/Sources/Web/WebView.swift b/iBox/Sources/Web/WebView.swift index 0572dc6..13d48ae 100644 --- a/iBox/Sources/Web/WebView.swift +++ b/iBox/Sources/Web/WebView.swift @@ -13,6 +13,8 @@ import SnapKit class WebView: UIView { var delegate: WebViewDelegate? + var errorDelegate: WebViewErrorDelegate? + var lastRequestedURL: URL? private var progressObserver: NSKeyValueObservation? @@ -26,7 +28,7 @@ class WebView: UIView { private var isActive = false // MARK: - UI Components - + private let webView:WKWebView @@ -164,6 +166,10 @@ extension WebView: WKNavigationDelegate { } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + // 마지막으로 시도한 navigation url + lastRequestedURL = navigationAction.request.url + // "새 창으로 열기" 링크 WebView 내에서 열기 if navigationAction.targetFrame == nil { webView.load(navigationAction.request) @@ -171,6 +177,24 @@ extension WebView: WKNavigationDelegate { decisionHandler(.allow) } + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + // 초기 로드시 에러 발생 + errorDelegate?.webView(self, didFailWithError: error, url: selectedWebsite) + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + // 네비게이션 중 에러 발생 시 + if let lastURL = lastRequestedURL { + errorDelegate?.webView(self, didFailWithError: error, url: lastURL) + } else { + // lastRequestedURL이 nil인 경우 대비 + errorDelegate?.webView(self, didFailWithError: error, url: nil) + } + } + + func retryLoading() { + webView.reload() + } } extension WebView: UIScrollViewDelegate { diff --git a/iBox/Sources/Web/WebViewController.swift b/iBox/Sources/Web/WebViewController.swift index 4c09737..c67e266 100644 --- a/iBox/Sources/Web/WebViewController.swift +++ b/iBox/Sources/Web/WebViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import WebKit protocol WebViewDelegate { func pushAddBookMarkViewController(url: URL) @@ -13,6 +14,7 @@ protocol WebViewDelegate { class WebViewController: BaseViewController, BaseViewControllerProtocol { + var errorViewController: ErrorPageViewController? var delegate: AddBookmarkViewControllerProtocol? var selectedWebsite: URL? @@ -22,11 +24,8 @@ class WebViewController: BaseViewController, BaseViewControllerProtocol super.viewDidLoad() setupNavigationBar() - view.backgroundColor = .backgroundColor - - guard let contentView = contentView as? WebView else { return } - contentView.delegate = self - contentView.selectedWebsite = selectedWebsite + setupView() + setupDelegate() } override func viewDidLayoutSubviews() { @@ -40,7 +39,20 @@ class WebViewController: BaseViewController, BaseViewControllerProtocol func setupNavigationBar() { setNavigationBarHidden(true) } + + func setupDelegate() { + guard let contentView = contentView as? WebView else { return } + contentView.delegate = self + contentView.selectedWebsite = selectedWebsite + errorViewController = ErrorPageViewController(webView: contentView) + contentView.errorDelegate = errorViewController + errorViewController?.delegate = self + } + + func setupView() { + view.backgroundColor = .backgroundColor + } } extension WebViewController: WebViewDelegate { @@ -53,7 +65,11 @@ extension WebViewController: WebViewDelegate { AddBookmarkManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) } } - } - +} + +extension WebViewController: ErrorPageControllerDelegate { + func presentErrorPage(_ errorPage: ErrorPageViewController) { + self.present(errorPage, animated: true, completion: nil) + } }