Skip to content

✨ feat: HomeViewController 캐러셀 레이아웃 구현 #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions iOS/Layover/Layover.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
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 */; };
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 */; };
Expand Down Expand Up @@ -97,6 +99,8 @@
194552272B0479B600299768 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
194552292B04883800299768 /* UIView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+.swift"; sourceTree = "<group>"; };
194552302B04DA1A00299768 /* LOCircleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOCircleButton.swift; sourceTree = "<group>"; };
194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCarouselCollectionViewCell.swift; sourceTree = "<group>"; };
1945523A2B05258200299768 /* HomeConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeConfigurator.swift; sourceTree = "<group>"; };
19C7AFCD2B02410F003B35F2 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = "<group>"; };
19C7AFD52B02584D003B35F2 /* KeychainStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStored.swift; sourceTree = "<group>"; };
FC25119F2B045C0A004717BC /* SignUpInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpInteractor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -183,16 +187,26 @@
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 = "<group>";
};
194552322B04FF2900299768 /* Cell */ = {
isa = PBXGroup;
children = (
194552382B05230E00299768 /* HomeCarouselCollectionViewCell.swift */,
);
path = Cell;
sourceTree = "<group>";
};
19C7AFCF2B02441C003B35F2 /* Common */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -337,7 +351,7 @@
children = (
1945520E2B03AEA400299768 /* Configurator.swift */,
FC2511A72B04DA9C004717BC /* MapScene */,
FCEE0FFB2B03AFAA00195BBE /* SingUpScene */,
FCEE0FFB2B03AFAA00195BBE /* SignUpScene */,
194552032B038FC400299768 /* Tabbar */,
194551EB2B037F1E00299768 /* Login */,
1945521A2B0478A100299768 /* Home */,
Expand Down Expand Up @@ -378,7 +392,7 @@
path = Resources;
sourceTree = "<group>";
};
FCEE0FFB2B03AFAA00195BBE /* SingUpScene */ = {
FCEE0FFB2B03AFAA00195BBE /* SignUpScene */ = {
isa = PBXGroup;
children = (
FCEE0FF92B03AF8400195BBE /* SignUpViewController.swift */,
Expand All @@ -387,7 +401,7 @@
FC2511A32B045D6C004717BC /* SignUpModels.swift */,
FC2511A52B049020004717BC /* SignUpConfigurator.swift */,
);
path = SingUpScene;
path = SignUpScene;
sourceTree = "<group>";
};
/* End PBXGroup section */
Expand Down Expand Up @@ -514,10 +528,12 @@
194552212B0478B400299768 /* HomePresenter.swift in Sources */,
1945520D2B0399E500299768 /* MainTabBarViewController.swift in Sources */,
FC2511AB2B04EA6B004717BC /* MapConfigurator.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 */,
Expand Down
5 changes: 5 additions & 0 deletions iOS/Layover/Layover/Extensions/UIView+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
import UIKit

extension UIView {

static var identifier: String {
return String(describing: self)
}

func addSubviews(_ views: UIView...) {
views.forEach { addSubview($0) }
}
Expand Down
3 changes: 2 additions & 1 deletion iOS/Layover/Layover/Scenes/BaseViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 31 additions & 0 deletions iOS/Layover/Layover/Scenes/Home/HomeConfigurator.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
1 change: 0 additions & 1 deletion iOS/Layover/Layover/Scenes/Home/HomeInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
127 changes: 81 additions & 46 deletions iOS/Layover/Layover/Scenes/Home/HomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -27,6 +24,25 @@ final class HomeViewController: BaseViewController, HomeDisplayLogic {

private let uploadButton: LOCircleButton = LOCircleButton(style: .add, diameter: 52)

private lazy var carouselCollectionView: UICollectionView = {
let layout = createCarouselLayout(groupWidthDimension: 314/375,
groupHeightDimension: 473/534,
maximumZoomScale: 1,
minimumZoomScale: 473/534)
Comment on lines +28 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 상수값들의 기준이 있을까요? 있다면 뷰마다 따로 만들어 둬도 좋을 것 같습니다.

Copy link
Collaborator Author

@loinsir loinsir Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

피그마 값을 기준으로 정했는데, 이곳 처럼 함수로 입력되는 외부 인자도 따로 빼서 관리해줘야 할지 고민이네요... 다같이 논의해봅시다.

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<UUID, Int>(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?) {
Expand All @@ -42,71 +58,90 @@ 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

// 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<UUID, Int>()
// 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 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(groupWidthDimension),
heightDimension: .fractionalHeight(groupHeightDimension))
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 scale = max(maximumZoomScale - (distanceFromCenter / containerWidth), minimumZoomScale) // 최대 비율에서 거리에 따라 비율을 줄임, 최소 비율보다 작아지지 않도록 함
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()
}