기존코드
저는 처음에 Cell에 Indicatorview를 넣어서 indexPath 값으로 변환 해줘야겠다 생각했어요
하지만 이방식으로 구현하면 Cell 마다 Indicatorview가 보이고 디자이너가 요구하는 사항을 충족하지 못하더라구요 항상 Indicatorview 때문에 머리가 아팠는데 이번 기회에 CompositionalLayout에서 Indicatorview를 구현 하는 방법을 알았습니다 하도 모르겠어서 삽집하다가 마지막에 생각난 아이디어가 CompositionalLayout에서 설정한 특정 섹션을 가져올 수 없을까 생각했는데 그 방법이 있어서 해당 방법을 사용했습니다.
코드양은 변경된 코드는 얼마 없지만 미치도록 삽질 했음
import UIKit
import SnapKit
import Then
final class HomeViewController: UIViewController {
// MARK: - Properties
private let bannerData = BannerResponse.mockData()
private let bannerPageIndicatorImage = [
ImageLiterals.pagebutton_01,
ImageLiterals.pagebutton_02,
ImageLiterals.pagebutton_03,
ImageLiterals.pagebutton_04,
ImageLiterals.pagebutton_05,
ImageLiterals.pagebutton_06
]
// MARK: - UI Components
private lazy var homeCollectionView = UICollectionView(
frame: .zero,
collectionViewLayout: self.createCompositionalLayout()
)
private let bannerPageLabel = UILabel()
private let bannerPageIndicatorImageView = UIImageView()
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
setUp()
setStyle()
setLayout()
setRegister()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateBannerOverlayPosition()
}
// MARK: - Setup
private func setUp() {
view.addSubviews(
homeCollectionView,
bannerPageLabel,
bannerPageIndicatorImageView
)
}
// MARK: - SetStyle
private func setStyle() {
homeCollectionView.do {
$0.backgroundColor = .white
$0.delegate = self
$0.dataSource = self
$0.showsHorizontalScrollIndicator = false
}
bannerPageLabel.do {
$0.backgroundColor = .gray07
$0.font = .pretendard(.body7_r_13)
$0.layer.cornerRadius = 12
$0.clipsToBounds = true
$0.textColor = .white
$0.textAlignment = .center
$0.text = "1/\(bannerData.count)"
}
bannerPageIndicatorImageView.do {
$0.contentMode = .scaleAspectFit
$0.image = bannerPageIndicatorImage[0]
}
}
// MARK: - SetLayout
private func setLayout() {
homeCollectionView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
// MARK: - SetRegister
private func setRegister() {
homeCollectionView.do {
$0.register(BannerCell.self, forCellWithReuseIdentifier: BannerCell.identifier)
}
}
// MARK: - Layout Configuration
private func createCompositionalLayout() -> UICollectionViewCompositionalLayout {
return CompositionalLayoutFactory.create()
}
//MARK: - Banner Section Indicator Method
private func updateBannerOverlayPosition() {
let indexPath = IndexPath(item: 0, section: HomeSectionType.banner.rawValue)
guard let attributes = homeCollectionView.layoutAttributesForItem(at: indexPath) else { return }
let cellFrame = attributes.frame
let convertedFrame = homeCollectionView.convert(cellFrame, to: view)
bannerPageLabel.snp.remakeConstraints {
$0.size.equalTo(CGSize(width: 44, height: 28))
$0.top.equalToSuperview().offset(convertedFrame.minY + 16)
$0.trailing.equalToSuperview().inset(15)
}
bannerPageIndicatorImageView.snp.remakeConstraints {
$0.size.equalTo(CGSize(width: 93, height: 8))
$0.top.equalToSuperview().offset(convertedFrame.maxY - 20)
$0.centerX.equalToSuperview()
}
}
private func updateBannerPageIndicator(currentPage: Int) {
bannerPageLabel.text = "\(currentPage + 1)/\(bannerData.count)"
bannerPageIndicatorImageView.image = bannerPageIndicatorImage[currentPage]
}
}
// MARK: - UICollectionView Delegate & DataSource
extension HomeViewController: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateBannerOverlayPosition()
}
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
guard HomeSectionType(rawValue: indexPath.section) == .banner else { return }
let currentPage = indexPath.item
DispatchQueue.main.async { [weak self] in
self?.updateBannerPageIndicator(currentPage: currentPage)
}
}
}
extension HomeViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return HomeSectionType.allCases.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let sectionType = HomeSectionType(rawValue: section) else { return 0 }
switch sectionType {
case .banner:
return bannerData.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let sectionType = HomeSectionType(rawValue: indexPath.section) else {
return UICollectionViewCell()
}
switch sectionType {
case .banner:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: BannerCell.identifier,
for: indexPath) as! BannerCell
cell.configure(image: bannerData[indexPath.item].Image)
return cell
}
}
}
| 이유 | 설명 |
|---|---|
| 셀 내부에 넣으면 | 셀 재사용 시 매번 인디케이터 상태를 셀마다 처리해야 함 → 복잡함, 오류 증가 |
| 셀 외부 오버레이 | 하나의 인디케이터만 관리하면 되고, 위치만 계산하면 됨 → 안정적이고 깔끔함 |
추후 가능 하다면 멸도의 뷰로 묶어서 관리하도록 리팩토링을 해야 겠습니다.
private let bannerPageLabel = UILabel()
private let bannerPageIndicatorImageView = UIImageView()
→ 이 두 뷰는 UICollectionView 위에 오버레이되며, 배너 섹션 위에 띄워지는 구조입니다.