Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing Modern Collection Views 애플 개발자 문서 1편 #159

Closed
Youngminah opened this issue Apr 26, 2022 · 1 comment
Closed

Comments

@Youngminah
Copy link
Owner

Youngminah commented Apr 26, 2022

Implementing Modern Collection Views



GridView: Horizontal

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                     heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
                                                 subitems: [item])

let section = NSCollectionLayoutSection(group: group)

let layout = UICollectionViewCompositionalLayout(section: section)
return layout
  • NSCollectionLayoutGroup.horizontal()은 item이 채워지는 방향이 horizontal임을 말함.


GridView: Vertical

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .fractionalHeight(0.2))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                      heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize,
                                                 subitems: [item])

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary

let layout = UICollectionViewCompositionalLayout(section: section)
return layout
  • NSCollectionLayoutGroup.vertical()로 설정할 경우 item이 수직 방향으로 채워짐
  • 그리고 수직방향으로 채워지면서
  • 직교 방향으로 스크롤을 원할 경우, 또는 직교 방향으로 item을 계속 채우고 싶은 경우는
  • section에 orthogonalScrollingBehavior값을 설정해주어야한다.
  • 궁금한 부분 NSCollectionLayoutGroup.vertical()이렇게 바로 쓸 수 있는 이유
    • 코드를 뜯어보니 horizontal이나 vertical메소드는
    • NSCollectionLayoutGroup의 인스턴스 메소드가 아닌 타입메소드 이더라.
    • final도 안되어있고 static 타입 메소드를 쓰지 않은 거 보니
    • 혹시나 NSCollectionLayoutGroup을 커스텀으로 만들 때
    • override해서 사용할 수도 있게 해놓은 것 같음.


InsetItemsGridView

let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
  • 이건 정말 별거 없음.
  • 위의 GridView랑 달라진 것은 item끼리의 Inset이 생겻다는 것.
  • 잘보면 item끼리의 inset이 기존보다 넓어짐


TwoColumnView

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, subitem: item, count: 2)
let spacing = CGFloat(10)
group.interItemSpacing = .fixed(spacing)

let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = spacing
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)

let layout = UICollectionViewCompositionalLayout(section: section)
return layout
  • 여기도 별거 없더라~
  • 컬럼을 2개로 사용하고 싶을 때, fractionalWidth를 이용하지 않고,
  • NSCollectionLayoutGroup.horizontal 또는 vertical 함수에 count 인자로 넣어줄 수 있다.
  • 그리고 살펴보면 좋을 것은 Spacing에 관한 것?
    • NSCollectionLayoutGroup 클래스 안의 interItemSpacing item들 사이에 Spacing을 줄 수 있고,
    • NSCollectionLayoutSection 클래스 안의 interGroupSpacing 값으로 group 사이에 Spacing을 줄 수 있다.
    • 두 가지의 차이점은 interGroupSpacing은 CGFloat 타입형인 반면에 ,
    • interItemSpacingNSCollectionLayoutSpacing 타입형이더라.
    • 두가지 타입형이 다른 이유는 분명 차이가 있기 때문이란 생각.
    • 그럼 NSCollectionLayoutSpacing 타입형이면 CGFloat타입형 일때 보다,어떤것이 더 가능할까?
    • NSCollectionLayoutSpacing 타입형이면 CGFloat을 인자로 받는 fixed, flexible 타입 메소드를 이용할 수 있다.
    • fixed는 주어진 인자 CGFloat 그대로 고정된 값이고
    • flexible은 주어진 인자 CGFloat 값보다 크거나 같은 spacing을 줄 수 있다.
    • 아무튼 결론적으로 NSCollectionLayoutSpacing타입형이면 유동적인 spacing이 가능하단 것이 결론
  • 그리고 언제 fractionalWidth를 사용하여 컬럼을 나누고, 언제는 count 인자로 컬럼을 나눌까?
    • count는 컬럼 줄의 갯수를 Int값으로 딱 넣을 수 있음 .
    • 그렇기 때문에 나중에 count값을 constant상수로 정해서 넣기 편리할 것임.
    • 더 나아가 constant 값이라면, 삼항연산자를 사용하여
    • iPad일때, iPhone일 때 constant를 나누어 주더라도 가독성을 해치지 않고
    • 간편하게 구성하기 좋다.


DistinctSectionsView

iPhone

iPad

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int,
        layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

        guard let sectionLayoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil }
        let columns = sectionLayoutKind.columnCount

        // The group auto-calculates the actual item width to make
        // the requested number of columns fit, so this widthDimension is ignored.
        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)

        let groupHeight = columns == 1 ?
            NSCollectionLayoutDimension.absolute(44) :
            NSCollectionLayoutDimension.fractionalWidth(0.2)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                              heightDimension: groupHeight)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
        return section
    }
    return layout
}
enum SectionLayoutKind: Int, CaseIterable {
    case list, grid5, grid3
    var columnCount: Int {
        switch self {
        case .grid3:
            return 3

        case .grid5:
            return 5

        case .list:
            return 1
        }
    }
}
  • UICollectionViewCompositionalLayout에서 레이아웃을 정의할 때 클로저를 이용하고 있는데,
  • 이 클래스의 생성자의 인자를 @escaping으로 처리하고 있는 것들이 있다.
  • 따라서 클로저를 이용하여, 외부에서 sectionindex나 environment활용 가능.
  • 여기서 눈에 띈 점은 layout에서 섹션 처리를 하는 방법이 눈에 띄었음
  • 레이아웃은 규격이 정해져있고 constant값들만 바뀐다.
  • 처음에는, section에 관한 case를 layout 생성부분에서 switch문으로 나누다보니
  • 코드가 너무 길어졌는데 그럴 필요가 없겠단 생각이 들었음.
  • 그래서, section enum값에서 애초에 constant에 해당하는 값들에 대한 올바른 값을 처리해주고,
  • constant들은 enum 타입의 인스턴스를 활용하면 될 것 같다.


AdaptiveSectionsView

iPhone

iPad

enum SectionLayoutKind: Int, CaseIterable {
    case list, grid5, grid3
    func columnCount(for width: CGFloat) -> Int {
        let wideMode = width > 800
        switch self {
        case .grid3:
            return wideMode ? 6 : 3

        case .grid5:
            return wideMode ? 10 : 5

        case .list:
            return wideMode ? 2 : 1
        }
    }
}
func createLayout() -> UICollectionViewLayout {
   ...
   let columns = layoutKind.columnCount(for: layoutEnvironment.container.effectiveContentSize.width)
   ...
}
  • Distinct와의 차이점은 아이패드 일때 적합한 레이아웃을 보여 준다는 것.
  • 신기한점은 layoutEnvironment.container.effectiveContentSize.width로 기기의 너비를 알아내고 있는데,
  • 여기서 NSCollectionLayoutEnvironment 인자 값을 가지고 오는 이유를 알 수 있음.
  • 기기의 너비를 기준으로 일정 기준을 넘으면,
  • 삼항 연산자로 Section count값을 다르게 설정해줌
  • count 뿐만 아니라 constant가 들어갈 수 있는 모든 것들을 이렇게 응용할 수 있을 것임.


SectionHeadersFootersView

func createLayout() -> UICollectionViewLayout {
   ...

    let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                 heightDimension: .estimated(44))
    let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerFooterSize,
        elementKind: SectionHeadersFootersViewController.sectionHeaderElementKind, alignment: .top)
    let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerFooterSize,
        elementKind: SectionHeadersFootersViewController.sectionFooterElementKind, alignment: .bottom)
    section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

    let layout = UICollectionViewCompositionalLayout(section: section)
    return layout
}
  • header, footer의 위치 설정을 alignment에서 설정해 줄 수 있다.
  • sectionHeader.pinToVisibleBounds = true 해주면
  • header 스크롤시 pin 가능


PinnedSectionHeaderFooterView

func createLayout() -> UICollectionViewLayout {
   ...
    sectionHeader.pinToVisibleBounds = true
    sectionHeader.zIndex = 2
    section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

    let layout = UICollectionViewCompositionalLayout(section: section)
    return layout
}
  • 위와 똑같은데 NSCollectionLayoutBoundarySupplementaryItem
  • pinToVisibleBounds라는 설정을 해주면 고정됨
  • zIndex은 z축에서의 우선순위를 나타낸다.


SectionDecorationView

func createLayout() -> UICollectionViewLayout {
   ...
   let sectionBackgroundDecoration = NSCollectionLayoutDecorationItem.background(
            elementKind: SectionDecorationViewController.sectionBackgroundDecorationElementKind)
   sectionBackgroundDecoration.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
   section.decorationItems = [sectionBackgroundDecoration]

   let layout = UICollectionViewCompositionalLayout(section: section)
   layout.register(
            SectionBackgroundDecorationView.self,
            forDecorationViewOfKind: SectionDecorationViewController.sectionBackgroundDecorationElementKind)
    return layout
}
  • section에 데코레이션 뷰 설정가능.
  • 뒷 배경 같은 개념으로 그림자도 넣을 수 있음.


NestedGroupsView

let layout = UICollectionViewCompositionalLayout {
    (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

    let leadingItem = NSCollectionLayoutItem(
        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
                                          heightDimension: .fractionalHeight(1.0)))
    leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)

    let trailingItem = NSCollectionLayoutItem(
        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                          heightDimension: .fractionalHeight(0.3)))
    trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
    let trailingGroup = NSCollectionLayoutGroup.vertical(
        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
                                          heightDimension: .fractionalHeight(1.0)),
        subitem: trailingItem, count: 2)

    let nestedGroup = NSCollectionLayoutGroup.horizontal(
        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                          heightDimension: .fractionalHeight(0.4)),
        subitems: [leadingItem, trailingGroup])
    let section = NSCollectionLayoutSection(group: nestedGroup)
    return section

}
return layout
  • NSCollectionLayoutGroup.horizontal이나 vertical 메소드에는
  • subitems 인자를 배열 형태로 넣을 수 있엇는데, 이유가 여기 있었음,
  • 여러개의 NSCollectionLayoutItem 인스턴스를 넣을 수 있어서,
  • Nested 형태의 자유로운 section 구성이 가능하다.


OrthogonalScrollingView

Simulator Screen Recording - iPhone 13 - 2022-04-27 at 10 51 23

let layout = UICollectionViewCompositionalLayout {
    (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

   ...
   section.orthogonalScrollingBehavior = .continuous
   return section
}
return layout
  • section 에 orthogonalScrollingBehavior 하나만 설정해주고
  • section을 한개 이상 두면 직교방향으로 스크롤 가능.


OrthogonalScrollingBehavior

  • continuous : 연속적인 끊기지 않는 스크롤
  • continuousGroupLeadingBoundary : 그룹 단위로 앞의 Leading에서 자연스럽게 끊기도록 하는 스크롤.
  • paging : 페이지 단위로 스크롤이 끊기도록 함.
  • groupPaging : 그룹을 페이지 단위로 스크롤이 끊김.
  • groupPagingCentered : 그룹을 페이지 단위로 스크롤이 그룹의 중앙을 보여주도록 끊김
  • none : 설정 안해주면 자동으로 default 값. 직교 방향으로 스크롤 허용 안함.


@Youngminah Youngminah changed the title Implementing Modern Collection Views 애플 개발자 문서 Implementing Modern Collection Views 애플 개발자 문서 1편 Apr 27, 2022
@Youngminah
Copy link
Owner Author

Advances in UICollectionView 14.0 이상

WWDC 2020



Emoji Explorer NSDiffableDataSourceSectionSnapshot(14.0)

image

  • 13.0에서는 NSDiffableDataSourceSnapshot라는 identifier기반 snapshot이 나왔는데,
  • 14.0에서는 NSDiffableDataSourceSectionSnapshot구조체가 추가로 등장함.
  • section 별로 snapshot을 나눔. 원하는 section만을 apply가 가능한 개념.


List 등장 CollectionView

image

  • 마치 테이블뷰처럼 이용이 가능하게 해줌.
  • 이것에 알맞는 UICollectionViewListCell 지원
  • 해더 푸터 지원


Modern Cell, New API Cell Registeration

image

  • 가장 큰 장점은 재사용에 용이 하다는 점.
  • 적은 코드로 cell을 등록할 수 있다는 점.


14.0 Collectionview Cell content Configurations

  • valuecell
    image
  • subtitlecell
    image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant