Skip to content

Commit

Permalink
Merge pull request #28 from boostcampwm2023/iOS/feat#21
Browse files Browse the repository at this point in the history
✨ feat: HomeViewController 캐러셀 레이아웃 구현
  • Loading branch information
loinsir authored Nov 17, 2023
2 parents 140aabd + 898f559 commit 12bdc6a
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 51 deletions.
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)
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()
}

0 comments on commit 12bdc6a

Please sign in to comment.