Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion iBox/Sources/AppDelegate.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,7 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

versioningHandler.checkAppVersion { result in
AppStateManager.shared.isVersionCheckCompleted = result
AppStateManager.shared.versionCheckCompleted = result
}

return true
Expand Down
47 changes: 28 additions & 19 deletions iBox/Sources/BoxList/BoxListView.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,31 +378,40 @@ extension BoxListView: UITableViewDelegate {

func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { [weak self] () -> UIViewController? in
guard let self = self, let cellViewModel = self.viewModel?.boxList[indexPath.section].boxListCellViewModelsWithStatus[indexPath.row] else { return nil }

let id = cellViewModel.id
let url = cellViewModel.url
let name = cellViewModel.name

let previewViewController: UIViewController

if let cachedViewController = WebCacheManager.shared.viewControllerForKey(id) {
previewViewController = cachedViewController
} else {
let viewController = WebViewController()
viewController.selectedWebsite = url
viewController.title = name
WebCacheManager.shared.cacheData(forKey: id, viewController: viewController)
previewViewController = viewController
}

return previewViewController
guard let self = self else { return nil }
return self.createOrRetrievePreviewController(for: indexPath)
}, actionProvider: { suggestedActions in
return self.makeContextMenu(for: indexPath)
})
return configuration
}

private func createOrRetrievePreviewController(for indexPath: IndexPath) -> UIViewController? {
guard let cellViewModel = self.viewModel?.boxList[indexPath.section].boxListCellViewModelsWithStatus[indexPath.row] else { return nil }
let id = cellViewModel.id
let cachedViewController = WebCacheManager.shared.viewControllerForKey(id)

if let cachedViewController = cachedViewController, cachedViewController.errorViewController?.isHandlingError == nil {
return cachedViewController
}

if cachedViewController?.errorViewController?.isHandlingError ?? false {
WebCacheManager.shared.removeViewControllerForKey(id)
}

let newViewController = createWebViewController(with: cellViewModel)
WebCacheManager.shared.cacheData(forKey: id, viewController: newViewController)
return newViewController
}

private func createWebViewController(with viewModel: BoxListCellViewModel) -> WebViewController {
let viewController = WebViewController()
viewController.selectedWebsite = viewModel.url
viewController.title = viewModel.name
viewController.id = viewModel.id
return viewController
}

func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
guard let indexPath = configuration.identifier as? IndexPath, let cell = tableView.cellForRow(at: indexPath) else {
return nil
Expand Down
50 changes: 38 additions & 12 deletions iBox/Sources/BoxList/BoxListViewController.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,19 @@ class BoxListViewController: BaseViewController<BoxListView>, BaseViewController
contentView.viewModel?.input.send(.viewWillAppear)
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil) { _ in
self.dismissPreviewIfNeeded()
}
}

func dismissPreviewIfNeeded() {
if let previewVC = self.presentedViewController as? WebViewController {
previewVC.dismiss(animated: true, completion: nil)
}
}

// MARK: - BaseViewControllerProtocol

func setupNavigationBar() {
Expand DownExpand Up@@ -205,20 +218,33 @@ extension BoxListViewController: BoxListViewDelegate {
self.present(controller, animated: true)
}


func didSelectWeb(id: UUID, at url: URL, withName name: String) {
if let cachedViewController = WebCacheManager.shared.viewControllerForKey(id) {
// 이미 캐시에 존재한다면, 그 인스턴스를 재사용
navigationController?.pushViewController(cachedViewController, animated: true)
} else {
// 캐시에 없는 경우, 새로운 viewController 인스턴스를 생성하고 캐시에 추가합니다.
let viewController = WebViewController()
viewController.delegate = self
viewController.selectedWebsite = url
viewController.title = name
WebCacheManager.shared.cacheData(forKey: id, viewController: viewController)
navigationController?.pushViewController(viewController, animated: true)
let viewController = getOrCreateWebViewController(id: id, url: url, name: name)
navigationController?.pushViewController(viewController, animated: true)
}

private func getOrCreateWebViewController(id: UUID, url: URL, name: String) -> WebViewController {
let cachedViewController = WebCacheManager.shared.viewControllerForKey(id)

if let cachedViewController = cachedViewController, cachedViewController.errorViewController?.isHandlingError == nil {
return cachedViewController
}

if cachedViewController?.errorViewController?.isHandlingError ?? false {
WebCacheManager.shared.removeViewControllerForKey(id)
}

return createAndCacheWebViewController(id: id, url: url, name: name)
}

private func createAndCacheWebViewController(id: UUID, url: URL, name: String) -> WebViewController {
let viewController = WebViewController()
viewController.delegate = self
viewController.selectedWebsite = url
viewController.title = name
viewController.id = id
WebCacheManager.shared.cacheData(forKey: id, viewController: viewController)
return viewController
}

func pushViewController(type: EditType) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,7 +63,7 @@ class CustomLaunchScreenView: UIView {
timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] timer in
guard let self = self else { return }

let state = AppStateManager.shared.isVersionCheckCompleted
let state = AppStateManager.shared.versionCheckCompleted
if state == .success || state == .later || state == .maxRetryReached {
timer.invalidate()
self.timer = nil
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,7 @@ class CustomLaunchScreenViewController: UIViewController {

// MARK: - Custom Update Checker View (예정)
private func observeVersionCheckCompletion() {
AppStateManager.shared.$isVersionCheckCompleted
AppStateManager.shared.$versionCheckCompleted
.receive(on: DispatchQueue.main)
.sink { [weak self] result in
switch result {
Expand Down
18 changes: 18 additions & 0 deletions iBox/Sources/Error/ErrorCode.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
//
// ErrorCode.swift
// iBox
//
// Created by Chan on 4/23/24.
//

import Foundation

enum ViewErrorCode: Equatable {
case normal
case unknown
case webContentProcessTerminated
case webViewInvalidated
case javaScriptExceptionOccurred
case javaScriptResultTypeIsUnsupported
case networkError(URLError)
}
165 changes: 121 additions & 44 deletions iBox/Sources/Error/ErrorPageView.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,38 +12,89 @@ 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?
var timer: Timer?

let messageLabel = UILabel()
private let backPannelView = UIView().then {
$0.backgroundColor = .backgroundColor
}

private let messageLabel = UILabel().then {
$0.textAlignment = .center
$0.numberOfLines = 0
$0.lineBreakMode = .byTruncatingTail
}

private let problemLabel = UILabel().then {
$0.textAlignment = .center
$0.numberOfLines = 0
$0.text = "해당 주소에서 문제가 발생했습니다."
}

let backButton = UIButton().then {
$0.setTitle("나가기", for: .normal)
$0.backgroundColor = .box2
$0.setTitleColor(.white, for: .normal)
$0.layer.cornerRadius = 10
$0.addAnimationForStateChange(from: .box, to: .box2)
}

let retryButton = UIButton().then {
$0.setTitle("새로고침", for: .normal)
$0.backgroundColor = .systemGray
$0.setTitleColor(.white, for: .normal)
$0.layer.cornerRadius = 10
$0.addAnimationForStateChange(from: .box, to: .systemGray)
}

let closeButton = UIButton().then {
$0.setTitle("닫기", for: .normal)
$0.backgroundColor = .systemGray
$0.setTitleColor(.white, for: .normal)
$0.layer.cornerRadius = 10
$0.addAnimationForStateChange(from: .box, to: .systemGray)
}

let backButton = UIButton()
let retryButton = UIButton()

override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
setupProperty()
setupHierarchy()
setupAnimation()
setupLayout()
changeImages()
}

override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)

if newSuperview == nil {
timer?.invalidate()
timer = nil
}
}

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)

override func layoutSubviews() {
super.layoutSubviews()
backPannelView.roundCorners([.bottomLeft, .bottomRight], radius: 20)
}

private func setupProperty() {
changeImages()
}

private func setupHierarchy() {
addSubview(backPannelView)
backPannelView.addSubview(retryButton)
backPannelView.addSubview(closeButton)
backPannelView.addSubview(backButton)
backPannelView.addSubview(problemLabel)
backPannelView.addSubview(messageLabel)
}

private func setupAnimation() {
for imageName in images {
let imageView = UIImageView(image: UIImage(named: imageName))
imageView.contentMode = .scaleAspectFit
Expand All@@ -52,53 +103,79 @@ class ErrorPageView: UIView {
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)
}
backPannelView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.trailing.leading.equalToSuperview().inset(20)
make.height.equalToSuperview().multipliedBy(0.5)
}

messageLabel.snp.makeConstraints { make in
make.top.equalTo(imageViews[0].snp.bottom).offset(20)
retryButton.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(10)
make.centerX.equalToSuperview()
make.leading.greaterThanOrEqualToSuperview().offset(20)
make.trailing.lessThanOrEqualToSuperview().offset(-20)
make.width.equalTo(100)
make.height.equalTo(44)
}

retryButton.snp.makeConstraints { make in
make.top.equalTo(messageLabel.snp.bottom).offset(20)
make.centerX.equalToSuperview()
closeButton.snp.makeConstraints { make in
make.leading.equalTo(retryButton.snp.trailing).offset(20)
make.centerY.equalTo(retryButton.snp.centerY)
make.width.equalTo(100)
make.height.equalTo(44)
}

backButton.snp.makeConstraints { make in
make.trailing.equalTo(retryButton.snp.leading).offset(-20)
make.centerY.equalTo(retryButton.snp.centerY)
make.width.equalTo(100)
make.height.equalTo(44)
}

problemLabel.snp.makeConstraints { make in
make.bottom.equalTo(retryButton.snp.top).offset(-10)
make.leading.equalToSuperview().offset(20)
make.trailing.equalToSuperview().offset(-20)
}

messageLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(problemLabel.snp.top).offset(-10)
make.leading.equalToSuperview().offset(20)
make.trailing.equalToSuperview().offset(-20)
}

imageViews.forEach { imageView in
imageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(safeAreaLayoutGuide).offset(10)
make.bottom.equalTo(messageLabel.snp.top).offset(-10)
make.leading.greaterThanOrEqualToSuperview().offset(20)
make.trailing.lessThanOrEqualToSuperview().offset(-20)
make.width.height.equalTo(32)
}
}
}

func configure(with error: Error, url: String) {
messageLabel.text = "\(url): \n해당 주소를 불러오는데 실패했어요!"
messageLabel.text = "\(url)"
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 {
guard let self = self else {
timer.invalidate()
self.timer = nil
return
}

let state = AppStateManager.shared.currentViewErrorState

if state == .normal {
return
}

Expand Down
Loading