diff --git a/SpaceCapsule/SpaceCapsule.xcodeproj/project.pbxproj b/SpaceCapsule/SpaceCapsule.xcodeproj/project.pbxproj index c2feb1e..9e73d1d 100644 --- a/SpaceCapsule/SpaceCapsule.xcodeproj/project.pbxproj +++ b/SpaceCapsule/SpaceCapsule.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ 4AA4C5B529473D8600171D0D /* MovableToCapsuleAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA4C5B429473D8600171D0D /* MovableToCapsuleAccess.swift */; }; 4AA4C5B72948729200171D0D /* Calendar+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA4C5B62948729200171D0D /* Calendar+.swift */; }; 4AF16AE7292B1F8400787C5C /* CustomAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF16AE6292B1F8400787C5C /* CustomAnnotationView.swift */; }; + 594C6A8F295462D400B42815 /* CustomCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594C6A8E295462D400B42815 /* CustomCollectionView.swift */; }; 599C19D2292B82D000517E93 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 599C19D1292B82D000517E93 /* LoadingViewController.swift */; }; 599C19D4292B838D00517E93 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 599C19D3292B838D00517E93 /* LoadingView.swift */; }; 599C1A2C292C9F5B00517E93 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 599C1A2B292C9F5B00517E93 /* .swiftlint.yml */; }; @@ -348,6 +349,7 @@ 4AA4C5B429473D8600171D0D /* MovableToCapsuleAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovableToCapsuleAccess.swift; sourceTree = ""; }; 4AA4C5B62948729200171D0D /* Calendar+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+.swift"; sourceTree = ""; }; 4AF16AE6292B1F8400787C5C /* CustomAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnnotationView.swift; sourceTree = ""; }; + 594C6A8E295462D400B42815 /* CustomCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCollectionView.swift; sourceTree = ""; }; 599C19D1292B82D000517E93 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Loading\U0010ViewController.swift"; sourceTree = ""; }; 599C19D3292B838D00517E93 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 599C1A2B292C9F5B00517E93 /* .swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; @@ -408,6 +410,7 @@ 0DA05E1C29308B8400B08FA5 /* CustomNavigationController.swift */, 0DD83D0E292F1BAD0020F15C /* CustomScrollView.swift */, 0DCB22922924CE58002CB095 /* CustomTabBarController.swift */, + 594C6A8E295462D400B42815 /* CustomCollectionView.swift */, 0DFF50DE2940587600079FA6 /* RefreshButton.swift */, 0DFF50DC29402E3100079FA6 /* EmptyView.swift */, 0DD83D12292F51960020F15C /* LoadingIndicatorView.swift */, @@ -1190,6 +1193,7 @@ 0DD83D13292F51960020F15C /* LoadingIndicatorView.swift in Sources */, 0DD83D0F292F1BAD0020F15C /* CustomScrollView.swift in Sources */, 0D706137292C630D0077787E /* DatePickerViewModel.swift in Sources */, + 594C6A8F295462D400B42815 /* CustomCollectionView.swift in Sources */, 0D90DAF929239A81000DCC84 /* UIImage+.swift in Sources */, 0DF9D2A5293F1CB3001C05DE /* DetailImageView.swift in Sources */, 0DE786E1292BBDEC007EF260 /* DatePickerViewController.swift in Sources */, diff --git a/SpaceCapsule/SpaceCapsule/Components/CustomCollectionView.swift b/SpaceCapsule/SpaceCapsule/Components/CustomCollectionView.swift new file mode 100644 index 0000000..b12f6d0 --- /dev/null +++ b/SpaceCapsule/SpaceCapsule/Components/CustomCollectionView.swift @@ -0,0 +1,24 @@ +// +// MyCollectionView.swift +// SpaceCapsule +// +// Created by 김민중 on 2022/12/22. +// + +import UIKit + +class CustomCollectionView: UICollectionView { + + private var reloadDataCompletionBlock: (() -> Void)? + + func reloadDataWithCompletion(_ complete: @escaping () -> Void) { + reloadDataCompletionBlock = complete + super.reloadData() + } + + override func layoutSubviews() { + super.layoutSubviews() + reloadDataCompletionBlock?() + reloadDataCompletionBlock = nil + } +} diff --git a/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeView.swift b/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeView.swift index 04df72b..a15281a 100644 --- a/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeView.swift +++ b/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeView.swift @@ -16,9 +16,9 @@ final class HomeView: UIView, BaseView { color: .themeGray300 ) - lazy var capsuleCollectionView: UICollectionView = { + lazy var capsuleCollectionView: CustomCollectionView = { let layout = CarouselCollectionViewFlowLayout() - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + let collectionView = CustomCollectionView(frame: .zero, collectionViewLayout: layout) collectionView.register(HomeCapsuleCell.self, forCellWithReuseIdentifier: HomeCapsuleCell.identifier) collectionView.backgroundColor = .none collectionView.showsHorizontalScrollIndicator = false diff --git a/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewController.swift b/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewController.swift index 00a076f..8d45d4e 100644 --- a/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewController.swift +++ b/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewController.swift @@ -15,7 +15,7 @@ final class HomeViewController: UIViewController, BaseViewController { var viewModel: HomeViewModel? var disposeBag = DisposeBag() - + var centerIndex: CGFloat { return homeView.capsuleCollectionView.contentOffset.x / (FrameResource.homeCapsuleCellWidth * 0.75 + 10) } @@ -28,8 +28,12 @@ final class HomeViewController: UIViewController, BaseViewController { override func viewDidLoad() { super.viewDidLoad() title = "홈" + + homeView.capsuleCollectionView.dataSource = self + configureView() bind() + } override func viewWillAppear(_ animated: Bool) { @@ -37,9 +41,11 @@ final class HomeViewController: UIViewController, BaseViewController { viewModel?.input.viewWillAppear.onNext(()) } + // MARK: - Functions + private func configureView() { view.backgroundColor = .themeBackground - + viewModel?.output.featuredCapsuleCellItems .withUnretained(self) .bind { owner, capsuleCellItems in @@ -49,11 +55,21 @@ final class HomeViewController: UIViewController, BaseViewController { } else { owner.emptyView = nil owner.showHomeView() + owner.homeView.capsuleCollectionView.reloadDataWithCompletion { + guard let featuredCapsules = owner.viewModel?.output.featuredCapsules else { + return + } + let startIndex = 100_000_000 + let randomInt = Int.random(in: startIndex..<(startIndex + featuredCapsules.count)) + owner.homeView.capsuleCollectionView.scrollToItem( + at: IndexPath(item: randomInt, section: 0), + at: .centeredHorizontally, + animated: false + ) + } } } .disposed(by: disposeBag) - - AppDataManager.shared.fetchCapsules() } private func showEmptyView() { @@ -88,18 +104,6 @@ final class HomeViewController: UIViewController, BaseViewController { return } - viewModel.output.featuredCapsuleCellItems - .bind(to: homeView.capsuleCollectionView.rx.items) { collectionView, index, element in - guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: HomeCapsuleCell.identifier, - for: IndexPath(item: index, section: 0) - ) as? HomeCapsuleCell else { - return UICollectionViewCell() - } - cell.configure(capsuleCellModel: element) - return cell - }.disposed(by: disposeBag) - viewModel.output.userCapsuleStatus .subscribe(onNext: { [weak self] status in self?.homeView.mainStatusLabel.updateUserCapsuleStatus( @@ -164,3 +168,26 @@ final class HomeViewController: UIViewController, BaseViewController { }).disposed(by: disposeBag) } } + +extension HomeViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + guard let featuredCapsules = viewModel?.output.featuredCapsules else { + return 0 + } + return featuredCapsules.isEmpty ? 0 : Int.max + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: HomeCapsuleCell.identifier, + for: indexPath + ) as? HomeCapsuleCell else { + return UICollectionViewCell() + } + guard let featuredCapsules = viewModel?.output.featuredCapsules else { + return cell + } + cell.configure(capsuleCellModel: featuredCapsules[indexPath.item % featuredCapsules.count]) + return cell + } +} diff --git a/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewModel.swift b/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewModel.swift index bb7f162..4cc5492 100644 --- a/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewModel.swift +++ b/SpaceCapsule/SpaceCapsule/Scene/TabBar/Home/HomeViewModel.swift @@ -29,6 +29,7 @@ final class HomeViewModel: BaseViewModel, CapsuleCellNeedable { } struct Output: ViewModelOutput { + var featuredCapsules = [HomeCapsuleCellItem]() let featuredCapsuleCellItems = PublishRelay<[HomeCapsuleCellItem]>() let userCapsuleStatus = PublishRelay() } @@ -53,10 +54,11 @@ final class HomeViewModel: BaseViewModel, CapsuleCellNeedable { let status = UserCapsuleStatus(nickname: nickname, capsuleCounts: capsuleList.count) owner.output.userCapsuleStatus.accept(status) + let featuredCapsules = CapsuleType.allCases + .compactMap { owner.getHomeCapsuleCellItem(capsules: capsuleList, type: $0) } + owner.output.featuredCapsules = featuredCapsules owner.output.featuredCapsuleCellItems.accept( - CapsuleType.allCases - .map { owner.getHomeCapsuleCellItem(capsules: capsuleList, type: $0) } - .compactMap({ $0 }) + featuredCapsules ) })