[iOS] UICollectionViewCompositionalLayout (1)

2023. 11. 19. 17:35iOS

반응형

우선 이번 글에서는 UICollectionView의 Layout을 지정하는 새로운 방법을 알아보려고합니다

구성을 위한 가장 기본적인 컴포넌트와 어떤 구조를 가지고 있는지 정도만 알아보고 다음 글에서 좀 더 구체적인 사용법들을 포스팅해보겠습니다 :)

 

UICollectionViewCompositionalLayout

우선 애플에서는 해당 레이아웃에 대해 유연하고 적응력 있게 우리가 기존에 사용하던 CollectionView의 레이아웃을 구성할 수 있다고 설명하고 있습니다

A layout object that lets you combine items in highly adaptive and flexible visual arrangements.

UICollectionViewCompositionalLayout은 한개 혹은 여러개의 Seciton을 가지는데, Section은 안에 여러개의 그룹으로 레이아웃이 나뉘어져 있습니다. 하나의 섹션은 여러개의 그룹 그리고 그룹은 개별 아이템들로 구성되어있습니다.

 

그림으로 이해하면 좀 더 이해가 쉬울 것 같은데요.

그림을 보시면 Section → Group → Item으로 이루어져서 하나의 Layout을 구성하는 것을 알 수 있습니다. 그렇다면 간단하게 코드로는 어떻게 구현되어 있는지 보고 각각의 구성요소에 대해 알아보겠습니다 🙃

func createBasicListLayout() -> UICollectionViewLayout { 
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),                                  
                                         heightDimension: .fractionalHeight(1.0))    
    let item = NSCollectionLayoutItem(layoutSize: itemSize)  
  
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),                                          
                                          heightDimension: .absolute(44))    
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,                                                   
                                                   subitems: [item])  
  
    let section = NSCollectionLayoutSection(group: group)    

    let layout = UICollectionViewCompositionalLayout(section: section)    
    return layout
}

가장 UICollectionViewCompositionalLayout의 기본적인 구성요소들을 코드로 작성하는 방법입니다.

위에서 설명했듯이 Section -> Group -> Item의 구성으로 이루어져서 하나의 레이아웃이 구성되는 것을 알 수 있습니다.

우선, 좀 더 복잡한 레이아웃은 다음 글에서 알아볼테니 오늘은 기본적인 각각의 컴포넌트에 대해 알아보도록하겠습니다 :)

 

NSCollectionLayoutSize

UICollectionViewCompositionalLayout에서 각 아이템 or 그룹의 사이즈를 지정할 때, 사용할 수 있는 타입이다.

이 타입의 생성자에서 NSCollectionLayoutDimesion을 받고 있는데 width, height의 쌍을 이루어 크기를 지정하는 타입입니다.

@available(iOS 13.0, *)
open class NSCollectionLayoutSize : NSObject, NSCopying {
    public convenience init(
        widthDimension width: NSCollectionLayoutDimension, 
        heightDimension height: NSCollectionLayoutDimension
    )
}

 

NSCollectionLayoutDimension

NSCollectionLayoutSize를 구성하고 있는 이 타입은 무엇일까요?

애플에서는 CollectionView의 높이 or 넓이를 보여주는 개별적인 값이라고 설명하고있습니다.

An individual dimension representing an item’s width or height in a collection view.

이애 대해 높이 or 높이에 대해 각각을 정의할 수 있게 총 3가지의 방법을 사용해서 나타낼 수 있게 제공하고있습니다.

 

  • Absoulte : 말그대로 절댓값으로 아래 예시에서는 44px에 해당하는 만큼의 width, height를 지정하게 된다
let absoluteSize = NSCollectionLayoutSize(widthDimension: .absolute(44),
                                         heightDimension: .absolute(44))
  • Estimated : 기존에 UITableView, UICollectionView에서 활용하던 estimated와 같은 개념입니다. 실제로 데이터 로드 후, 값이 변하는 경우나 유동적으로 사용해야 할 때 대략의 값을 넘겨주면 런타임 중에 실제 width, height가 계산된다.
let estimatedSize = NSCollectionLayoutSize(widthDimension: .estimated(200),
                                          heightDimension: .estimated(100))
  • Fractional : 이 값으로 설정하면 비율에 따라 유동적으로 변하는 경우에 유용하게 사용할 수 있습니다. 아래 예시와 같게 설정하면 Group의 넓이의 20%에 해당하게 width, height를 설정하게 됩니다.
// 넓이의 20%에 해당하게 width, height 설정
let fractionalSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                           heightDimension: .fractionalWidth(0.2))

// 높이의 20%에 해당하게 width, height 설정
let fractionalSize = NSCollectionLayoutSize(widthDimension: .fractionalHeight(0.2),
                                           heightDimension: .fractionalHeight(0.2))

 

NSCollectionLayoutItem

이제 위에서 보았던 NSCollectionLayoutDimension, NSCollectionLayoutSize 이 요소들을 사용해서 Layout을 이루는 가장 기본 구성단위인 Item에 대해서 알아보겠습니다.

 

Layout의 기본 단위인 Item은 일반적으로 실제 보이는 화면에서 렌더링되는 Cell이라고 보아도 될 것 같습니다. 물론 Item은 헤더, 푸터로 제공되는 supplementary views일수도 있습니다 !

 

Item의 크기를 잡는 방법은 위의 요소에서 설명했든 LayoutSize를 이용해서 컨테이너 뷰와 상대적으로 잡거나, 절대적인 크기 또는 런타임 중에 유동적으로 변화할 수 있게 추정해서 사이즈를 잡을 수도 있습니다.

@available(iOS 13.0, *)
@MainActor open class NSCollectionLayoutItem : NSObject, NSCopying {
    public convenience init(layoutSize: NSCollectionLayoutSize)
}

 

NSCollectionLayoutGroup

이 타입은 Item들의 집합으로 Item들이 어떻게 화면에 배열되고 어떤 관계를 가지는지를 결정하는 요소입니다. 자체로는 콘텐츠를 렌더링하지 않지만 내부 Item들이 실제 UI에 렌더링이 되고 어떻게 보일지를 결정하는 요소입니다. 그러나 Group 역시 NSCollectionLayoutItem을 상속받고 있어서 Item처럼 사용될 수 있습니다.

 

이를 활용해서 다른 Group 안의 요소로 넣어서 Group을 중첩해서 아이템처럼 사용하는 것이 가능합니다. 그렇기 때문에 이를 활용하면 더욱 다양한 Layout을 쉽게 표현하는 것이 가능해집니다. 

 

NSCollectionLayoutSection

NSCollectionLayoutGroup보다 한단계 큰 개념으로 그룹들을 묶는 집합의 역할을 합니다. 여기서 각각의 섹션들은 같은 레이아웃을 가질수도 있고 다른 레이아웃을 가질수도 있는데, 이는 내부 요소의 Group 객체들이 결정합니다.

 

Section에 Header, Footer들을 넣을 수 있습니다. 그리고.. 가장 놀랬던 점은 기존에는 vertical axis를 가진 CollectionView에 내부의 horizonl axis를 가진 스크롤이 필요할 경우 CollectionView를 중첩해서 넣어야했지만, 여기서는 이럴 필요가 없어졌습니다 😎

 

바로 해당 객체의 orthogonalScrollingBehavior 프로퍼티를 설정함으로 이를 가능하게 합니다. 만약 Section에서 메인 axis와 다른 값을 가져야할 경우 이 값을 수정하면 됩니다

 

심지어 이 값을 활용하면 그동안 inset을 가진 CollectionView의 페이징 시, center 배치를 위해 직접 offset을 계산해서 넣어주어야 하는 과정이 필요했는데 이것도 설정값으로만 가능해졌습니다… (애플이 개발자들에게 생각을 하지못하게 하는건가.. 바보가 되어가는 느낌)

 

NSCollectionLayoutAnchor

이 타입은 Supplymentary 아이템이 CollectionView 안에서 어떻게 위치하는지를 정의하는 객체입니다. 크게 두 가지를 이용해서 어디에 위치할지를 정해줄 수 있습니다.

 

  • Edges : Supplymentary 아이템이 모서리 중, 어디쪽에 붙을지를 정의할 수 있습니다. 총 4가지의 정보가 있고 2가지의 인접한 엣지를 이용해서 총 8가지의 위치를 정의할 수 있습니다.

  • Offset : 정의했던 Edge로부터 어느정도 떨어지는지를 정의할 수 있습니다. 여기서도 정의할 수 있는 2가지 방법이 있습니다.
    • Absolute : 픽셀값으로 지정하는 방법입니다. 흔히 사용하던 절대적으로 값을 지정하는 방법입니다.
    • Fractional : 상대적인 값으로 지정하는 방법입니다. 만약 x = 0.3으로 지정한다면 width의 30% 정도의 위치만큼 떨어진다는 뜻입니다.
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(44),
                                     heightDimension: .absolute(44))
    
// Fractional
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing],
                                           fractionalOffset: CGPoint(x: 0.3, y: -0.3))
// Absolute
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing],
                                           absoluteOffset: CGPoint(x: 30, y: -30)
    
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20),
                                      heightDimension: .absolute(20))
    
let badge = NSCollectionLayoutSupplementaryItem(layoutSize: badgeSize,
                                               elementKind: "badge",
                                               containerAnchor: badgeAnchor)
    
let item = NSCollectionLayoutItem(layoutSize: itemSize,
                                  supplementaryItems: [badge])

 

NSCollectionLayoutSupplementaryItem

CollectionView 아이템들의 주변에 시각적인 효과를 만들어줄 수 있는 객체이다. 아래 그림에서 우측 상단의 아이콘 같은 요소를 표현할 때 사용할 수 있습니다. (근데, 개인적으로는 Cell 안에 아이콘을 넣지 해당 객체를 이용할 것 같진 않습니다..🙁)

 

NSCollectionLayoutBoundarySupplementaryItem

CollectionView의 Seciton의 Header, Footer로 활용할 수 있는 객체이다. 이 객체 역시 NSCollectionLayoutSupplementaryItem을 상속받아서 사용하고 있습니다. 

let section = NSCollectionLayoutSection(group: group)
let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                              heightDimension: .estimated(44))

let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerFooterSize,
                                                                elementKind: ElementKind.sectionHeader,
                                                                alignment: .top)
let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerFooterSize,
                                                                elementKind: ElementKind.sectionFooter,
                                                                alignment: .bottom)
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

 

일단 오늘은 간단히 UICollectionViewCompositionalLayout의 기본적인 요소에 대해 알아보았습니다 :)

다음에는 이를 이용해 간단한 레이아웃들을 구성해보고 좀 더 사용해보면서 알아가보도록하겠습니다 

반응형