UICollectionViewCompositionalLayout에 대한 연구

DevelopRecord·2022년 5월 27일
0

안녕하세요. 오늘은 UICollectionViewCompositionalLayout 이라는 레이아웃에 대해 알아볼 거에요.

먼저 UICollectionViewCompositionalLayout는 WWDC19에서 소개된 내용입니다.

iOS 13 이상부터 사용이 가능한 기능이구요. 복잡한 컬렉션뷰를 구성할 때 사용하기 좋아요.

UICollectionView로 구성된 레이아웃이라고 이름에서 어림짐작 할 수가 있어요.

이 레이아웃을 사용하기 전에 먼저 알아야 할 개념들이 존재합니다.
( 여기를 보시면 좋을 것 같아요. )

사진에서 나와있듯이 Section, Group, Item 이 세개를 알아야 합니다.

ItemGroup라는 레이아웃에 속해 있고,
GroupSection이라는 레이아웃에 속해 있죠?

말 그대로 Section을 만드려면 Group이 필요하고,
Group를 만드려면 Item이 필요해요.

그럼 우리는 역순으로 Item을 먼저 구현해야 합니다.

또한 셀의 크기를 결정지어주는 요소들을 알아야 할 것이 몇가지 더 있습니다.

absolute
-> 항상 고정 크기를 계산합니다.

estimated
-> 런타임에 따른 크기 결정. 시스템 예상 크기를 기반으로 실제 크기를 계산합니다.

fractional
-> 속해 있는 레이아웃의 그룹의 크기 비율만큼 크기를 계산합니다.


lazy var collectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: HomeController.createLayout())
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.register(HomeTopCell.self, forCellWithReuseIdentifier: HomeTopCell.identifier)
    collectionView.register(HomeCell.self, forCellWithReuseIdentifier: HomeCell.identifier)
    collectionView.translatesAutoresizingMaskIntoConstraints = false

    return collectionView
}()

먼저 뷰컨안에 늘 하던대로 collectionView 프로퍼티를 생성해 주시구요.
안에 보시면 HomeController.createLayout()이라고 있죠?

static func createLayout() -> UICollectionViewCompositionalLayout {
    return UICollectionViewCompositionalLayout { sectionNumber, env in
        if sectionNumber == 0 {
            let item: NSCollectionLayoutItem = .init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
            item.contentInsets.leading = 10
            item.contentInsets.bottom = 5
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(0.95), heightDimension: .absolute(200)), subitems: [item])
            let section = NSCollectionLayoutSection(group: group)
            section.orthogonalScrollingBehavior = .groupPaging
            return section
        } else if sectionNumber == 1 {
            let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
            item.contentInsets.leading = 10
            item.contentInsets.bottom = 5
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .absolute(190), heightDimension: .absolute(190)), subitems: [item])
            let section = NSCollectionLayoutSection(group: group)
            section.orthogonalScrollingBehavior = .groupPaging
            return section
        } else if sectionNumber == 2 {
            let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
            item.contentInsets.leading = 10
            item.contentInsets.bottom = 5
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .absolute(190), heightDimension: .absolute(190)), subitems: [item])
            let section = NSCollectionLayoutSection(group: group)
            section.orthogonalScrollingBehavior = .groupPaging
            return section
        } else {
            let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
            item.contentInsets.leading = 10
            item.contentInsets.bottom = 5
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .absolute(150), heightDimension: .absolute(150)), subitems: [item])
            let section = NSCollectionLayoutSection(group: group)
            section.orthogonalScrollingBehavior = .continuous
            return section
        }
    }
}

무엇인가가 상당히 많습니다. 저도 이해하는데 오래 걸렸어요..
중복되는 코드만 많으니까 SectionNumber 별로 쪼개서 보면 훨씬 나으실 거에요.

let item: NSCollectionLayoutItem = .init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))

위에서 말했던 내용 기억하시나요? 우리는 item을 먼저 구현해야 해요.

item은 NSCollectionLayoutItem을 상속받습니다.

안에 레이아웃 사이즈를 보시면 fractionalWidth(1), fractionalHeight(1) 이렇게 있는데,

1이 의미하는 바는 Group의 사이즈의 비율에 대한 크기 지정입니다.
예를 들어보면 만약 Group의 너비, 높이가 100일 때,
Item의 크기를 fractionalWidth(0.1)로 지정하면
Item의 너비는 10을 받을 수 있겠네요.

let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(0.95), heightDimension: .absolute(200)), subitems: [item])

다음 Group은 NSCollectionnLayoutGroup를 상속받아요.
.horizontal 이라고 있는데 가로나 세로 기준으로 커스텀이 가능하구요. (.vertical로 존재합니다)

fractional은 앞에서 했던 개념이고 absolute는 단순합니다.
괄호 안에 넣은 값을 고정값으로 사용하겠다 이말이에요.

let section = NSCollectionLayoutSection(group: group)

마지막 Section은 NSCollectionLayoutSection을 상속합니다.
위에서 만든 Group을 받아주면 끝이구요.

section.orthogonalScrollingBehavior = .groupPaging

이건 페이징 방식을 지정합니다.
( 여기를 보시면 좋을 것 같아요. )

위 빨간 셀처럼 한 개씩 그룹별로 나눠서 스크롤 하는 방식이라 보시면 됩니다.
그리고 이건 HIG에도 명시되어 있고 개인적으로 중요하다 생각이 드는 것이,
옆에 다음 셀의 끝을 조금 보여주어 사용자에게 스크롤 가능하다는 것을 알려줍니다.
앱을 만들 때 궁극적인 목표가 사용자가 무조건적으로 이해하기 쉽고 편리한 UI여야 하기 때문이에요.

마지막 네번째 섹션은 .groupPaging 방식이 아닌, .continuous 입니다.
이 외에도 다른 페이징 방식이 존재하니까 하나씩 써보는 것도 좋을 것 같아요.

func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 4
}

그리고 섹션의 수는 위의 레이아웃 섹션 수에 맞게 설정합니다.

요즘 앱들 보면(앱스토어, 유튜브 뮤직 등등) 많은 어플들이 복잡한 컬렉션뷰의 모양을 가지고 있어요.
그럴 때 사용하시면 중첩 컬렉션뷰를 하지 않아도 되고 코드도 간단해집니다.

참고

레퍼런스

0개의 댓글