[TIL] UICollectionView

박주하·2025년 6월 20일

UICollectionView


  • 셀을 그리드처럼 배치할 수 있는 유연한 목록 뷰
  • 예: 갤러리 앱, 쇼핑 앱, 슬라이드 배너
  • UITableView는 "1열 리스트"만 가능하지만, UICollectionView는 "여러 열"로 자유로운 레이아웃 가능

UITableView와 차이점

항목UITableViewUICollectionView
레이아웃1열 세로 방향가로/세로/그리드/자유롭게
커스터마이징제한적매우 유연
섹션 헤더/푸터간단한 지원더 유연하고 커스텀 가능
대표 레이아웃고정UICollectionViewLayout으로 교체 가능

헤더 추가


1. 헤더용 뷰 클래스 구현

final class MyHeaderView: UICollectionReusableView {
    static let id = "MyHeaderView"

    private let titleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .systemGroupedBackground
        titleLabel.font = .boldSystemFont(ofSize: 18)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(titleLabel)

        NSLayoutConstraint.activate([
            titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }

    required init?(coder: NSCoder) { fatalError() }

    func configure(text: String) { titleLabel.text = text }
}

2. 컬렉션 뷰에 등록

collectionView.register(
    MyHeaderView.self,
    forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
    withReuseIdentifier: MyHeaderView.id
)

3. dataSource에서 헤더 반환

func collectionView(_ collectionView: UICollectionView,
                    viewForSupplementaryElementOfKind kind: String,
                    at indexPath: IndexPath) -> UICollectionReusableView {

    guard kind == UICollectionView.elementKindSectionHeader else {
        return UICollectionReusableView()
    }

    let header = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: MyHeaderView.id,
        for: indexPath
    ) as! MyHeaderView

    header.configure(text: "섹션 \(indexPath.section) 헤더")
    return header
}

4. 레이아웃 delegate로 헤더 크기 지정

// 크기 지정을 빼먹으면 헤더가 높이 0 으로 보이지 않으니 꼭 지정!
func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.bounds.width, height: 44)
}

예제 코드


  1. UICollectionView 생성
  2. UICollectionViewCell 등록
  3. dataSource로 데이터 연결
  4. delegate로 동작 제어
  5. UICollectionViewLayout로 레이아웃 설정

UIViewController

import UIKit

// 상수 모아두기
private enum Const {
    static let cellWidth: CGFloat = 80.0
    static let cellHeight: CGFloat = 80.0
    
    static let lineSpacing: CGFloat = 10.0        // 셀 사이 수직 간격
    static let itemSpacing: CGFloat = 10.0        // 셀 사이 수평 간격
    
    static let sectionInsetLeft: CGFloat = 20.0   // 왼쪽 여백
    static let sectionInsetRight: CGFloat = 20.0  // 오른쪽 여백
}

class MyCollectionViewController: UIViewController {

    var collectionView: UICollectionView!
    var items = [String]()   // 이모지 데이터 목록
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 예시 데이터 추가
        for _ in 0..<10 {
            items.append(contentsOf: ["🍎", "🍌", "🍇", "🍓", "🍑", "🍒"])
        }
        
        // 레이아웃 설정
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: Const.cellWidth, height: Const.cellHeight)  // 셀 크기
        layout.scrollDirection = .vertical  // 세로 스크롤
        layout.minimumLineSpacing = Const.lineSpacing         // 줄 간격
        layout.minimumInteritemSpacing = Const.itemSpacing    // 셀 간 간격
        layout.sectionInset = .init(
            top: 0.0,
            left: Const.sectionInsetLeft,
            bottom: 0.0,
            right: Const.sectionInsetRight
        )
        
        // 컬렉션뷰 생성
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.backgroundColor = .white
        view.addSubview(collectionView)
        
        // 셀 및 헤더 뷰 등록
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") // 셀
        collectionView.register( // 헤더 등록
            MyHeaderView.self,
            forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
            withReuseIdentifier: MyHeaderView.id
        )
        
        // 데이터 소스 & 델리게이트 설정
        collectionView.dataSource = self
        collectionView.delegate = self
    }
}

UICollectionViewDataSource

// 데이터 소스 설정
extension MyCollectionViewController: UICollectionViewDataSource {

    // 섹션당 셀 개수
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }
    
    // 셀 구성
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // 재사용 셀 가져오기
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        
        // 셀 스타일
        cell.backgroundColor = .systemTeal.withAlphaComponent(0.2)
        cell.layer.cornerRadius = 8
        
        // 기존 contentView의 하위 뷰 제거 (재사용 때문)
        cell.contentView.subviews.forEach { $0.removeFromSuperview() }
        
        // 이모지 라벨 추가
        let label = UILabel(frame: cell.bounds)
        label.text = items[indexPath.item]
        label.font = .systemFont(ofSize: 40)
        label.textAlignment = .center
        cell.contentView.addSubview(label)
        
        return cell
    }
    
    // 헤더 뷰 구성
    func collectionView(_ collectionView: UICollectionView,
                        viewForSupplementaryElementOfKind kind: String,
                        at indexPath: IndexPath) -> UICollectionReusableView {
        
        // 헤더 타입일 때만 처리
        guard kind == UICollectionView.elementKindSectionHeader else {
            return UICollectionReusableView()
        }
        
        // 헤더 뷰 재사용
        let header = collectionView.dequeueReusableSupplementaryView(
            ofKind: kind,
            withReuseIdentifier: MyHeaderView.id,
            for: indexPath
        ) as! MyHeaderView
        
        // 헤더 텍스트 설정
        header.configure(text: "과일 섹션 \(indexPath.section)")
        return header
    }
}

UICollectionViewDelegateFlowLayout

// 셀/헤더 레이아웃 설정 (FlowLayout 전용 델리게이트)
extension MyCollectionViewController: UICollectionViewDelegateFlowLayout {

    // 헤더 높이 지정
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        referenceSizeForHeaderInSection section: Int) -> CGSize {
        let width = collectionView.bounds.width
        let height = 50.0
        
        return .init(width: width, height: height)
    }
}

UICollectionViewDelegate

// 셀 선택 시 동작
extension MyCollectionViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("선택된 항목: \(items[indexPath.item])")
    }
}

결과 화면

0개의 댓글