From 93af59fff3f70e429f7271b6c3c3eae2125fd8af Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 17 Nov 2023 01:55:47 +0900 Subject: [PATCH 1/3] =?UTF-8?q?:sparkles:=20feat:=20HomeViewController=20?= =?UTF-8?q?=EC=BA=90=EB=9F=AC=EC=85=80=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 25 +++- iOS/Layover/Layover/Extensions/UIView+.swift | 5 + .../Layover/Scenes/BaseViewController.swift | 3 +- .../Cell/HomeCarouselCollectionViewCell.swift | 42 ++++++ .../Home/Cell/ZoomAndSnapFlowLayout.swift | 114 ++++++++++++++++ .../Scenes/Home/HomeConfigurator.swift | 31 +++++ .../Layover/Scenes/Home/HomeInteractor.swift | 1 - .../Scenes/Home/HomeViewController.swift | 123 +++++++++++------- .../SignUpConfigurator.swift | 0 .../SignUpInteractor.swift | 0 .../SignUpModels.swift | 0 .../SignUpPresenter.swift | 0 12 files changed, 294 insertions(+), 50 deletions(-) create mode 100644 iOS/Layover/Layover/Scenes/Home/Cell/HomeCarouselCollectionViewCell.swift create mode 100644 iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift create mode 100644 iOS/Layover/Layover/Scenes/Home/HomeConfigurator.swift rename iOS/Layover/Layover/Scenes/{SingUpScene => SignUpScene}/SignUpConfigurator.swift (100%) rename iOS/Layover/Layover/Scenes/{SingUpScene => SignUpScene}/SignUpInteractor.swift (100%) rename iOS/Layover/Layover/Scenes/{SingUpScene => SignUpScene}/SignUpModels.swift (100%) rename iOS/Layover/Layover/Scenes/{SingUpScene => SignUpScene}/SignUpPresenter.swift (100%) diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 6d504b5..2a15d60 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -28,6 +28,9 @@ 194552282B0479B600299768 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552272B0479B600299768 /* BaseViewController.swift */; }; 1945522A2B04883800299768 /* UIView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552292B04883800299768 /* UIView+.swift */; }; 194552312B04DA1A00299768 /* LOCircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552302B04DA1A00299768 /* LOCircleButton.swift */; }; + 194552392B05230E00299768 /* HomeCarouselCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */; }; + 1945523B2B05258200299768 /* HomeConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1945523A2B05258200299768 /* HomeConfigurator.swift */; }; + 1945523D2B05338B00299768 /* ZoomAndSnapFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1945523C2B05338B00299768 /* ZoomAndSnapFlowLayout.swift */; }; 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */; }; 19C7AFD62B02584D003B35F2 /* KeychainStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */; }; FC2511A02B045C0A004717BC /* SignUpInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC25119F2B045C0A004717BC /* SignUpInteractor.swift */; }; @@ -97,6 +100,9 @@ 194552272B0479B600299768 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 194552292B04883800299768 /* UIView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+.swift"; sourceTree = ""; }; 194552302B04DA1A00299768 /* LOCircleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOCircleButton.swift; sourceTree = ""; }; + 194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCarouselCollectionViewCell.swift; sourceTree = ""; }; + 1945523A2B05258200299768 /* HomeConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeConfigurator.swift; sourceTree = ""; }; + 1945523C2B05338B00299768 /* ZoomAndSnapFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomAndSnapFlowLayout.swift; sourceTree = ""; }; 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStored.swift; sourceTree = ""; }; FC25119F2B045C0A004717BC /* SignUpInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpInteractor.swift; sourceTree = ""; }; @@ -183,16 +189,27 @@ 1945521A2B0478A100299768 /* Home */ = { isa = PBXGroup; children = ( + 194552322B04FF2900299768 /* Cell */, 1945521E2B0478B400299768 /* HomeModels.swift */, 1945521F2B0478B400299768 /* HomeViewController.swift */, 194552202B0478B400299768 /* HomeInteractor.swift */, 1945521B2B0478B400299768 /* HomePresenter.swift */, 1945521C2B0478B400299768 /* HomeWorker.swift */, 1945521D2B0478B400299768 /* HomeRouter.swift */, + 1945523A2B05258200299768 /* HomeConfigurator.swift */, ); path = Home; sourceTree = ""; }; + 194552322B04FF2900299768 /* Cell */ = { + isa = PBXGroup; + children = ( + 194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */, + 1945523C2B05338B00299768 /* ZoomAndSnapFlowLayout.swift */, + ); + path = Cell; + sourceTree = ""; + }; 19C7AFCF2B02441C003B35F2 /* Common */ = { isa = PBXGroup; children = ( @@ -338,6 +355,7 @@ 1945520E2B03AEA400299768 /* Configurator.swift */, FC2511A72B04DA9C004717BC /* MapScene */, FCEE0FFB2B03AFAA00195BBE /* SingUpScene */, + FCEE0FFB2B03AFAA00195BBE /* SignUpScene */, 194552032B038FC400299768 /* Tabbar */, 194551EB2B037F1E00299768 /* Login */, 1945521A2B0478A100299768 /* Home */, @@ -378,7 +396,7 @@ path = Resources; sourceTree = ""; }; - FCEE0FFB2B03AFAA00195BBE /* SingUpScene */ = { + FCEE0FFB2B03AFAA00195BBE /* SignUpScene */ = { isa = PBXGroup; children = ( FCEE0FF92B03AF8400195BBE /* SignUpViewController.swift */, @@ -387,7 +405,7 @@ FC2511A32B045D6C004717BC /* SignUpModels.swift */, FC2511A52B049020004717BC /* SignUpConfigurator.swift */, ); - path = SingUpScene; + path = SignUpScene; sourceTree = ""; }; /* End PBXGroup section */ @@ -514,10 +532,13 @@ 194552212B0478B400299768 /* HomePresenter.swift in Sources */, 1945520D2B0399E500299768 /* MainTabBarViewController.swift in Sources */, FC2511AB2B04EA6B004717BC /* MapConfigurator.swift in Sources */, + 1945523D2B05338B00299768 /* ZoomAndSnapFlowLayout.swift in Sources */, + 1945523B2B05258200299768 /* HomeConfigurator.swift in Sources */, 194551F62B037F2D00299768 /* LoginViewController.swift in Sources */, FC7E458E2AFF7462004F155A /* DummyService.swift in Sources */, FCEE0FF22B036B6000195BBE /* LOButton.swift in Sources */, FC2511A62B049020004717BC /* SignUpConfigurator.swift in Sources */, + 194552392B05230E00299768 /* HomeCarouselCollectionViewCell.swift in Sources */, 194552132B03AFFC00299768 /* DummyViewController.swift in Sources */, 194551F22B037F2D00299768 /* LoginPresenter.swift in Sources */, 194552242B0478B400299768 /* HomeModels.swift in Sources */, diff --git a/iOS/Layover/Layover/Extensions/UIView+.swift b/iOS/Layover/Layover/Extensions/UIView+.swift index 1309511..9184429 100644 --- a/iOS/Layover/Layover/Extensions/UIView+.swift +++ b/iOS/Layover/Layover/Extensions/UIView+.swift @@ -9,6 +9,11 @@ import UIKit extension UIView { + + static var identifier: String { + return String(describing: self) + } + func addSubviews(_ views: UIView...) { views.forEach { addSubview($0) } } diff --git a/iOS/Layover/Layover/Scenes/BaseViewController.swift b/iOS/Layover/Layover/Scenes/BaseViewController.swift index a8f4e05..8207f91 100644 --- a/iOS/Layover/Layover/Scenes/BaseViewController.swift +++ b/iOS/Layover/Layover/Scenes/BaseViewController.swift @@ -25,12 +25,13 @@ class BaseViewController: UIViewController { // MARK: - Methods + /// Call when ViewDidLoad func setConstraints() { // Set AutoLayout } + /// Call when ViewDidLoad After setConstraints() func setUI() { - // Set UI view.backgroundColor = UIColor.background } } diff --git a/iOS/Layover/Layover/Scenes/Home/Cell/HomeCarouselCollectionViewCell.swift b/iOS/Layover/Layover/Scenes/Home/Cell/HomeCarouselCollectionViewCell.swift new file mode 100644 index 0000000..5b30e92 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Home/Cell/HomeCarouselCollectionViewCell.swift @@ -0,0 +1,42 @@ +// +// HomeCarouselCollectionViewCell.swift +// Layover +// +// Created by 김인환 on 11/16/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +final class HomeCarouselCollectionViewCell: UICollectionViewCell { + + // MARK: - Properties + + // MARK: - UI Components + + // MARK: - Object lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + setConstraints() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + setConstraints() + } + + // MARK: - Setup + + private func setup() { + backgroundColor = .white + } + + private func setConstraints() { + + } + + // MARK: - Methods +} diff --git a/iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift b/iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift new file mode 100644 index 0000000..fcfb50d --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift @@ -0,0 +1,114 @@ +// +// ZoomAndSnapFlowLayout.swift +// Layover +// +// Created by 김인환 on 11/16/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +final class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout { + + // MARK: Properties + + private let activeDistance: CGFloat = 100 + private let zoomFactor: CGFloat + + // MARK: Initializer + + init(zoomFactor: CGFloat = 1.0) { + self.zoomFactor = zoomFactor + super.init() + } + + required init?(coder: NSCoder) { + self.zoomFactor = 1.0 + super.init(coder: coder) + } + + // MARK: Methods + + override func prepare() { + guard let collectionView else { + super.prepare() + return + } + + let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2 + let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2 + sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets) + super.prepare() + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + guard let collectionView = collectionView else { return nil } + let rectAttributes = super.layoutAttributesForElements(in: rect)?.compactMap { $0.copy() as? UICollectionViewLayoutAttributes } ?? [] + let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size) + let visibleAttributes = rectAttributes.filter { $0.frame.intersects(visibleRect) } + + // Keep the spacing between cells the same. + // Each cell shifts the next cell by half of it's enlarged size. + // Calculated separately for each direction. + func adjustXPosition(_ toProcess: [UICollectionViewLayoutAttributes], direction: CGFloat, zoom: Bool = false) { + var xDiff: CGFloat = 0 + + for attributes in toProcess { + let distance = visibleRect.midX - attributes.center.x + attributes.frame.origin.x += xDiff + + if distance.magnitude < activeDistance { + let normalizedDistance = distance / activeDistance + let zoomAddition = zoomFactor * (1 - normalizedDistance.magnitude) + let widthAddition = attributes.frame.width * zoomAddition / 2 + xDiff += widthAddition * direction + + if zoom { + let scale = 1 + zoomAddition + attributes.transform3D = CATransform3DMakeScale(scale, scale, 1) + } + } + } + } + + // Adjust the x position first from left to right. + // Then adjust the x position from right to left. + // Lastly zoom the cells when they reach the center of the screen (zoom: true). + adjustXPosition(visibleAttributes, direction: +1) + adjustXPosition(visibleAttributes.reversed(), direction: -1, zoom: true) + + return rectAttributes + } + + override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { + guard let collectionView else { return .zero } + + // Add some snapping behaviour so that the zoomed cell is always centered + let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height) + guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero } + + var offsetAdjustment = CGFloat.greatestFiniteMagnitude + let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2 + + for layoutAttributes in rectAttributes { + let itemHorizontalCenter = layoutAttributes.center.x + if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude { + offsetAdjustment = itemHorizontalCenter - horizontalCenter + } + } + + return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) + } + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + // Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen + return true + } + + override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { + let context = super.invalidationContext(forBoundsChange: newBounds) + guard let invalidationContext = context as? UICollectionViewFlowLayoutInvalidationContext else { return context } + invalidationContext.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size + return context + } +} diff --git a/iOS/Layover/Layover/Scenes/Home/HomeConfigurator.swift b/iOS/Layover/Layover/Scenes/Home/HomeConfigurator.swift new file mode 100644 index 0000000..d762ed0 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Home/HomeConfigurator.swift @@ -0,0 +1,31 @@ +// +// HomeConfigurator.swift +// Layover +// +// Created by 김인환 on 11/16/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +final class HomeConfigurator: Configurator { + + static let shared = HomeConfigurator() + + private init() { } + + func configure(_ viewController: HomeViewController) { + let router = HomeRouter() + router.viewController = viewController + + let presenter = HomePresenter() + presenter.viewController = viewController + + let interactor = HomeInteractor() + interactor.presenter = presenter + interactor.worker = HomeWorker() + + viewController.router = router + viewController.interactor = interactor + } +} diff --git a/iOS/Layover/Layover/Scenes/Home/HomeInteractor.swift b/iOS/Layover/Layover/Scenes/Home/HomeInteractor.swift index e794405..30778fd 100644 --- a/iOS/Layover/Layover/Scenes/Home/HomeInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Home/HomeInteractor.swift @@ -54,7 +54,6 @@ class HomeInteractor: HomeBusinessLogic, HomeDataStore { // <#Analytics Worker Instance#>.trackAnalytics(event: request.event) let response = Models.TrackAnalytics.Response() -// presenter?.presentTrackAnalytics(with: response) } // MARK: - Use Case - Home diff --git a/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift b/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift index 16442c8..9e9e407 100644 --- a/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift +++ b/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift @@ -9,10 +9,7 @@ import UIKit protocol HomeDisplayLogic: AnyObject { -// func displayFetchFromLocalDataStore(with viewModel: HomeModels.FetchFromLocalDataStore.ViewModel) -// func displayFetchFromRemoteDataStore(with viewModel: HomeModels.FetchFromRemoteDataStore.ViewModel) -// func displayTrackAnalytics(with viewModel: HomeModels.TrackAnalytics.ViewModel) -// func displayPerformHome(with viewModel: HomeModels.PerformHome.ViewModel) + } final class HomeViewController: BaseViewController, HomeDisplayLogic { @@ -27,6 +24,22 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic { private let uploadButton: LOCircleButton = LOCircleButton(style: .add, diameter: 52) + private lazy var carouselCollectionView: UICollectionView = { + let layout = makeCarouselLayout() + let collectionView = UICollectionView(frame: .init(), collectionViewLayout: layout) + collectionView.register(HomeCarouselCollectionViewCell.self, forCellWithReuseIdentifier: HomeCarouselCollectionViewCell.identifier) + collectionView.backgroundColor = .clear + collectionView.contentInsetAdjustmentBehavior = .always + return collectionView + }() + + private lazy var carouselDatasource = UICollectionViewDiffableDataSource(collectionView: carouselCollectionView) { collectionView, indexPath, _ in + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeCarouselCollectionViewCell.identifier, + for: indexPath) as? HomeCarouselCollectionViewCell else { return UICollectionViewCell() } + cell.layer.cornerRadius = 10 + return cell + } + // MARK: - Object lifecycle override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { @@ -42,17 +55,7 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic { // MARK: - Setup private func setup() { - let viewController = self - let interactor = HomeInteractor() - let presenter = HomePresenter() - let router = HomeRouter() - - viewController.router = router - viewController.interactor = interactor - interactor.presenter = presenter - presenter.viewController = viewController - router.viewController = viewController - router.dataStore = interactor + } // MARK: - View Lifecycle @@ -60,53 +63,81 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic { // MARK: - UI override func setConstraints() { - uploadButton.translatesAutoresizingMaskIntoConstraints = false + super.setConstraints() + + [carouselCollectionView, uploadButton].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + } - view.addSubviews(uploadButton) + view.addSubviews(carouselCollectionView, uploadButton) NSLayoutConstraint.activate([ - uploadButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -(tabBarController?.tabBar.bounds.height ?? 83) - 20), + carouselCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + carouselCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + carouselCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 42), + carouselCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -109), + + uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20) ]) } - // MARK: - Use Case - - // MARK: - Use Case - Fetch From Remote DataStore - - @IBOutlet var exampleRemoteLabel: UILabel! = UILabel() - func setupFetchFromRemoteDataStore() { - let request = Models.FetchFromRemoteDataStore.Request() - interactor?.fetchFromRemoteDataStore(with: request) + override func setUI() { + super.setUI() + setCarouselCollectionView() } - func displayFetchFromRemoteDataStore(with viewModel: HomeModels.FetchFromRemoteDataStore.ViewModel) { - exampleRemoteLabel.text = viewModel.exampleVariable + private func setCarouselCollectionView() { + carouselCollectionView.dataSource = carouselDatasource + var snapshot = NSDiffableDataSourceSnapshot() + // sample data + snapshot.appendSections([UUID()]) + snapshot.appendItems([1, 2, 3, 4]) + carouselDatasource.apply(snapshot) } - // MARK: - Use Case - Track Analytics - - @objc - func trackScreenViewAnalytics() { - trackAnalytics(event: .screenView) + private func makeCarouselLayout() -> UICollectionViewCompositionalLayout { + let layout = UICollectionViewCompositionalLayout { _, _ in + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), + heightDimension: .fractionalHeight(1)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(314/375), + heightDimension: .fractionalHeight(473/534)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, + subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = 0 + section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30) + section.orthogonalScrollingBehavior = .groupPagingCentered + + section.visibleItemsInvalidationHandler = { items, offset, environment in + let containerWidth = environment.container.contentSize.width + + items.forEach { item in + let itemCenterRelativeToOffset = item.frame.midX - offset.x // 아이템 중심점과 container offset(왼쪽)의 거리 + let distanceFromCenter = abs(itemCenterRelativeToOffset - containerWidth / 2.0) // container 중심점과 아이템 중심점의 거리 + let minScale: CGFloat = 473/534 // 최소 비율 + let maxScale: CGFloat = 1 + let scale = max(maxScale - (distanceFromCenter / containerWidth), minScale) // 최대 비율에서 거리에 따라 비율을 줄임, 최소 비율보다 작아지지 않도록 함 + item.transform = CGAffineTransform(scaleX: scale, y: scale) + } + } + return section + } + return layout } - func trackAnalytics(event: HomeModels.AnalyticsEvents) { - let request = Models.TrackAnalytics.Request(event: event) - interactor?.trackAnalytics(with: request) - } - - func displayTrackAnalytics(with viewModel: HomeModels.TrackAnalytics.ViewModel) { - // do something after tracking analytics (if needed) - } + // MARK: - Use Case - // MARK: - Use Case - Home + // MARK: - Use Case - Fetch From Remote DataStore - func performHome(_ sender: Any) { + // MARK: - Use Case - Track Analytics - } + // MARK: - Use Case - Home - func displayPerformHome(with viewModel: HomeModels.PerformHome.ViewModel) { +} - } +#Preview { + HomeViewController() } diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpConfigurator.swift b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpConfigurator.swift similarity index 100% rename from iOS/Layover/Layover/Scenes/SingUpScene/SignUpConfigurator.swift rename to iOS/Layover/Layover/Scenes/SignUpScene/SignUpConfigurator.swift diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpInteractor.swift b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpInteractor.swift similarity index 100% rename from iOS/Layover/Layover/Scenes/SingUpScene/SignUpInteractor.swift rename to iOS/Layover/Layover/Scenes/SignUpScene/SignUpInteractor.swift diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpModels.swift b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpModels.swift similarity index 100% rename from iOS/Layover/Layover/Scenes/SingUpScene/SignUpModels.swift rename to iOS/Layover/Layover/Scenes/SignUpScene/SignUpModels.swift diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpPresenter.swift b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpPresenter.swift similarity index 100% rename from iOS/Layover/Layover/Scenes/SingUpScene/SignUpPresenter.swift rename to iOS/Layover/Layover/Scenes/SignUpScene/SignUpPresenter.swift From 588ed4aa8cef20aaea3343060d088c4feec7c1f0 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 17 Nov 2023 02:35:32 +0900 Subject: [PATCH 2/3] =?UTF-8?q?:fire:=20delete:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=BB=A4=EC=8A=A4=ED=85=80=20flowlayout=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 5 - .../Home/Cell/ZoomAndSnapFlowLayout.swift | 114 ------------------ 2 files changed, 119 deletions(-) delete mode 100644 iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 2a15d60..0e669e4 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -30,7 +30,6 @@ 194552312B04DA1A00299768 /* LOCircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552302B04DA1A00299768 /* LOCircleButton.swift */; }; 194552392B05230E00299768 /* HomeCarouselCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */; }; 1945523B2B05258200299768 /* HomeConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1945523A2B05258200299768 /* HomeConfigurator.swift */; }; - 1945523D2B05338B00299768 /* ZoomAndSnapFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1945523C2B05338B00299768 /* ZoomAndSnapFlowLayout.swift */; }; 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */; }; 19C7AFD62B02584D003B35F2 /* KeychainStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */; }; FC2511A02B045C0A004717BC /* SignUpInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC25119F2B045C0A004717BC /* SignUpInteractor.swift */; }; @@ -102,7 +101,6 @@ 194552302B04DA1A00299768 /* LOCircleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOCircleButton.swift; sourceTree = ""; }; 194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCarouselCollectionViewCell.swift; sourceTree = ""; }; 1945523A2B05258200299768 /* HomeConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeConfigurator.swift; sourceTree = ""; }; - 1945523C2B05338B00299768 /* ZoomAndSnapFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomAndSnapFlowLayout.swift; sourceTree = ""; }; 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStored.swift; sourceTree = ""; }; FC25119F2B045C0A004717BC /* SignUpInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpInteractor.swift; sourceTree = ""; }; @@ -205,7 +203,6 @@ isa = PBXGroup; children = ( 194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */, - 1945523C2B05338B00299768 /* ZoomAndSnapFlowLayout.swift */, ); path = Cell; sourceTree = ""; @@ -354,7 +351,6 @@ children = ( 1945520E2B03AEA400299768 /* Configurator.swift */, FC2511A72B04DA9C004717BC /* MapScene */, - FCEE0FFB2B03AFAA00195BBE /* SingUpScene */, FCEE0FFB2B03AFAA00195BBE /* SignUpScene */, 194552032B038FC400299768 /* Tabbar */, 194551EB2B037F1E00299768 /* Login */, @@ -532,7 +528,6 @@ 194552212B0478B400299768 /* HomePresenter.swift in Sources */, 1945520D2B0399E500299768 /* MainTabBarViewController.swift in Sources */, FC2511AB2B04EA6B004717BC /* MapConfigurator.swift in Sources */, - 1945523D2B05338B00299768 /* ZoomAndSnapFlowLayout.swift in Sources */, 1945523B2B05258200299768 /* HomeConfigurator.swift in Sources */, 194551F62B037F2D00299768 /* LoginViewController.swift in Sources */, FC7E458E2AFF7462004F155A /* DummyService.swift in Sources */, diff --git a/iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift b/iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift deleted file mode 100644 index fcfb50d..0000000 --- a/iOS/Layover/Layover/Scenes/Home/Cell/ZoomAndSnapFlowLayout.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// ZoomAndSnapFlowLayout.swift -// Layover -// -// Created by 김인환 on 11/16/23. -// Copyright © 2023 CodeBomber. All rights reserved. -// - -import UIKit - -final class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout { - - // MARK: Properties - - private let activeDistance: CGFloat = 100 - private let zoomFactor: CGFloat - - // MARK: Initializer - - init(zoomFactor: CGFloat = 1.0) { - self.zoomFactor = zoomFactor - super.init() - } - - required init?(coder: NSCoder) { - self.zoomFactor = 1.0 - super.init(coder: coder) - } - - // MARK: Methods - - override func prepare() { - guard let collectionView else { - super.prepare() - return - } - - let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2 - let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2 - sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets) - super.prepare() - } - - override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { - guard let collectionView = collectionView else { return nil } - let rectAttributes = super.layoutAttributesForElements(in: rect)?.compactMap { $0.copy() as? UICollectionViewLayoutAttributes } ?? [] - let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size) - let visibleAttributes = rectAttributes.filter { $0.frame.intersects(visibleRect) } - - // Keep the spacing between cells the same. - // Each cell shifts the next cell by half of it's enlarged size. - // Calculated separately for each direction. - func adjustXPosition(_ toProcess: [UICollectionViewLayoutAttributes], direction: CGFloat, zoom: Bool = false) { - var xDiff: CGFloat = 0 - - for attributes in toProcess { - let distance = visibleRect.midX - attributes.center.x - attributes.frame.origin.x += xDiff - - if distance.magnitude < activeDistance { - let normalizedDistance = distance / activeDistance - let zoomAddition = zoomFactor * (1 - normalizedDistance.magnitude) - let widthAddition = attributes.frame.width * zoomAddition / 2 - xDiff += widthAddition * direction - - if zoom { - let scale = 1 + zoomAddition - attributes.transform3D = CATransform3DMakeScale(scale, scale, 1) - } - } - } - } - - // Adjust the x position first from left to right. - // Then adjust the x position from right to left. - // Lastly zoom the cells when they reach the center of the screen (zoom: true). - adjustXPosition(visibleAttributes, direction: +1) - adjustXPosition(visibleAttributes.reversed(), direction: -1, zoom: true) - - return rectAttributes - } - - override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { - guard let collectionView else { return .zero } - - // Add some snapping behaviour so that the zoomed cell is always centered - let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height) - guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero } - - var offsetAdjustment = CGFloat.greatestFiniteMagnitude - let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2 - - for layoutAttributes in rectAttributes { - let itemHorizontalCenter = layoutAttributes.center.x - if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude { - offsetAdjustment = itemHorizontalCenter - horizontalCenter - } - } - - return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) - } - - override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { - // Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen - return true - } - - override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { - let context = super.invalidationContext(forBoundsChange: newBounds) - guard let invalidationContext = context as? UICollectionViewFlowLayoutInvalidationContext else { return context } - invalidationContext.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size - return context - } -} From 898f559315be44503140f75a8c26ff36db199e0d Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 17 Nov 2023 03:10:20 +0900 Subject: [PATCH 3/3] =?UTF-8?q?:art:=20chore:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/Home/HomeViewController.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift b/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift index 9e9e407..0a944bc 100644 --- a/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift +++ b/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift @@ -25,7 +25,10 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic { private let uploadButton: LOCircleButton = LOCircleButton(style: .add, diameter: 52) private lazy var carouselCollectionView: UICollectionView = { - let layout = makeCarouselLayout() + let layout = createCarouselLayout(groupWidthDimension: 314/375, + groupHeightDimension: 473/534, + maximumZoomScale: 1, + minimumZoomScale: 473/534) let collectionView = UICollectionView(frame: .init(), collectionViewLayout: layout) collectionView.register(HomeCarouselCollectionViewCell.self, forCellWithReuseIdentifier: HomeCarouselCollectionViewCell.identifier) collectionView.backgroundColor = .clear @@ -96,13 +99,16 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic { carouselDatasource.apply(snapshot) } - private func makeCarouselLayout() -> UICollectionViewCompositionalLayout { + private func createCarouselLayout(groupWidthDimension: CGFloat, + groupHeightDimension: CGFloat, + maximumZoomScale: CGFloat, + minimumZoomScale: CGFloat) -> UICollectionViewCompositionalLayout { let layout = UICollectionViewCompositionalLayout { _, _ in let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) let item = NSCollectionLayoutItem(layoutSize: itemSize) - let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(314/375), - heightDimension: .fractionalHeight(473/534)) + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(groupWidthDimension), + heightDimension: .fractionalHeight(groupHeightDimension)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) @@ -117,9 +123,7 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic { items.forEach { item in let itemCenterRelativeToOffset = item.frame.midX - offset.x // 아이템 중심점과 container offset(왼쪽)의 거리 let distanceFromCenter = abs(itemCenterRelativeToOffset - containerWidth / 2.0) // container 중심점과 아이템 중심점의 거리 - let minScale: CGFloat = 473/534 // 최소 비율 - let maxScale: CGFloat = 1 - let scale = max(maxScale - (distanceFromCenter / containerWidth), minScale) // 최대 비율에서 거리에 따라 비율을 줄임, 최소 비율보다 작아지지 않도록 함 + let scale = max(maximumZoomScale - (distanceFromCenter / containerWidth), minimumZoomScale) // 최대 비율에서 거리에 따라 비율을 줄임, 최소 비율보다 작아지지 않도록 함 item.transform = CGAffineTransform(scaleX: scale, y: scale) } }