From 1272255035a585b241db9b568a0a084227e61116 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 19:52:22 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[Feat]=20#43=20-=20ViewPager=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 36 ++++ .../ViewPager/PagedView/CVC/PageCVC.swift | 42 +++++ .../ViewPager/PagedView/PagedView.swift | 116 ++++++++++++ .../UIComponents/ViewPager/ViewPager.swift | 176 ++++++++++++++++++ .../CourseStorage/VC/CourseStorageVC.swift | 50 ++++- 5 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/CVC/PageCVC.swift create mode 100644 Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/PagedView.swift create mode 100644 Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index e74cadf1..982b17a0 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -18,6 +18,9 @@ A3BC2F3D296468E500198261 /* UploadedCourseInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BC2F3C296468E500198261 /* UploadedCourseInfoModel.swift */; }; A3BC2F3F2964706100198261 /* UploadedCourseInfoCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BC2F3E2964706100198261 /* UploadedCourseInfoCVC.swift */; }; A3BC2F4129667A0D00198261 /* NicknameEditorVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BC2F4029667A0D00198261 /* NicknameEditorVC.swift */; }; + CE0C23742966D62A00B45063 /* PagedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0C23732966D62A00B45063 /* PagedView.swift */; }; + CE0C23772966D64D00B45063 /* PageCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0C23762966D64D00B45063 /* PageCVC.swift */; }; + CE0C23792966D6AF00B45063 /* ViewPager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0C23782966D6AF00B45063 /* ViewPager.swift */; }; CE0D9FD329648DA300CEB5CD /* CustomAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0D9FD229648DA300CEB5CD /* CustomAlertVC.swift */; }; CE146770296568DC00DCEA1B /* RunTrackingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE14676F296568DC00DCEA1B /* RunTrackingVC.swift */; }; CE14677829658C7200DCEA1B /* Stopwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE14677729658C7200DCEA1B /* Stopwatch.swift */; }; @@ -119,6 +122,9 @@ A3BC2F3C296468E500198261 /* UploadedCourseInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadedCourseInfoModel.swift; sourceTree = ""; }; A3BC2F3E2964706100198261 /* UploadedCourseInfoCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadedCourseInfoCVC.swift; sourceTree = ""; }; A3BC2F4029667A0D00198261 /* NicknameEditorVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameEditorVC.swift; sourceTree = ""; }; + CE0C23732966D62A00B45063 /* PagedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedView.swift; sourceTree = ""; }; + CE0C23762966D64D00B45063 /* PageCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageCVC.swift; sourceTree = ""; }; + CE0C23782966D6AF00B45063 /* ViewPager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPager.swift; sourceTree = ""; }; CE0D9FD229648DA300CEB5CD /* CustomAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlertVC.swift; sourceTree = ""; }; CE14676F296568DC00DCEA1B /* RunTrackingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunTrackingVC.swift; sourceTree = ""; }; CE14677729658C7200DCEA1B /* Stopwatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stopwatch.swift; sourceTree = ""; }; @@ -289,6 +295,32 @@ path = UploadedCourseInfoCollectionView; sourceTree = ""; }; + CE0C23712966D5FF00B45063 /* ViewPager */ = { + isa = PBXGroup; + children = ( + CE0C23782966D6AF00B45063 /* ViewPager.swift */, + CE0C23722966D62200B45063 /* PagedView */, + ); + path = ViewPager; + sourceTree = ""; + }; + CE0C23722966D62200B45063 /* PagedView */ = { + isa = PBXGroup; + children = ( + CE0C23752966D63C00B45063 /* CVC */, + CE0C23732966D62A00B45063 /* PagedView.swift */, + ); + path = PagedView; + sourceTree = ""; + }; + CE0C23752966D63C00B45063 /* CVC */ = { + isa = PBXGroup; + children = ( + CE0C23762966D64D00B45063 /* PageCVC.swift */, + ); + path = CVC; + sourceTree = ""; + }; CE14676C296568C000DCEA1B /* RunTracking */ = { isa = PBXGroup; children = ( @@ -661,6 +693,7 @@ CE6655B6295D803C00C64E12 /* UIComponents */ = { isa = PBXGroup; children = ( + CE0C23712966D5FF00B45063 /* ViewPager */, CEC2A6882962ADB900160BF7 /* MapView */, CEEC6B4A2961D89700D00E1E /* CustomNavigationBar.swift */, CEC2A6842961F92C00160BF7 /* CustomButton.swift */, @@ -932,6 +965,7 @@ DA20D847296697A600F1581F /* PlusDetailViewController.swift in Sources */, CE66560E295D92A500C64E12 /* setStatusBarBackgroundColor.swift in Sources */, CE9291292965E01D0010959C /* RNTimeFormatter.swift in Sources */, + CE0C23792966D6AF00B45063 /* ViewPager.swift in Sources */, CE6655D7295D86F900C64E12 /* String+.swift in Sources */, CE58759E29601476005D967E /* LoadingIndicator.swift in Sources */, CE5875A2296015A2005D967E /* NetworkLoggerPlugin.swift in Sources */, @@ -943,6 +977,7 @@ CE6655E0295D87D200C64E12 /* UIDevice+.swift in Sources */, CE17F0382961BF8B00E1DED0 /* FontLiterals.swift in Sources */, CE6655E8295D889600C64E12 /* UISwitch+.swift in Sources */, + CE0C23772966D64D00B45063 /* PageCVC.swift in Sources */, CE5875A029601500005D967E /* Toast.swift in Sources */, CE14677C2965C1B100DCEA1B /* RunningRecordVC.swift in Sources */, CE6655F6295D90B600C64E12 /* addToolBar.swift in Sources */, @@ -985,6 +1020,7 @@ DA20D8432966977D00F1581F /* SearchResultViewController.swift in Sources */, CE5875A4296015D2005D967E /* Encodable+.swift in Sources */, A3BC2F4129667A0D00198261 /* NicknameEditorVC.swift in Sources */, + CE0C23742966D62A00B45063 /* PagedView.swift in Sources */, CE14677A2965A80700DCEA1B /* CustomBottomSheetVC.swift in Sources */, CEEC6B4B2961D89700D00E1E /* CustomNavigationBar.swift in Sources */, CE17F02D2961BBA100E1DED0 /* ColorLiterals.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/CVC/PageCVC.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/CVC/PageCVC.swift new file mode 100644 index 00000000..be961b81 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/CVC/PageCVC.swift @@ -0,0 +1,42 @@ +// +// PageCVC.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/05. +// + +import UIKit + +final class PageCVC: UICollectionViewCell { + + // MARK: - UI Components + + public var view: UIView? { + didSet { + self.setLayout() + } + } + + // MARK: - Initialization + + override init(frame: CGRect) { + super.init(frame: frame) + self.setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UI & Layout + + private func setLayout() { + guard let view = view else { return } + + self.contentView.addSubview(view) + + view.snp.makeConstraints { make in + make.edges.equalTo(contentView) + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/PagedView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/PagedView.swift new file mode 100644 index 00000000..8a6f46a6 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/PagedView/PagedView.swift @@ -0,0 +1,116 @@ +// +// PagedView.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/05. +// + +import UIKit +import Combine + +final class PagedView: UIView { + + // MARK: - Properties + + var pages: [UIView] { + didSet { + self.collectionView.reloadData() + } + } + var movedPage = PassthroughSubject() + var percent = PassthroughSubject() + + // MARK: - UI Components + + private lazy var collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + let collectionView = UICollectionView( + frame: .zero, + collectionViewLayout: layout + ) + collectionView.isScrollEnabled = true + collectionView.showsHorizontalScrollIndicator = false + collectionView.isPagingEnabled = true + collectionView.register(PageCVC.self, forCellWithReuseIdentifier: PageCVC.className) + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + + // MARK: - Initialization + + init(pages: [UIView] = []) { + self.pages = pages + super.init(frame: .zero) + + self.setUI() + self.setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UI & Layout + private func setUI() { + collectionView.backgroundColor = .white + } + + private func setLayout() { + self.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + // MARK: - Methods + + public func moveToPage(at index: Int) { + self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true) + } +} + +// MARK: - UICollectionViewDelegateFlowLayout + +extension PagedView: UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + return CGSize(width: self.collectionView.frame.width, + height: self.collectionView.frame.height) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + let page = Int(self.collectionView.contentOffset.x / self.collectionView.frame.size.width) + self.movedPage.send(page) + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offsetX = scrollView.contentOffset.x + let contentSize = scrollView.contentSize.width + + self.percent.send(offsetX / contentSize) + } +} + +// MARK: - UICollectionViewDataSource + +extension PagedView: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return pages.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PageCVC.className, for: indexPath) as? PageCVC + else { return UICollectionViewCell() } + let page = self.pages[indexPath.item] + cell.view = page + return cell + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift new file mode 100644 index 00000000..69dd11d5 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift @@ -0,0 +1,176 @@ +// +// ViewPager.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/05. +// + +import UIKit +import Combine + +class ViewPager: UIView { + + // MARK: - Properties + + @Published var selectedTabIndex = 0 + private var cancelBag = CancelBag() + + // 탭이 클릭된 건지 판단하기 위한 변수(스와이프랑 구분하기 위함) + private var tappedButton: Bool = false + + // MARK: - UI Components + + private lazy var buttonStackView = UIStackView().then { + $0.distribution = .fillEqually + } + + private let barBackgroundView = UIView().then { + $0.backgroundColor = .clear + } + + private let barView = UIView().then { + $0.backgroundColor = .m1 + } + + private let bottomBorderView = UIView().then { + $0.backgroundColor = .g3 + } + + public let pagedView = PagedView() + + // MARK: - Initialization + + init(pageTitles: [String]) { + super.init(frame: .zero) + self.makeTabbedView(pageTitles: pageTitles) + self.setLayout() + self.bind() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension ViewPager { + + /// paging할 UIView 추가 + @discardableResult + func addPagedView(pagedView: [UIView]) -> Self { + self.pagedView.pages += pagedView + return self + } + + private func makeTabbedView(pageTitles: [String]) { + for (index, pageTitle) in pageTitles.enumerated() { + let button = UIButton() + button.setAttributedTitle(NSAttributedString(string: pageTitle, attributes: [.font: UIFont.h5, .foregroundColor: UIColor.m1]), for: .selected) + button.setAttributedTitle(NSAttributedString(string: pageTitle, attributes: [.font: UIFont.b3, .foregroundColor: UIColor.g2]), for: .normal) + button.tag = index + button.addTarget(self, action: #selector(tabButtonTapped), for: .touchUpInside) + buttonStackView.addArrangedSubview(button) + } + } + + private func moveBar(index: Int) { + var leadingConstant: CGFloat = 0 + let buttonWidth = self.buttonStackView.arrangedSubviews[0].frame.width + leadingConstant += (buttonWidth * CGFloat(index)) + + UIView.animate(withDuration: 0.5) { + self.barView.snp.updateConstraints { make in + make.leading.equalToSuperview().offset(leadingConstant) + } + self.barBackgroundView.layoutIfNeeded() + } + + selectedTabIndex = index + tappedButton = false + } +} + +// MARK: - @objc Function + +extension ViewPager { + @objc private func tabButtonTapped(_ sender: UIButton) { + let index = sender.tag + guard index != selectedTabIndex else { return } // 이미 선택되어 있는 페이지일 경우 이동 X + + tappedButton = true + + moveBar(index: index) + pagedView.moveToPage(at: index) + } +} + +// MARK: - Bind + +extension ViewPager { + private func bind() { + pagedView.movedPage.sink { [weak self] index in + guard let self = self, !self.tappedButton else { return } + self.selectedTabIndex = index + }.store(in: cancelBag) + + pagedView.percent.sink { [weak self] percent in + guard let self = self, !self.tappedButton else { return } + let leadingContraints = self.barBackgroundView.frame.width * percent + self.barView.snp.updateConstraints { make in + make.leading.equalToSuperview().offset(leadingContraints) + } + }.store(in: cancelBag) + + $selectedTabIndex.sink { index in + for (i, tabView) in self.buttonStackView.arrangedSubviews.enumerated() { + guard let button = tabView as? UIButton else { return } + button.isSelected = (i == index) + } + }.store(in: cancelBag) + } +} + +// MARK: - UI & Layout + +extension ViewPager { + private func setUI() { + self.backgroundColor = .w1 + } + + private func setLayout() { + self.addSubviews(buttonStackView, barBackgroundView, pagedView) + barBackgroundView.addSubviews(bottomBorderView, barView) + + buttonStackView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(38) + } + + barBackgroundView.snp.makeConstraints { make in + make.height.equalTo(2) + make.bottom.equalTo(buttonStackView.snp.bottom) + make.leading.trailing.equalToSuperview().inset(16) + } + + bottomBorderView.snp.makeConstraints { make in + make.top.equalTo(barBackgroundView.snp.bottom).inset(1) + make.leading.trailing.equalToSuperview() + make.height.equalTo(1) + } + + guard let button = buttonStackView.arrangedSubviews.first as? UIButton else { return } + + barView.snp.makeConstraints { make in + make.leading.top.bottom.equalToSuperview() + make.width.equalTo(button.snp.width) + } + + pagedView.snp.makeConstraints { make in + make.top.equalTo(barBackgroundView.snp.bottom) + make.leading.trailing.equalToSuperview() + make.bottom.equalToSuperview() + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift index cfee89aa..1a43e8a9 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift @@ -8,9 +8,55 @@ import UIKit final class CourseStorageVC: UIViewController { - + + // MARK: - UI Components + + private lazy var naviBar = CustomNavigationBar(self, type: .title).setTitle("보관함") + + private let myCourseView = UIView().then { + $0.backgroundColor = .w1 + } + + private let scrapCourseView = UIView().then { + $0.backgroundColor = .w1 + } + + private lazy var viewPager = ViewPager(pageTitles: ["내가 그린 코스", "스크랩 코스"]) + .addPagedView(pagedView: [myCourseView, scrapCourseView]) + + // MARK: - View Life Cycle + override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .g2 + self.setUI() + self.setLayout() + } +} + +// MARK: - Methods + +extension CourseStorageVC { + +} + +// MARK: - UI & Layout + +extension CourseStorageVC { + private func setUI() { + view.backgroundColor = .w1 + } + + private func setLayout() { + view.addSubviews(naviBar, viewPager) + + naviBar.snp.makeConstraints { make in + make.leading.top.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(48) + } + + viewPager.snp.makeConstraints { make in + make.top.equalTo(naviBar.snp.bottom) + make.leading.bottom.trailing.equalTo(view.safeAreaLayoutGuide) + } } } From 1cc3b4f652cdb9dae1456114166c25ce9113a458 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 20:07:11 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[Chore]=20#43=20-=20ViewPager=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/ViewPager/ViewPager.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift index 69dd11d5..1d1a0beb 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ViewPager/ViewPager.swift @@ -25,7 +25,8 @@ class ViewPager: UIView { } private let barBackgroundView = UIView().then { - $0.backgroundColor = .clear + $0.backgroundColor = .w1 + $0.clipsToBounds = true } private let barView = UIView().then { @@ -79,7 +80,7 @@ extension ViewPager { let buttonWidth = self.buttonStackView.arrangedSubviews[0].frame.width leadingConstant += (buttonWidth * CGFloat(index)) - UIView.animate(withDuration: 0.5) { + UIView.animate(withDuration: 0.4, delay: 0, options: [.curveLinear]) { self.barView.snp.updateConstraints { make in make.leading.equalToSuperview().offset(leadingConstant) } From 371a378c96f2d83aad290b226c69326e134e3342 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Fri, 6 Jan 2023 01:33:26 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[Feat]=20#43=20-=20CourseListCVC=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B3=B4=EA=B4=80=ED=95=A8=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B7=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 40 ++++- .../CourseStorage/VC/CourseStorageVC.swift | 10 +- .../Presentation/CourseStorage/Views/.gitkeep | 0 .../Views/CVC/CourseListCVC.swift | 157 ++++++++++++++++++ .../PrivateCourseListView.swift | 109 ++++++++++++ .../CourseListView/ScrapCourseListView.swift | 109 ++++++++++++ .../VC/CountDownVC.swift | 0 .../VC/RunTrackingVC.swift | 0 .../VC/RunningRecordVC.swift | 0 9 files changed, 411 insertions(+), 14 deletions(-) delete mode 100644 Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/.gitkeep create mode 100644 Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CVC/CourseListCVC.swift create mode 100644 Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift create mode 100644 Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift rename Runnect-iOS/Runnect-iOS/Presentation/{CourseDrawing => Running}/VC/CountDownVC.swift (100%) rename Runnect-iOS/Runnect-iOS/Presentation/{RunTracking => Running}/VC/RunTrackingVC.swift (100%) rename Runnect-iOS/Runnect-iOS/Presentation/{CourseDrawing => Running}/VC/RunningRecordVC.swift (100%) diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 982b17a0..a75f188e 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -82,6 +82,9 @@ CE665610295D92C200C64E12 /* setTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE66560F295D92C200C64E12 /* setTextLineHeight.swift */; }; CE665612295D92E400C64E12 /* UserDefaultWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE665611295D92E400C64E12 /* UserDefaultWrapper.swift */; }; CE665615295D989A00C64E12 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = CE665614295D989A00C64E12 /* .swiftlint.yml */; }; + CE6B63D02967230D003F900F /* PrivateCourseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63CF2967230D003F900F /* PrivateCourseListView.swift */; }; + CE6B63D3296725E6003F900F /* CourseListCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63D2296725E6003F900F /* CourseListCVC.swift */; }; + CE6B63D6296731F9003F900F /* ScrapCourseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63D5296731F9003F900F /* ScrapCourseListView.swift */; }; CE9291252965C9FB0010959C /* CourseDetailInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */; }; CE9291272965D0ED0010959C /* StatsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291262965D0ED0010959C /* StatsInfoView.swift */; }; CE9291292965E01D0010959C /* RNTimeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291282965E01D0010959C /* RNTimeFormatter.swift */; }; @@ -193,6 +196,9 @@ CE66560F295D92C200C64E12 /* setTextLineHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = setTextLineHeight.swift; sourceTree = ""; }; CE665611295D92E400C64E12 /* UserDefaultWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultWrapper.swift; sourceTree = ""; }; CE665614295D989A00C64E12 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; + CE6B63CF2967230D003F900F /* PrivateCourseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateCourseListView.swift; sourceTree = ""; }; + CE6B63D2296725E6003F900F /* CourseListCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseListCVC.swift; sourceTree = ""; }; + CE6B63D5296731F9003F900F /* ScrapCourseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrapCourseListView.swift; sourceTree = ""; }; CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailInfoView.swift; sourceTree = ""; }; CE9291262965D0ED0010959C /* StatsInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsInfoView.swift; sourceTree = ""; }; CE9291282965E01D0010959C /* RNTimeFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNTimeFormatter.swift; sourceTree = ""; }; @@ -209,7 +215,6 @@ CEEC6B3B2961C51A00D00E1E /* CourseStorageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseStorageVC.swift; sourceTree = ""; }; CEEC6B3D2961C53700D00E1E /* CourseDiscoveryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDiscoveryVC.swift; sourceTree = ""; }; CEEC6B3F2961C55000D00E1E /* MyPageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageVC.swift; sourceTree = ""; }; - CEEC6B422961C59600D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CEEC6B432961C59F00D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CEEC6B442961C5A800D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CEEC6B452961C5B200D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; @@ -321,19 +326,21 @@ path = CVC; sourceTree = ""; }; - CE14676C296568C000DCEA1B /* RunTracking */ = { + CE14676C296568C000DCEA1B /* Running */ = { isa = PBXGroup; children = ( CE14676E296568CD00DCEA1B /* Views */, CE14676D296568CA00DCEA1B /* VC */, ); - path = RunTracking; + path = Running; sourceTree = ""; }; CE14676D296568CA00DCEA1B /* VC */ = { isa = PBXGroup; children = ( + CEB8416F2963360800BF8080 /* CountDownVC.swift */, CE14676F296568DC00DCEA1B /* RunTrackingVC.swift */, + CE14677B2965C1B100DCEA1B /* RunningRecordVC.swift */, ); path = VC; sourceTree = ""; @@ -416,8 +423,6 @@ CEEC6B392961C4F300D00E1E /* CourseDrawingHomeVC.swift */, CEC2A6912962BE2900160BF7 /* DepartureSearchVC.swift */, CE29D581296402B500F47542 /* CourseDrawingVC.swift */, - CEB8416F2963360800BF8080 /* CountDownVC.swift */, - CE14677B2965C1B100DCEA1B /* RunningRecordVC.swift */, ); path = VC; sourceTree = ""; @@ -441,7 +446,8 @@ CE17F0422961C3D300E1DED0 /* Views */ = { isa = PBXGroup; children = ( - CEEC6B422961C59600D00E1E /* .gitkeep */, + CE6B63D4296731D8003F900F /* CourseListView */, + CE6B63D1296725BD003F900F /* CVC */, ); path = Views; sourceTree = ""; @@ -542,7 +548,7 @@ CE17F03C2961C32C00E1DED0 /* CourseDiscovery */, CE17F03B2961C2F700E1DED0 /* MyPage */, CE17F03E2961C38100E1DED0 /* CourseDetail */, - CE14676C296568C000DCEA1B /* RunTracking */, + CE14676C296568C000DCEA1B /* Running */, ); path = Presentation; sourceTree = ""; @@ -779,6 +785,23 @@ path = "UIKit+"; sourceTree = ""; }; + CE6B63D1296725BD003F900F /* CVC */ = { + isa = PBXGroup; + children = ( + CE6B63D2296725E6003F900F /* CourseListCVC.swift */, + ); + path = CVC; + sourceTree = ""; + }; + CE6B63D4296731D8003F900F /* CourseListView */ = { + isa = PBXGroup; + children = ( + CE6B63CF2967230D003F900F /* PrivateCourseListView.swift */, + CE6B63D5296731F9003F900F /* ScrapCourseListView.swift */, + ); + path = CourseListView; + sourceTree = ""; + }; CEC2A6882962ADB900160BF7 /* MapView */ = { isa = PBXGroup; children = ( @@ -966,6 +989,7 @@ CE66560E295D92A500C64E12 /* setStatusBarBackgroundColor.swift in Sources */, CE9291292965E01D0010959C /* RNTimeFormatter.swift in Sources */, CE0C23792966D6AF00B45063 /* ViewPager.swift in Sources */, + CE6B63D02967230D003F900F /* PrivateCourseListView.swift in Sources */, CE6655D7295D86F900C64E12 /* String+.swift in Sources */, CE58759E29601476005D967E /* LoadingIndicator.swift in Sources */, CE5875A2296015A2005D967E /* NetworkLoggerPlugin.swift in Sources */, @@ -985,6 +1009,7 @@ CE6655F0295D891B00C64E12 /* UITextView+.swift in Sources */, CEC2A6922962BE2900160BF7 /* DepartureSearchVC.swift in Sources */, CE6655EE295D88E600C64E12 /* UITextField+.swift in Sources */, + CE6B63D6296731F9003F900F /* ScrapCourseListView.swift in Sources */, DA20D841296696C300F1581F /* MapCollectionViewCell.swift in Sources */, CE6655F8295D90CF00C64E12 /* adjusted+.swift in Sources */, CE4545CB295D7AF4003201E1 /* SceneDelegate.swift in Sources */, @@ -1002,6 +1027,7 @@ CEEC6B3A2961C4F300D00E1E /* CourseDrawingHomeVC.swift in Sources */, CEC2A6902962B06C00160BF7 /* convertLocationObject.swift in Sources */, CEC2A6852961F92C00160BF7 /* CustomButton.swift in Sources */, + CE6B63D3296725E6003F900F /* CourseListCVC.swift in Sources */, CE29D584296416D800F47542 /* caculateStatusBarHeight.swift in Sources */, CE66560C295D928300C64E12 /* setRootViewController.swift in Sources */, CE6655D9295D871B00C64E12 /* URL+.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift index 1a43e8a9..1f8f3d00 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift @@ -13,16 +13,12 @@ final class CourseStorageVC: UIViewController { private lazy var naviBar = CustomNavigationBar(self, type: .title).setTitle("보관함") - private let myCourseView = UIView().then { - $0.backgroundColor = .w1 - } + private let privateCourseListView = PrivateCourseListView() - private let scrapCourseView = UIView().then { - $0.backgroundColor = .w1 - } + private let scrapCourseListView = ScrapCourseListView() private lazy var viewPager = ViewPager(pageTitles: ["내가 그린 코스", "스크랩 코스"]) - .addPagedView(pagedView: [myCourseView, scrapCourseView]) + .addPagedView(pagedView: [privateCourseListView, scrapCourseListView]) // MARK: - View Life Cycle diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/.gitkeep b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CVC/CourseListCVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CVC/CourseListCVC.swift new file mode 100644 index 00000000..8dca7b92 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CVC/CourseListCVC.swift @@ -0,0 +1,157 @@ +// +// CourseListCVC.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/06. +// + +import UIKit + +protocol CourseListCVCDeleagte: AnyObject { + func likeButtonTapped(wantsTolike: Bool) +} + +@frozen +enum CourseListCVCType { + case title + case titleWithLocation + case all + + static func getCellHeight(type: CourseListCVCType, cellWidth: CGFloat) -> CGFloat { + let imageHeight = cellWidth * (124/174) + switch type { + case .title: + return imageHeight + 24 + case .titleWithLocation, .all: + return imageHeight + 40 + } + } +} + +final class CourseListCVC: UICollectionViewCell { + + // MARK: - Properties + + weak var delegate: CourseListCVCDeleagte? + + // MARK: - UI Components + + private let courseImageView = UIImageView().then { + $0.backgroundColor = .g3 + $0.contentMode = .scaleToFill + $0.layer.cornerRadius = 5 + } + + private let titleLabel = UILabel().then { + $0.text = "제목" + $0.font = .b4 + $0.textColor = .g1 + } + + private let locationLabel = UILabel().then { + $0.text = "위치" + $0.font = .b6 + $0.textColor = .g2 + } + + private lazy var labelStackView = UIStackView( + arrangedSubviews: [titleLabel, locationLabel] + ).then { + $0.axis = .vertical + $0.alignment = .leading + } + + private let likeButton = UIButton(type: .custom).then { + $0.setImage(ImageLiterals.icHeartFill, for: .selected) + $0.setImage(ImageLiterals.icHeart, for: .normal) + $0.isSelected = true + $0.backgroundColor = .w1 + } + + // MARK: - initialization + + override init(frame: CGRect) { + super.init(frame: frame) + self.setUI() + self.setLayout() + self.setAddTarget() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension CourseListCVC { + private func setAddTarget() { + likeButton.addTarget(self, action: #selector(likeButtonDidTap), for: .touchUpInside) + } + + func setData(imageURL: String, title: String, location: String?, didLike: Bool?) { + self.courseImageView.setImage(with: imageURL) + self.titleLabel.text = title + + if let location = location { + self.locationLabel.text = location + } + + if let didLike = didLike { + self.likeButton.isSelected = didLike + } + } +} + +// MARK: - @objc Function + +extension CourseListCVC { + @objc func likeButtonDidTap(_ sender: UIButton) { + sender.isSelected.toggle() + delegate?.likeButtonTapped(wantsTolike: (sender.isSelected == true)) + } +} + +// MARK: - UI & Layout + +extension CourseListCVC { + private func setUI() { + self.contentView.backgroundColor = .w1 + } + + private func setLayout() { + self.contentView.addSubviews(courseImageView, labelStackView, likeButton) + + courseImageView.snp.makeConstraints { make in + make.leading.top.trailing.equalToSuperview() + let imageHeight = contentView.frame.width * (124/174) + make.height.equalTo(imageHeight) + } + + likeButton.snp.makeConstraints { make in + make.top.equalTo(courseImageView.snp.bottom).offset(7) + make.trailing.equalToSuperview() + make.width.height.equalTo(14) + } + + labelStackView.snp.makeConstraints { make in + make.top.equalTo(courseImageView.snp.bottom).offset(4) + make.leading.equalToSuperview() + make.width.equalTo(courseImageView.snp.width).multipliedBy(0.7) + } + } + + func setCellType(type: CourseListCVCType) { + switch type { + case .title: + self.locationLabel.isHidden = true + self.likeButton.isHidden = true + case .titleWithLocation: + self.locationLabel.isHidden = false + self.likeButton.isHidden = true + case .all: + self.locationLabel.isHidden = false + self.likeButton.isHidden = false + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift new file mode 100644 index 00000000..83b97125 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift @@ -0,0 +1,109 @@ +// +// PrivateCourseListView.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/06. +// + +import UIKit + +final class PrivateCourseListView: UIView { + + // MARK: - Properties + + final let collectionViewInset = UIEdgeInsets(top: 28, left: 16, bottom: 28, right: 16) + final let itemSpacing: CGFloat = 10 + final let lineSpacing: CGFloat = 20 + + // MARK: - UI Components + + private let collectionViewLayout = UICollectionViewFlowLayout().then { + $0.scrollDirection = .vertical + } + + private lazy var courseListCollectionView = UICollectionView( + frame: .zero, + collectionViewLayout: collectionViewLayout + ).then { + $0.backgroundColor = .clear + } + + // MARK: - initialization + + init() { + super.init(frame: .zero) + self.setUI() + self.setLayout() + self.setDelegate() + self.register() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension PrivateCourseListView { + private func setDelegate() { + courseListCollectionView.delegate = self + courseListCollectionView.dataSource = self + } + + private func register() { + courseListCollectionView.register(CourseListCVC.self, + forCellWithReuseIdentifier: CourseListCVC.className) + } +} + +// MARK: - UI & Layout + +extension PrivateCourseListView { + private func setUI() { + self.backgroundColor = .w1 + } + + private func setLayout() { + self.addSubviews(courseListCollectionView) + courseListCollectionView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading.bottom.trailing.equalToSuperview() + } + } +} + +extension PrivateCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 15 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CourseListCVC.className, + for: indexPath) + as? CourseListCVC else { return UICollectionViewCell() } + cell.setCellType(type: .title) + return cell + } +} + +extension PrivateCourseListView: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let cellWidth = (UIScreen.main.bounds.width - (self.itemSpacing + 2*self.collectionViewInset.left)) / 2 + let cellHeight = CourseListCVCType.getCellHeight(type: .title, cellWidth: cellWidth) + + return CGSize(width: cellWidth, height: cellHeight) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return self.collectionViewInset + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return self.itemSpacing + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return self.lineSpacing + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift new file mode 100644 index 00000000..3ae8f98a --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift @@ -0,0 +1,109 @@ +// +// ScrapCourseListView.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/06. +// + +import UIKit + +final class ScrapCourseListView: UIView { + + // MARK: - Properties + + final let collectionViewInset = UIEdgeInsets(top: 28, left: 16, bottom: 28, right: 16) + final let itemSpacing: CGFloat = 10 + final let lineSpacing: CGFloat = 20 + + // MARK: - UI Components + + private let collectionViewLayout = UICollectionViewFlowLayout().then { + $0.scrollDirection = .vertical + } + + private lazy var courseListCollectionView = UICollectionView( + frame: .zero, + collectionViewLayout: collectionViewLayout + ).then { + $0.backgroundColor = .clear + } + + // MARK: - initialization + + init() { + super.init(frame: .zero) + self.setUI() + self.setLayout() + self.setDelegate() + self.register() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension ScrapCourseListView { + private func setDelegate() { + courseListCollectionView.delegate = self + courseListCollectionView.dataSource = self + } + + private func register() { + courseListCollectionView.register(CourseListCVC.self, + forCellWithReuseIdentifier: CourseListCVC.className) + } +} + +// MARK: - UI & Layout + +extension ScrapCourseListView { + private func setUI() { + self.backgroundColor = .w1 + } + + private func setLayout() { + self.addSubviews(courseListCollectionView) + courseListCollectionView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading.bottom.trailing.equalToSuperview() + } + } +} + +extension ScrapCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 15 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CourseListCVC.className, + for: indexPath) + as? CourseListCVC else { return UICollectionViewCell() } + cell.setCellType(type: .all) + return cell + } +} + +extension ScrapCourseListView: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let cellWidth = (UIScreen.main.bounds.width - (self.itemSpacing + 2*self.collectionViewInset.left)) / 2 + let cellHeight = CourseListCVCType.getCellHeight(type: .all, cellWidth: cellWidth) + + return CGSize(width: cellWidth, height: cellHeight) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return self.collectionViewInset + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return self.itemSpacing + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return self.lineSpacing + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CountDownVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/CountDownVC.swift similarity index 100% rename from Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CountDownVC.swift rename to Runnect-iOS/Runnect-iOS/Presentation/Running/VC/CountDownVC.swift diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunTrackingVC.swift similarity index 100% rename from Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift rename to Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunTrackingVC.swift diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningRecordVC.swift similarity index 100% rename from Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift rename to Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningRecordVC.swift From 58d4cf3ae83ff4fb56c576f28683bd59dea93bb3 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Fri, 6 Jan 2023 01:56:19 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[Feat]=20#43=20-=20ListEmptyView=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 4 + .../Global/UIComponents/ListEmptyView.swift | 95 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index a75f188e..9dd7465a 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ CE6B63D02967230D003F900F /* PrivateCourseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63CF2967230D003F900F /* PrivateCourseListView.swift */; }; CE6B63D3296725E6003F900F /* CourseListCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63D2296725E6003F900F /* CourseListCVC.swift */; }; CE6B63D6296731F9003F900F /* ScrapCourseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63D5296731F9003F900F /* ScrapCourseListView.swift */; }; + CE6B63D829673450003F900F /* ListEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6B63D729673450003F900F /* ListEmptyView.swift */; }; CE9291252965C9FB0010959C /* CourseDetailInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */; }; CE9291272965D0ED0010959C /* StatsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291262965D0ED0010959C /* StatsInfoView.swift */; }; CE9291292965E01D0010959C /* RNTimeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291282965E01D0010959C /* RNTimeFormatter.swift */; }; @@ -199,6 +200,7 @@ CE6B63CF2967230D003F900F /* PrivateCourseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateCourseListView.swift; sourceTree = ""; }; CE6B63D2296725E6003F900F /* CourseListCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseListCVC.swift; sourceTree = ""; }; CE6B63D5296731F9003F900F /* ScrapCourseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrapCourseListView.swift; sourceTree = ""; }; + CE6B63D729673450003F900F /* ListEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListEmptyView.swift; sourceTree = ""; }; CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailInfoView.swift; sourceTree = ""; }; CE9291262965D0ED0010959C /* StatsInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsInfoView.swift; sourceTree = ""; }; CE9291282965E01D0010959C /* RNTimeFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNTimeFormatter.swift; sourceTree = ""; }; @@ -707,6 +709,7 @@ CE1467792965A80700DCEA1B /* CustomBottomSheetVC.swift */, CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */, CE9291262965D0ED0010959C /* StatsInfoView.swift */, + CE6B63D729673450003F900F /* ListEmptyView.swift */, ); path = UIComponents; sourceTree = ""; @@ -1004,6 +1007,7 @@ CE0C23772966D64D00B45063 /* PageCVC.swift in Sources */, CE5875A029601500005D967E /* Toast.swift in Sources */, CE14677C2965C1B100DCEA1B /* RunningRecordVC.swift in Sources */, + CE6B63D829673450003F900F /* ListEmptyView.swift in Sources */, CE6655F6295D90B600C64E12 /* addToolBar.swift in Sources */, CEC2A68A2962ADCD00160BF7 /* RNMapView.swift in Sources */, CE6655F0295D891B00C64E12 /* UITextView+.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift new file mode 100644 index 00000000..7ebd4585 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift @@ -0,0 +1,95 @@ +// +// ListEmptyView.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/06. +// + +import UIKit + +protocol ListEmptyViewDelegate: AnyObject { + func emptyViewButtonTapped() +} + +final class ListEmptyView: UIView { + + // MARK: - Properties + + weak var delegate: ListEmptyViewDelegate? + + // MARK: - UI Components + + private let mainImageView = UIImageView().then { + $0.image = ImageLiterals.imgStorage + $0.clipsToBounds = true + } + + private let descriptionLabel = UILabel().then { + $0.font = .b4 + $0.textColor = .g2 + $0.numberOfLines = 0 + } + + private let bottomButton = CustomButton(title: "코스 그리기") + + private lazy var containerStackView = UIStackView( + arrangedSubviews: [mainImageView, descriptionLabel, bottomButton] + ).then { + $0.axis = .vertical + $0.alignment = .center + $0.spacing = 22 + } + + // MARK: - initialization + + init(description: String, buttonTitle: String) { + super.init(frame: .zero) + self.setUI(description: description, buttonTitle: buttonTitle) + self.setLayout() + self.setAddTarget() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension ListEmptyView { + private func setAddTarget() { + bottomButton.addTarget(self, action: #selector(bottomButtonDidTap), for: .touchUpInside) + } +} + +// MARK: - @objc Function + +extension ListEmptyView { + @objc private func bottomButtonDidTap() { + delegate?.emptyViewButtonTapped() + } +} + +// MARK: - UI & Layout + +extension ListEmptyView { + private func setUI(description: String, buttonTitle: String) { + self.backgroundColor = .clear + + self.descriptionLabel.text = description + self.bottomButton.titleLabel?.text = buttonTitle + } + + private func setLayout() { + self.addSubviews(containerStackView) + + bottomButton.snp.makeConstraints { make in + make.height.equalTo(40) + make.leading.trailing.equalToSuperview() + } + + containerStackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} From e7a7d40901f201ea688ae84e0fa76d7bb47791b4 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Fri, 6 Jan 2023 01:56:54 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[Feat]=20#43=20-=20PrivateCourseListView?= =?UTF-8?q?=EC=9D=98=20EmptyView=20=ED=84=B0=EC=B9=98=20=EC=95=A1=EC=85=98?= =?UTF-8?q?=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CourseStorage/VC/CourseStorageVC.swift | 12 +++++++- .../PrivateCourseListView.swift | 29 ++++++++++++++++++- .../CourseListView/ScrapCourseListView.swift | 4 +++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift index 1f8f3d00..b664e3e0 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift @@ -6,9 +6,14 @@ // import UIKit +import Combine final class CourseStorageVC: UIViewController { + // MARK: - Properties + + private let cancelBag = CancelBag() + // MARK: - UI Components private lazy var naviBar = CustomNavigationBar(self, type: .title).setTitle("보관함") @@ -26,13 +31,18 @@ final class CourseStorageVC: UIViewController { super.viewDidLoad() self.setUI() self.setLayout() + self.bindUI() } } // MARK: - Methods extension CourseStorageVC { - + private func bindUI() { + privateCourseListView.courseDrawButtonTapped.sink { + self.tabBarController?.selectedIndex = 0 + }.store(in: cancelBag) + } } // MARK: - UI & Layout diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift index 83b97125..abc41d89 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift @@ -6,11 +6,14 @@ // import UIKit +import Combine final class PrivateCourseListView: UIView { // MARK: - Properties + var courseDrawButtonTapped = PassthroughSubject() + final let collectionViewInset = UIEdgeInsets(top: 28, left: 16, bottom: 28, right: 16) final let itemSpacing: CGFloat = 10 final let lineSpacing: CGFloat = 20 @@ -28,6 +31,9 @@ final class PrivateCourseListView: UIView { $0.backgroundColor = .clear } + private let emptyView = ListEmptyView(description: "아직 내가 그린코스가 없어요\n직접 코스를 그려주세요", + buttonTitle: "코스 그리기") + // MARK: - initialization init() { @@ -49,6 +55,8 @@ extension PrivateCourseListView { private func setDelegate() { courseListCollectionView.delegate = self courseListCollectionView.dataSource = self + + emptyView.delegate = self } private func register() { @@ -66,16 +74,25 @@ extension PrivateCourseListView { private func setLayout() { self.addSubviews(courseListCollectionView) + courseListCollectionView.addSubviews(emptyView) + courseListCollectionView.snp.makeConstraints { make in make.top.equalToSuperview() make.leading.bottom.trailing.equalToSuperview() } + + emptyView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(80) + } } } +// MARK: - UICollectionViewDelegate, UICollectionViewDataSource + extension PrivateCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 15 + return 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { @@ -87,6 +104,8 @@ extension PrivateCourseListView: UICollectionViewDelegate, UICollectionViewDataS } } +// MARK: - UICollectionViewDelegateFlowLayout + extension PrivateCourseListView: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let cellWidth = (UIScreen.main.bounds.width - (self.itemSpacing + 2*self.collectionViewInset.left)) / 2 @@ -107,3 +126,11 @@ extension PrivateCourseListView: UICollectionViewDelegateFlowLayout { return self.lineSpacing } } + +// MARK: - Section Heading + +extension PrivateCourseListView: ListEmptyViewDelegate { + func emptyViewButtonTapped() { + self.courseDrawButtonTapped.send() + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift index 3ae8f98a..05592935 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift @@ -73,6 +73,8 @@ extension ScrapCourseListView { } } +// MARK: - UICollectionViewDelegate, UICollectionViewDataSource + extension ScrapCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 15 @@ -87,6 +89,8 @@ extension ScrapCourseListView: UICollectionViewDelegate, UICollectionViewDataSou } } +// MARK: - UICollectionViewDelegateFlowLayout + extension ScrapCourseListView: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let cellWidth = (UIScreen.main.bounds.width - (self.itemSpacing + 2*self.collectionViewInset.left)) / 2 From b2cfb707b6b7b2dbf92c4f3d67f46ae311db7dbe Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Fri, 6 Jan 2023 02:22:33 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[Feat]=20#43=20-=20ScrapCourseListView?= =?UTF-8?q?=EC=9D=98=20EmptyView=20=ED=84=B0=EC=B9=98=20=EC=95=A1=EC=85=98?= =?UTF-8?q?=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/ListEmptyView.swift | 1 + .../CourseStorage/VC/CourseStorageVC.swift | 4 +++ .../PrivateCourseListView.swift | 3 ++- .../CourseListView/ScrapCourseListView.swift | 25 ++++++++++++++++++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift index 7ebd4585..28f7544e 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/ListEmptyView.swift @@ -28,6 +28,7 @@ final class ListEmptyView: UIView { $0.font = .b4 $0.textColor = .g2 $0.numberOfLines = 0 + $0.textAlignment = .center } private let bottomButton = CustomButton(title: "코스 그리기") diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift index b664e3e0..a46da683 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift @@ -42,6 +42,10 @@ extension CourseStorageVC { privateCourseListView.courseDrawButtonTapped.sink { self.tabBarController?.selectedIndex = 0 }.store(in: cancelBag) + + scrapCourseListView.scrapButtonTapped.sink { + self.tabBarController?.selectedIndex = 2 + }.store(in: cancelBag) } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift index abc41d89..9f5069c4 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/PrivateCourseListView.swift @@ -70,6 +70,7 @@ extension PrivateCourseListView { extension PrivateCourseListView { private func setUI() { self.backgroundColor = .w1 + self.emptyView.isHidden = true } private func setLayout() { @@ -92,7 +93,7 @@ extension PrivateCourseListView { extension PrivateCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 0 + return 15 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift index 05592935..44052ee7 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift @@ -6,11 +6,14 @@ // import UIKit +import Combine final class ScrapCourseListView: UIView { // MARK: - Properties + var scrapButtonTapped = PassthroughSubject() + final let collectionViewInset = UIEdgeInsets(top: 28, left: 16, bottom: 28, right: 16) final let itemSpacing: CGFloat = 10 final let lineSpacing: CGFloat = 20 @@ -28,6 +31,9 @@ final class ScrapCourseListView: UIView { $0.backgroundColor = .clear } + private let emptyView = ListEmptyView(description: "아직 스크랩한 코스가 없어요\n코스를 스크랩 해주세요", + buttonTitle: "스크랩 하기") + // MARK: - initialization init() { @@ -49,6 +55,8 @@ extension ScrapCourseListView { private func setDelegate() { courseListCollectionView.delegate = self courseListCollectionView.dataSource = self + + emptyView.delegate = self } private func register() { @@ -66,10 +74,17 @@ extension ScrapCourseListView { private func setLayout() { self.addSubviews(courseListCollectionView) + courseListCollectionView.addSubviews(emptyView) + courseListCollectionView.snp.makeConstraints { make in make.top.equalToSuperview() make.leading.bottom.trailing.equalToSuperview() } + + emptyView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(80) + } } } @@ -77,7 +92,7 @@ extension ScrapCourseListView { extension ScrapCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 15 + return 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { @@ -111,3 +126,11 @@ extension ScrapCourseListView: UICollectionViewDelegateFlowLayout { return self.lineSpacing } } + +// MARK: - Section Heading + +extension ScrapCourseListView: ListEmptyViewDelegate { + func emptyViewButtonTapped() { + self.scrapButtonTapped.send() + } +} From 5a7e80f1497adaeb5a55a873cccc0da1eb634ac1 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Fri, 6 Jan 2023 02:27:30 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[Chore]=20#43=20-=20emptyView=20hidden=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/CourseListView/ScrapCourseListView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift index 44052ee7..ce744c2c 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/Views/CourseListView/ScrapCourseListView.swift @@ -70,6 +70,7 @@ extension ScrapCourseListView { extension ScrapCourseListView { private func setUI() { self.backgroundColor = .w1 + self.emptyView.isHidden = true } private func setLayout() { @@ -92,7 +93,7 @@ extension ScrapCourseListView { extension ScrapCourseListView: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 0 + return 15 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {