WWDC19에서 소개된 내용이에요
CollectionView의 Layout을 커스텀하기 쉽게 만들어주는 역할을 해줘요
iOS13+ 에서 사용할 수 있는 API구요
들어가기앞서 이 레이아웃을 사용하기위해서 기본적으로 알아야할 개념들이있어요
NSCollectionLayoutSize
NSCollectionLayoutItem
NSCollectionLayoutGroup
NSCollectionLayoutSection
여기에 추가적으로
NSCollectionLayoutSupplementaryItem
NSCollectionLayoutBoundarySupplementaryItem
까지!
(이부분은 헤더뷰 푸터뷰 필요없으면 안보셔도 OK)
왜알아야해...?
UICollectionViewCompositionalLayout를 만들기위해서는
NSCollectionLayoutSection을 필요로하구요
NSCollectionLayoutSection을 만들기위해서는
NSCollectionLayoutGroup이 필요하고...
NSCollectionLayoutGroup은 NSCollectionLayoutItem를 상속받아 만들어졌어요
아이템이 모인 그룹이니까 아이템이라고 볼 수도 있죠
NSCollectionLayoutGroup을 만들기위해선 NSCollectionLayoutItem이 필요하죠
이렇게 모두 연결되어있기 때문에 다 알아야해요 ㅎㅎ
레이아웃은
이런형태로 구성되어있어요
NSCollectionLayoutSize
let size = NSCollectionLayoutSize(widthDimension: .absolute(100), heightDimension: .fractionalHeight(0.2))
셀의 크기를 결정해주는 요소에요
옵션으로
absolute - 고정 크기
fractionalHeight - 포함된 그룹안의 세로비율만큼의 크기(최대 1)
fractionalWidth - 포함된 그룹안의 세로비율만큼의 크기(최대 1)
estimated - 최소크기
를 나타내고
포함된 그룹안의 ~비율만큼크기?
포함된 뷰가 200 x 100 크기라면
fractionalWidth(0.2)를 적용한다면
뷰의 가로크기는 200이니까 200 * 0.2 -> 40크기를 받겠네요
비율에 맞게 늘어나고 줄어들도록 할 수 있어요
NSCollectionLayoutItem
let item = NSCollectionLayoutItem(layoutSize: size)
말 그대로 아이템을 나타내요
위에서 만든 size를 적용한 Cell 이죠
NSCollectionLayoutGroup
NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 4)
NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [leadingItem, trailingGroup])
가로와 세로를 기준으로 만들수 있고 또는 커스텀으로 만들 수 있어요
같은 크기의 셀을 반복하느냐
다른 크기의 셀을 반복하느냐 그런 차이가있죠
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout
그룹넣어주고 섹션넣어주고..
레이아웃 적용하면 끗!
적용을 해봐야겠죠?!
.
.
.
.
간단예제
우선 간단한 예제부터 시작해보려구요
let size = NSCollectionLayoutSize(widthDimension: .absolute(100), heightDimension: .fractionalHeight(0.2))
let item = NSCollectionLayoutItem(layoutSize: size)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.5))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 4)
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = colors[indexPath.item % colors.count]
return cell
}
}
위의 코드를 적용시킨결과를 살펴볼게요
우선 섹션 2개니까 반복되는게 2개가 보이구요
Cell Size == NSCollectionLayoutSize 라고했잖아요?
셀크기는 가로 100고정
세로는 그룹세로길이의 0.2
얼추맞는것 같죠?
그룹의 크기는
컬렉션뷰 가로길이의 1
컬렉션뷰 세로길이의 0.5
딱 맞네요!
그룹의 아이템으로는
위에서 적용한 Cell Size를
가로로 4개까지 포함 되도록했어요
그래서 지금 3개의 셀이기때문에 한칸이 비어있네요
이상태에서 만약 5개의 셀이 있다면
그룹이 아래에 하나 더 생기겠죠
아래처럼요
핑크색선이 그룹을 나타내고
갈색선이 섹션을 나타내요
벳지
NSCollectionLayoutSupplementaryItem
문서의 설명으론
벳지나 프레임같이 시각적 장식을 추가하는데 사용되는 객체 라고 써잇어요
궁금해서 써보려구요 ㅎㅎ
먼저
NSCollectionLayoutAnchor
라는게 있는데
Cell의 어디에 붙을거니? 를 정해주는거에요
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: .init(x: 0.3, y: -0.3))
그다음 벳지의 사이즈를 정해주고
아이템을 만들어주면 되요
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20), heightDimension: .absolute(20))
let badge = NSCollectionLayoutSupplementaryItem(layoutSize: badgeSize, elementKind: "badge", containerAnchor: badgeAnchor)
let badgeItem = NSCollectionLayoutItem(layoutSize: size, supplementaryItems: [badge])
이렇게 만든
벳지가 붙은 아이템을
적용하려면 어떻게해야할까요
먼저 벳지뷰를 담당할
UICollectionReusableView Class를 하나 생성해주세요
그리고
등록을 해줘야해요
class BadgeView: UICollectionReusableView {}
collectionView.register(BadgeView.self, forSupplementaryViewOfKind: "badge", withReuseIdentifier: "BadgeView")
그다음
DataSource에 있는 함수에서 벳지에 대한 정의를 해줘야해요
저는 빨간색을 적용하고
동그랗게 해줬어요
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == "badge" {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "BadgeView", for: indexPath)
view.backgroundColor = .red
view.layer.cornerRadius = 10
return view
} else {
return .init()
}
}
코드 변경사항은 item을
badgeItem으로 바꿔주기만했어요
헤더뷰 푸터뷰
NSCollectionLayoutBoundarySupplementaryItem
라는 클래스를 사용해요
위에서 본
NSCollectionLayoutSupplementaryItem 와는 다른 용도로 사용돼요
헤더뷰나 푸터뷰의 레이아웃을 추가할 때 사용하는 객체구요
벳지를 사용할 때와 사용법은 비슷해요
등록을먼저해주고
collectionView.register(HeaderView.self,
forSupplementaryViewOfKind: "header",
withReuseIdentifier: "HeaderView")
collectionView.register(FooterView.self,
forSupplementaryViewOfKind: "footer",
withReuseIdentifier: "FooterView")
헤더와 푸터의 사이즈를 지정해줘요
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(44))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize,
elementKind: "header",
alignment: .top)
let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(22))
let footer = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerSize,
elementKind: "footer",
alignment: .bottom)
// 헤더뷰가 계속 떠잇도록 해주는 프로퍼티
header.pinToVisibleBounds = true
section.boundarySupplementaryItems = [header, footer]
마지막에 section에 적용해줘야하구요
헤더뷰에 Label을 달아줘밨어요
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == "badge" {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "BadgeView", for: indexPath)
view.backgroundColor = .red
view.layer.cornerRadius = 10
return view
} else if kind == "footer" {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FooterView", for: indexPath)
view.backgroundColor = .gray
return view
} else if kind == "header" {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath)
view.backgroundColor = .purple
let label: UILabel = {
$0.text = "헤더뷰"
$0.textColor = .white
return $0
}(UILabel())
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
return view
} else {
return .init()
}
}
Nested CollectionVIew 중첩 레이아웃
중첩된 그룹을 사용해서
여러 레이아웃이 적용될 수 있는 컬렉션뷰를 해볼거에요
이런 레이아웃을 한번 구현해볼까요
코드를 먼저 보고 설명할게요
// 상단 왼쪽 셀
let leadingSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
heightDimension: .absolute(100))
let leadingItem = NSCollectionLayoutItem(layoutSize: leadingSize)
// 상단 오른쪽 셀
let trailingSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .absolute(100))
// absolute가 먹히질않는 이슈가있음.. 고정되지않고 group의 크기에 맞게 늘어남
let trailingItem = NSCollectionLayoutItem(layoutSize: trailingSize)
let trailingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(0.5))
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize,
subitem: trailingItem,
count: 3)
// 상단 그룹
let topGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5))
let topGroup = NSCollectionLayoutGroup.horizontal(layoutSize: topGroupSize,
subitems: [leadingItem, trailingGroup])
상단부터 구현할거에요
이렇게 생겼네요
왼쪽 부분과 오른쪽 부분을 나눠서 구현해볼거에요
왼쪽은 leadingItem
오른쪽은 trailingItem을 만들고
3개가 잇어야하니까 trailingGroup으로 만들어줄거구요
leadingItem과 trailingGroup을
하나의 그룹으로 묶을거에요
묶어서 topGroup이라고 정의했고
가로로 스크롤될 수 있도록 구현했어요
크기를 조정하는 부분에서 absolute가 고정크기를 나타내는건데 먹히질 않더라구요.. 흠
그룹 크기에 따라 늘어나기도 줄어들기도해요
그룹이 있다면
그룹방향과 같은 쪽은 (세로방향 - 높이, 가로방향 - 넓이)
그룹방향사이즈 / count로 넓이를 균등하게 주는것같아요
다음으로 아래 부분!
작은 셀과
큰 셀이 보이네요
let bottomSmallItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.3))
let bottomBigItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.7))
let bottomSmallItem = NSCollectionLayoutItem(layoutSize: bottomSmallItemSize)
let bottomBigItem = NSCollectionLayoutItem(layoutSize: bottomBigItemSize)
let bottomGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5))
let bottomGroup = NSCollectionLayoutGroup.vertical(layoutSize: bottomGroupSize,
subitems: [bottomSmallItem, bottomBigItem])
비율에 맞도록
topGroup과 bottomGroup은 각각 반씩 차지하도록 0.5의 높이를 가지도록했어요
let containerGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(1))
let containerGroup = NSCollectionLayoutGroup.vertical(layoutSize: containerGroupSize,
subitems: [topGroup, bottomGroup])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .continuous
마지막으로
이 topGroup과 bottomGroup을 하나의 그룹으로 또 묶어주면
완성이에요
그리고 섹션 프로퍼티중
orthogonalScrollingBehavior
라는 게 있는데
스크롤 형식을 설정할 수 있어요
continuous - 일반적 이동가능
continuousGroupLeadingBoundary - 무조건 전페이지의 leading으로이동
groupPaging - 가까운 그룹으로 페이징효과(leading에 맞춰짐)
groupPagingCentered - 그룹의 센터로 이동
none - 스크롤 x
paging - 컬렉션뷰기준 페이징
원하는 동작방식에 맞게 설정해주시면 됩니다!
이렇게 구현하면 셀은 어떤형식으로 보여질까요?
최종 그룹인
containerGroup이 vertical로 선언되었네요
그럼 세로로 만들어질거니까
containerGroup하나는 보라색 영역이 될거에요
containerGroup이 세로로 선언되서
topGroup
bottomGroup 순으로 cell이 생길거구요
더 많아진다면
가로로 그룹이 추가될거에요
섹션을 2개로했다면 아래에 같은 레이아웃이 또있겟죠
아래는 전체코드에요
func nestedCollectionView() -> NSCollectionLayoutSection {
let leadingSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
heightDimension: .absolute(100))
let leadingItem = NSCollectionLayoutItem(layoutSize: leadingSize)
let trailingSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .absolute(100))
// absolute가 먹히질않는 이슈가있음.. 고정되지않고 group의 크기에 맞게 늘어남
let trailingItem = NSCollectionLayoutItem(layoutSize: trailingSize)
let trailingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(0.5))
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize,
subitem: trailingItem,
count: 3)
let topGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5))
let topGroup = NSCollectionLayoutGroup.horizontal(layoutSize: topGroupSize,
subitems: [leadingItem, trailingGroup])
let bottomSmallItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.3))
let bottomBigItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.7))
let bottomSmallItem = NSCollectionLayoutItem(layoutSize: bottomSmallItemSize)
let bottomBigItem = NSCollectionLayoutItem(layoutSize: bottomBigItemSize)
let bottomGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5))
let bottomGroup = NSCollectionLayoutGroup.vertical(layoutSize: bottomGroupSize,
subitems: [bottomSmallItem, bottomBigItem])
let containerGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(1))
let containerGroup = NSCollectionLayoutGroup.vertical(layoutSize: containerGroupSize,
subitems: [topGroup, bottomGroup])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .continuous
return section
}
정렬된 코드인데 포스팅하면 코드위치가 어긋나네요..ㅠ..
'iyOmSd > Title: Swift' 카테고리의 다른 글
[Swift] Image Resize (5) | 2021.05.29 |
---|---|
[Swift] UITableViewDiffableDataSource (0) | 2021.05.01 |
[Swift] 정규식으로 유효성 확인하기 (2) | 2021.03.20 |
[Swift] TTS 텍스트 음성전환 (AVSpeechSynthesizer) (0) | 2021.02.23 |
[Swift] AVAudioRecord, AVAudioPlayer, AVAudioEngine을 사용한 음성파일 재생, 녹음, 효과음 주기 (2) | 2021.02.19 |