오늘은 구현을 진행하는 것 보다 이전에 짠 코드를 조금 더 이해하고 Compositional Layout에 대해 조금 더 자세하게 이해하는 방향으로 공부했다.
Compositional Layout
이전에 컬렉션 뷰를 다루면서 Flow Layout은 몇번 사용해 봤으니 이번에는 컴포지셔널 레이아웃을 한번 활용해 보자.
먼저 컴포지셔널 레이아웃은 빠르고 유연하게 컬렉션 뷰를 구현할 수 있는 CollectionViewLayout의 한 종류이며 iOS 13.0 이상부터 지원하는 방식이다.
장점
- 복잡한 레이아웃을 선언형 API로 간단하게 구축할 수 있다.
- 하나의 컬렉션 뷰로 다양한 레이아웃을 구성할 수 있다.
- 속도가 빠르다.
Compositional Layout 구성
컴포지셔널 레이아웃은 Item, Group, Section으로 구성되어있다.
Item은 하나의 Item (Cell) 이라고 할 수 있고
Group은 Item의 집합
Section은 여러 Group의 집합이라고 할 수 있다.
NSCollectionLayoutDimension을 통해 각각 레이아웃 사이즈를 지정할 수 있다.
.fractionalWidtg, .fractionalHeight : 부모 컴포넌트 크기에 비례해서 크기를 설정
.absolute : 고정된 크기를 사용할 때 설정
.estimated : 크기가 변동될 수 있을때 설정
// fractional
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
// absolute
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(56), heightDimension: .absolute(56))
// estimate
let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .estimated(100))
자 여기까지 살짝 컴포지셔널 레이아웃을 설명했다면 이제 오늘 수정한 코드를 가지고 어떻게 레이아웃을 설정했는지 보자
Compositional Layout 활용
먼저 최근 본 책을 보여줄 좌우로 스크롤 되는 레이아웃을 구현해보자
case 0:
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(100), heightDimension: .estimated(130))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
section = NSCollectionLayoutSection(group: group)
section?.interGroupSpacing = 5
section?.orthogonalScrollingBehavior = .continuous
group에 원하는 사이즈를 설정하고 item은 fractionalWidth , fractionalHeight 를 각각 1로 설정하면 사이즈는 얼추 원하는 만큼 잡히게 된다. 여기서 나중에 변경하고 싶다면 쉽게 수정하면 된다.
가장 중요한 점은 바로바로
section?.orthogonalScrollingBehavior = .continuous
이 설정을 꼭 넣어주어야 하는 것.. 이것을 넣지 않으면 가로로 스크롤이 되지 않고 화면 밖에 넘어간 셀들이 밑으로 내려와 버린다..
이 설정을 못해서 거의 처음부터 다시 컴포지셔널 레이아웃을 잡았다고 해도 무방하다.. 그래도 덕분에 컴포지셔널 레이아웃에 대해 더 이해할 수 있게 된 것 같다.
이렇게 설정이 완료 됐다면 이렇게 가로로 스와이프가 되는 컬렉션 뷰가 만들어진다.
이제 다음 책 목록을 보여줄 세로로 스와이프 될 컬렉션 뷰의 컴포지셔널 레이아웃을 살펴보자
case 1:
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(170))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
section = NSCollectionLayoutSection(group: group)
section?.interGroupSpacing = 10
item 이 3개 들어갈 수 있도록 fractionalWidth 의 크기를 1/3 으로 설정해 3개의 셀이 들어갈 수 있도록 설정한다.
그리고 각 셀이 살짝 경계를 가지고 있기 위해 leading과 trailing을 5 씩 잡아 설정한다. 이걸 설정을 안했더니 나눠지지 않은 줄 알고.. 여러번 고쳤는데 알고보니 이미 설정이 다 된 상태였다..
이렇게 설정을 넣어주면 바로 이렇게 레이아웃 구성이 완료된다.
Compositional Layout Header 영역 설정
각각 컬렉션 뷰 마다 해당하는 컬렉션 뷰의 제목을 달아주기 위해 헤더 영역을 사용해 구성해 보자!
컬렉션 뷰에 헤더를 넣기위해서는 헤더를 SupplementaryView에 넣어주어야 한다.
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(30))
let headerSupplementary = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section?.boundarySupplementaryItems = [headerSupplementary]
return section
이렇게 설정해주고 이제 헤더를 가지고 있을 셀을 새로 만들자!
class HeaderView: UICollectionReusableView {
static let identifier = "HeaderView"
let titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 25)
label.textColor = .black
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
titleLabel.snp.makeConstraints {
$0.leading.equalToSuperview().offset(5)
$0.centerY.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(title: String) {
titleLabel.text = title
}
}
헤더 셀을 만들고 헤더에 들어갈 레이블을 구성해 넣어준 뒤 레이아웃도 잡아준다.
그 이후 메소드에 들어가야할 label 을 저장해 VC로 전달하면 새로운 셀 만들기 완성
다시 VC로 넘어와 각각 헤더에 들어갈 텍스트를 배열을 통해 만들어준다.
let sectionTitles = ["최근 본 책", "Book List"]
이렇게 만들어진 타이틀을 각각 넣어주면 완성
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard kind == UICollectionView.elementKindSectionHeader else {
fatalError("Unexpected supplementary view kind")
}
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderView.identifier, for: indexPath) as! HeaderView
let sectionTitle: String
switch indexPath.section {
case 0:
sectionTitle = "최근 본 책"
case 1:
sectionTitle = "Book List"
default:
fatalError("Unknown section")
}
headerView.configure(title: sectionTitle)
return headerView
}
CollectionView DataSource를 활용해 viewForSupplementaryElementOfKind 를 불러와 그 안에 헤더에 들어갈 값을 넣어주면 된다!
위에 컴포지셔널레이아웃도 그렇고 두개의 컬렉션 뷰를 스위치를 활용해 각각 케이스에 나눠 설정해뒀으니 헤더도 동일하게 설정하면 헤더 구성이 완료된다.
static 을 활용한 identifier 설정
오늘 시간을 많이 날린 실수가 있어서 정리하고 넘어가려고 한다. 오늘은 각 컬렉션 뷰 셀을 연결할때 각각 셀 파일 내 스테틱을 활용해 identifier를 설정해 두었었다.
하지만 이전에도 그냥 “” 을 사용해 identifier를 넣어주었었는데 여기에 오타가 있어서 하루 종일 앱이 빌드되지 않고 오류가 있었다..
혹시나 하는 마음에 그냥 스트링으로 넣어두었던 identifier를
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecentlyViewedCollectionViewCell.identifier, for: indexPath) as? RecentlyViewedCollectionViewCell else { fatalError("에러입니다.") }
이런식으로 RecentlyViewedCollectionViewCell.identifier 호출하는 형식으로 사용했더니 앱이 정상적으로 잘 빌드될 수 있었다.
이러한 실수를 막기위해 앞으로 선언을 해서 안전하게 불러오는 방식을 사용해야겠다고 생각했다 🙂
'◽️ Programming > T I L' 카테고리의 다른 글
책 검색 앱 만들기 (4) (3) | 2024.05.07 |
---|---|
책 검색 앱 만들기 (3) (0) | 2024.05.03 |
책 검색 앱 만들기 (1) (3) | 2024.04.30 |
[ProJect 일지] 영화 예매 앱 만들기 (5) (2) | 2024.04.27 |
[ProJect 일지] 영화 예매 앱 만들기 (4) (0) | 2024.04.25 |