상세 컨텐츠

본문 제목

[UIKit] Layout Consistency

iOS

by kimrindev 2025. 5. 25. 16:14

본문


레이아웃 정합성이란?

레이아웃 정합성(Layout Consistency)이란 UIKit이 UICollectionView를 화면에 그리기 위해 내부적으로 유지하고 있는 데이터 구조와 실제 데이터가 '서로 맞아떨어지는 상태'를 의미한다.

 

 

UICollectionView의 레이아웃 캐싱 메커니즘

레이아웃 사전 계산 (Pre-layout Phase)

UICollectionView는 셀을 그릴 때 단순히 그리는 것이 아니다. 내부적으로 "섹션 몇 개, 각 섹션에 셀 몇 개, 셀의 위치, 크기"를 전부 계산해서 기억해둔다. 이를 레이아웃 캐시(layout cache)라고 한다.

 

UICollectionView는 레이아웃을 그리기 전에:
layoutSubviews()나 reloadData() 전 "지금 섹션 몇 개야? 각 섹션에 아이템 몇 개야?"를 먼저 물어본다
섹션 &아이템 수 테이블을 만들고 레이아웃 캐시를 구성한다

 

 

Compositional Layout의 특징

Compositional Layout은 레이아웃을 '전제 조건'으로 계산해두고 이후 변화에 따라 animation을 적용한다.

각 섹션의 그룹/아이템 수, 크기, spacing, scroll type 등 훨씬 더 복잡한 레이아웃 정보를 미리 계산해두기 때문에, 중간에 하나라도 꼬이면 전체 섹션이 다 깨질 수 있다.

 

전체 UICollectionView의 layout을 미리 캐싱해두고, insert, delete, reload 같은 변화가 생기면 해당 변화량을 레이아웃에 덧셈 방식으로 적용한다.

 

 

문제 상황: 0개 섹션의 함정

레이아웃 정합성 위반 시나리오

예를 들어 UIKit이 이렇게 기억하고 있다고 하자:

Section 0 → 5개
Section 1 → 0개
Section 2 → 3개

개발자가 insertItems(at: [IndexPath(item: 0, section: 1)])을 호출하면

 

 

UIKit은 이렇게 생각한다:

"잠깐… 너 방금 전에 Section 1엔 아무것도 없다고 했잖아? 그런데 item 0을 넣는다고?" "그럼 기존 상태랑 안 맞는데? → 레이아웃 깨질 수도 있어!"

 

→ Crash or layout 오류 발생!

 

UIKit의 0개 섹션 처리 원칙

 

UIKit이 Compositional Layout을 설계할 때 고려한 원칙 중 하나는:

"초기 상태에서 셀 개수가 0인 섹션은 렌더링 대상이 아니다."

 

즉, 셀이 없으면:

  • 그룹도 생성 안 됨, 스크롤 크기 계산에서 제외, 보기도 안 보이게 함
  • "그릴 가치가 없는 섹션"으로 선언돼버림

 

 

총 책임자와 하위 책임자 구조

 

총 책임자: UICollectionViewCompositionalLayout

  • 전체 섹션들의 배치, 흐름, layout cycle을 관리
  • 레이아웃 계산을 위해 numberOfSections, numberOfItemsInSection을 물어봄

 

하위 책임자들: 각 Section 레이아웃 정보

  • NSCollectionLayoutSection + UICollectionViewLayoutAttributes 등으로 생성됨
  • "이 섹션에 아이템 몇 개 있어?"라고 물어봤는데 0이라면:
    • layout 정보 자체를 만들지 않음
    • layout 캐시에도 이 섹션 정보 없음

"처음에 0개였던 섹션"

  • 총책임자가 해당 섹션을 관리 목록에 포함시키지 않음
  • 나중에 해당 섹션에 reloadSections, insertItems를 호출해도 하위 섹션 책임자가 존재하지 않음
  • → 에러 발생

 

 

 

 

 

결론

UICollectionView에서 "레이아웃이 한 번도 만들어진 적 없는 섹션"은 시스템적으로 접근조차 불가능하다. 그걸 다시 활성화하려면 반드시 reloadData()로 전체 초기화를 해야 한다.

초기 layout pass 시점에서 "어떤 section이 비어 있다"고 판정되면, 그 이후에 해당 섹션에 데이터를 추가해도 layout은 무시하거나 crash를 발생시킨다. 그걸 막으려면 모든 section이 준비된 시점에서 한 번에 reloadData()를 해줘야 한다.

 

그래서 실제로 0개 가 되지않게 이런식으로 스켈레톤 셀을 보여주고, 실제 데이터가 들어오면 교체하는 방식을 활용한다고 한다.

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    switch section {
    case 0: return nowPlayingMovies.isEmpty ? 1 : nowPlayingMovies.count  // 로딩 셀 1개
    case 1: return upcomingMovies.isEmpty ? 1 : upcomingMovies.count
    case 2: return popularMovies.isEmpty ? 1 : popularMovies.count
    default: return 0
    }
}

 

이렇게 하면하면 문제가 생기지 않는다 

  • UIKit이 처음부터 "이 섹션에는 셀이 있구나"라고 인식
  • 레이아웃 캐시에 해당 섹션이 포함됨
  • 이후 실제 데이터로 reloadSections 또는 insertItems 가능