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
1 change: 1 addition & 0 deletions iBox/Sources/BoxList/BoxListViewController.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -156,6 +156,7 @@ extension BoxListViewController: BoxListViewDelegate {
} else {
// 캐시에 없는 경우, 새로운 viewController 인스턴스를 생성하고 캐시에 추가합니다.
let viewController = WebViewController()
viewController.delegate = self
viewController.selectedWebsite = url
viewController.title = name
WebCacheManager.shared.cacheData(forKey: id, viewController: viewController)
Expand Down
2 changes: 1 addition & 1 deletion iBox/Sources/Extension/UIColor+Extension.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ extension UIColor {
static let box = UIColor(hex: 0xFF7F29)
static let box2 = UIColor(hex: 0xFF9548)
static let box3 = UIColor(hex: 0xFFDC6E)
static let tableViewBackgroundColor = color(light: .systemGroupedBackground, dark: .systemGray5)
static let tableViewBackgroundColor = color(light: .systemGroupedBackground, dark: .systemGray4)
static let folderGray = color(light: .systemGray3, dark: .systemGray2)
static let webIconColor = color(light: .black, dark: .systemGray)
static let dimmedViewColor = UIColor.black.withAlphaComponent(0.75)
Expand Down
1 change: 1 addition & 0 deletions iBox/Sources/Extension/UIFont+Extension.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,5 +17,6 @@ extension UIFont {
static let cellTitleFont = UIFont.systemFont(ofSize: 16.0)
static let cellDescriptionFont = UIFont.systemFont(ofSize: 13.0, weight: .regular)
static let descriptionFont = UIFont.systemFont(ofSize: 14.0)
static let refreshControlFont = UIFont.boldSystemFont(ofSize: 12.0)

}
119 changes: 119 additions & 0 deletions iBox/Sources/Web/RefreshControl.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
//
// RefreshControl.swift
// iBox
//
// Created by jiyeon on 4/4/24.
//

import UIKit

import SnapKit

enum RefreshControlType {
case addBookmark
case refresh
case back
}

class RefreshControl: UIView {

var currentType: RefreshControlType?

// MARK: - UI Components

let addBookmarkButton = UIButton().then {
$0.configuration = .plain()
$0.tintColor = .label
$0.configuration?.image = UIImage(systemName: "bookmark.circle")
$0.configuration?.imagePadding = 10
$0.configuration?.imagePlacement = .top
$0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 20.0)
$0.configuration?.attributedTitle = AttributedString("북마크 추가", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.refreshControlFont]))
$0.layer.cornerRadius = 15
}

let refreshButton = UIButton().then {
$0.configuration = .plain()
$0.tintColor = .label
$0.configuration?.image = UIImage(systemName: "arrow.clockwise.circle")
$0.configuration?.imagePadding = 10
$0.configuration?.imagePlacement = .top
$0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 20.0)
$0.configuration?.attributedTitle = AttributedString("새로고침", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.refreshControlFont]))
$0.layer.cornerRadius = 15
}

let backButton = UIButton().then {
$0.configuration = .plain()
$0.tintColor = .label
$0.configuration?.image = UIImage(systemName: "arrowshape.turn.up.backward.circle")
$0.configuration?.imagePadding = 10
$0.configuration?.imagePlacement = .top
$0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 20.0)
$0.configuration?.attributedTitle = AttributedString("처음으로 이동", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.refreshControlFont]))
$0.layer.cornerRadius = 15
}

let stackView = UIStackView().then {
$0.axis = .horizontal
$0.distribution = .fillEqually
$0.spacing = 20
}


// MARK: - Initializer

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

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MAKR: - Setup Methods

private func setupProperty() {
backgroundColor = .backgroundColor
isUserInteractionEnabled = true
}

private func setupHierarchy() {
addSubview(stackView)
stackView.addArrangedSubview(addBookmarkButton)
stackView.addArrangedSubview(refreshButton)
stackView.addArrangedSubview(backButton)
}

private func setupLayout() {
stackView.snp.makeConstraints { make in
make.leading.bottom.trailing.equalToSuperview().inset(20)
}
}

func setSelected(_ type: RefreshControlType) {
if type == currentType { return }
currentType = type
clear()
switch type {
case .addBookmark: addBookmarkButton.backgroundColor = .tableViewBackgroundColor
case .refresh: refreshButton.backgroundColor = .tableViewBackgroundColor
case .back: backButton.backgroundColor = .tableViewBackgroundColor
}
if UserDefaultsManager.isHaptics {
let generator = UIImpactFeedbackGenerator(style: .light)
generator.prepare()
generator.impactOccurred()
}
}

func clear() {
[addBookmarkButton, refreshButton, backButton].forEach { button in
button.backgroundColor = .clear
}
}

}
83 changes: 75 additions & 8 deletions iBox/Sources/Web/WebView.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ import SnapKit

class WebView: UIView {

var delegate: WebViewDelegate?

private var progressObserver: NSKeyValueObservation?

var selectedWebsite: URL? {
Expand All@@ -20,19 +22,22 @@ class WebView: UIView {
}
}

private var refreshControlHeight: CGFloat = 120.0
private var isActive = false

// MARK: - UI Components


private let webView:WKWebView

private let refreshControl = UIRefreshControl()

private let progressView = UIProgressView().then {
$0.progressViewStyle = .bar
$0.tintColor = .label
$0.sizeToFit()
}

private var refreshControl: RefreshControl?

// MARK: - Initializer

override init(frame: CGRect) {
Expand DownExpand Up@@ -64,8 +69,6 @@ class WebView: UIView {
private func setupProperty() {
backgroundColor = .backgroundColor
webView.navigationDelegate = self
webView.scrollView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(handleRefreshControl), for: .valueChanged)
progressObserver = webView.observe(\.estimatedProgress, options: .new) { [weak self] webView, _ in
self?.progressView.setProgress(Float(webView.estimatedProgress), animated: true)
}
Expand All@@ -86,16 +89,59 @@ class WebView: UIView {
}
}

func setupRefreshControl() {
// pan gesture
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleSwipe))
panGestureRecognizer.delegate = self // UIGestureRecognizerDelegate
addGestureRecognizer(panGestureRecognizer)
// refresh control
let refreshControl = RefreshControl(frame: .init(x: 0, y: -frame.size.height, width: frame.size.width, height: frame.size.height))
webView.scrollView.addSubview(refreshControl)
webView.scrollView.backgroundColor = .backgroundColor
webView.scrollView.delegate = self // UIScrollViewDelegate
self.refreshControl = refreshControl
}

private func loadWebsite() {
guard let url = selectedWebsite else { return }
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}

@objc private func handleRefreshControl() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { [weak self] in
self?.webView.reload()
self?.refreshControl.endRefreshing()
@objc func handleSwipe(_ gesture: UIPanGestureRecognizer) {
guard isActive, let refreshControl = refreshControl else { return }

let translation = gesture.translation(in: self)
if gesture.state == .changed {
if abs(translation.x) > 60.0 {
if translation.x > 0 { // 오른쪽 스와이프 : 처음 북마크로 돌아가기
refreshControl.setSelected(.back)
} else { // 왼쪽 스와이프 : 현재 링크 북마크 추가
refreshControl.setSelected(.addBookmark)
}
} else { // 아래 : 새로고침
refreshControl.setSelected(.refresh)
}
} else if gesture.state == .ended { // 사용자의 터치가 끝났을 때
switch refreshControl.currentType {
case .addBookmark:
guard let url = webView.url else { return }
delegate?.pushAddBookMarkViewController(url: url)
case .refresh:
self.webView.reload()
case .back:
loadWebsite()
case .none: break
}
// 제스처 완료 후 초기화
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
refreshControl.clear()
}
}
// 제스처 초기화
if gesture.state == .ended || gesture.state == .cancelled {
gesture.setTranslation(CGPoint.zero, in: self)
refreshControl.currentType = nil
}
}

Expand All@@ -116,5 +162,26 @@ extension WebView: WKNavigationDelegate {
self.progressView.isHidden = true
}
}

}

extension WebView: UIScrollViewDelegate {

func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < -refreshControlHeight {
isActive = true
} else {
isActive = false
}
}

}

extension WebView: UIGestureRecognizerDelegate {

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 다른 제스처 인식기와 동시에 인식되도록 허용
return true
}

}
27 changes: 27 additions & 0 deletions iBox/Sources/Web/WebViewController.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,13 @@

import UIKit

protocol WebViewDelegate {
func pushAddBookMarkViewController(url: URL)
}

class WebViewController: BaseViewController<WebView>, BaseViewControllerProtocol {

var delegate: AddBookmarkViewControllerProtocol?
var selectedWebsite: URL?

// MARK: - Life Cycle
Expand All@@ -20,13 +25,35 @@ class WebViewController: BaseViewController<WebView>, BaseViewControllerProtocol
view.backgroundColor = .backgroundColor

guard let contentView = contentView as? WebView else { return }
contentView.delegate = self
contentView.selectedWebsite = selectedWebsite
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let contentView = contentView as? WebView else { return }
contentView.setupRefreshControl()
}

// MARK: - BaseViewControllerProtocol

func setupNavigationBar() {
setNavigationBarHidden(true)
}

}

extension WebViewController: WebViewDelegate {

func pushAddBookMarkViewController(url: URL) {
URLDataManager.shared.incomingData = url.absoluteString

let addBookmarkViewController = AddBookmarkViewController()
addBookmarkViewController.delegate = delegate

let navigationController = UINavigationController(rootViewController: addBookmarkViewController)
navigationController.modalPresentationStyle = .pageSheet
self.present(navigationController, animated: true, completion: nil)
}

}