본문 바로가기

UIKIT

UICollectionViewCompositionalLayout - 간단 사용

 UICollectionViewCompositionalLayout은 iOS에서 컬렉션 뷰 레이아웃을 정의하는 데 사용되는 강력하고 유연한 레이아웃 시스템입니다.

 이 레이아웃은 컴포지셔널 디자인의 개념을 도입하여 섹션, 그룹, 항목을 계층적으로 정의해 복잡한 레이아웃을 간단하게 구성할 수 있도록 해줍니다.

 

구성 요소

1. Items (항목)

  • 컬렉션 뷰에서 가장 작은 데이터 단위입니다.
  • NSCollectionLayoutItem 클래스를 사용해 정의합니다.
  • 크기(size)와 경계(insets)를 설정할 수 있습니다. 
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 
                                      heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

 

2. Groups (그룹)

  • 여러 항목을 포함하는 컨테이너입니다.
  • NSCollectionLayoutGroup 클래스를 사용해 정의하며, 그룹 내의 항목 배치를 설정할 수 있습니다.
  • 그룹의 방향(horizontal, vertical)과 크기를 정의합니다. 
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                       heightDimension: .fractionalHeight(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

 

3. Sections (섹션)

  • 그룹을 포함하며 컬렉션 뷰의 한 영역을 나타냅니다.
  • NSCollectionLayoutSection 클래스를 사용해 정의합니다.
  • 섹션 간격, 콘텐츠 방향, 헤더/푸터 설정 등을 추가할 수 있습니다.
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 10
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)

 

4. Layout (레이아웃)

  • 섹션으로 구성된 전체 컬렉션 뷰의 레이아웃입니다.
  • UICollectionViewCompositionalLayout를 사용하여 생성합니다. 
let layout = UICollectionViewCompositionalLayout(section: section)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

 

전체 흐름 요약

  1. Item → Group → Section → Layout의 순서로 설계합니다.
  2. 설계한 Layout을 UICollectionView에 연결합니다.
  3. 데이터 소스(UICollectionViewDataSource)를 통해 데이터를 공급합니다.

 

전체 예시

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource {

    lazy var collectionView: UICollectionView = {
        // 1. Item 정의
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
                                              heightDimension: .estimated(50))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)

        // 2. Group 정의
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                               heightDimension: .estimated(100))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item])

        // 3. Section 정의
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 10
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)

        // 4. Layout 정의
        let layout = UICollectionViewCompositionalLayout(section: section)

        // 5. UICollectionView 생성
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.backgroundColor = .white
        collectionView.dataSource = self
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")

        return collectionView
    }()

    let data = Array(1...20).map { "Item \($0)\\nDynamic content for size adjustment" }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
    }

    // MARK: - UICollectionViewDataSource

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return data.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .systemBlue

        // 셀 텍스트 레이블 추가
        let label = UILabel(frame: cell.contentView.bounds)
        label.text = data[indexPath.item]
        label.numberOfLines = 0
        label.textAlignment = .center
        label.textColor = .white
        label.font = UIFont.boldSystemFont(ofSize: 16)
        label.sizeToFit()

        // 중복 방지
        for subview in cell.contentView.subviews {
            subview.removeFromSuperview()
        }

        cell.contentView.addSubview(label)
        return cell
    }
}

 

 

🔥 NSCollectionLayoutSize는 UICollectionViewCompositionalLayout에서 아이템, 그룹, 또는 섹션의 크기를 정의할 때 사용되는 클래스입니다. 이 클래스는 세 가지 방식으로 크기를 설정할 수 있습니다:

 

1. 절대 값 (Absolute)

  • 설명: 크기를 고정된 점(point) 값으로 설정합니다. 화면의 크기나 레이아웃에 관계없이, 항상 같은 크기를 유지합니다.
  • 사용 예시: 아이콘, 버튼 등 항상 고정된 크기를 가져야 하는 경우. 
let absoluteSize = NSCollectionLayoutSize(widthDimension: .absolute(44),
                                          heightDimension: .absolute(44))

 

2. 추정 값 (Estimated)

  • 설명: 초기 추정 크기를 제공하며, 시스템이 런타임에 실제 콘텐츠 크기에 맞게 자동으로 크기를 계산합니다.
  • 사용 예시: 데이터에 따라 크기가 유동적으로 변하는 콘텐츠 (예: 동적으로 늘어나는 텍스트). 
let estimatedSize = NSCollectionLayoutSize(widthDimension: .estimated(200),
                                           heightDimension: .estimated(100))

 

3. 비율 값 (Fractional)

  • 설명: 부모 컨테이너의 크기에 대해 상대적인 비율로 크기를 설정합니다.
  • 사용 예시: 특정 비율을 유지해야 하는 콘텐츠 (예: 화면 너비의 50%에 해당하는 셀).
let fractionalSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                            heightDimension: .fractionalWidth(0.2))

 

 

🔥 Estimated? 이건 꼭 필요한가?

 

  • 초기 레이아웃 성능 최적화:
    • UICollectionView는 처음 레이아웃을 계산할 때 .estimated 값을 기준으로 셀의 위치와 크기를 미리 계산합니다.
    • 이 값이 너무 작거나 크면 레이아웃 계산이 비효율적일 수 있습니다.
  • 스크롤 성능:
    • 스크롤 시 새로운 콘텐츠를 로드하거나 셀이 재사용될 때 .estimated 값이 초기 레이아웃의 크기를 결정합니다.
    • 현실적인 추정값을 제공하면, 콘텐츠를 올바르게 예측하여 불필요한 레이아웃 재계산을 줄일 수 있습니다.
  • 플리커 현상 방지:
    • .estimated 값이 너무 작으면 콘텐츠 크기가 변경될 때 플리커(깜빡임) 현상이 발생할 수 있습니다.
    • 적절한 추정값을 제공하면 이러한 문제를 최소화할 수 있습니다.

결론 -> 적절하게 사용할 것 

  • .estimated 값은 초기 레이아웃 계산과 성능 최적화를 위해 중요합니다.
  • 현실적인 평균값을 설정하는 것이 가장 이상적입니다.
    • 예: 콘텐츠 크기가 100~200 사이면, 추정값은 150 정도로 설정.
  • 너무 작거나 큰 값을 사용하면 플리커 현상, 스크롤 성능 저하 등의 문제가 발생할 수 있습니다.