이번엔 Compositional Layout을 이용하여 dummy 화면을 구성할 것이다.
지난번에 Cannot find type 'NSCollecitonLayoutSection' in scope 에러가 있는 상태에서 끝났었다.
Layout
일단 section을 선언해 주고 group을 받자. 다시 group을 위에서 선언한다. 일반적인 Group을 만들 때와는 달리 지금은 vertical을 만들 것이다. 서브아이템으로 [item]을 받게 하고 사이즈는 일단 비워두자
private func createSection(for sectionIndex: Int) -> NSCollecitonLayoutSection {
let group = NSCollectionLayoutGroup.vertical(
layoutSize: ,
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
}
item을 보면 재밌는 사실이 있는데 NScollectionLayoutSize에서 필요한 NSCollectionLayoutDimension은 Enum이다. 내부에는 네 가지 case가 존재한다.
- absolute - 지정된 수로 고정
- fractionalWidth - 너비의 길이를 기준으로 %로 지정
- estimated - 시스템이 Content의 사이즈를 고려하여 스스로 크기를 조정한다.
- fractionalHeight - 높이의 길이를 기준으로 %로 지정
우리는 item에서는 100% 다 차게, 그룹에서는 너비는 꽉차고 높이는 절댓값 100으로 준다. 어떻게 해야겠나?
아래와 같이 하면 된다.
private func createSection(for sectionIndex: Int) -> NSCollectionLayoutSection {
let item = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)))
let group = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(100)),
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}
이제 CharacterDetailViewController에서 collectionViewDelegate와 DataSource를 받자.
- 스냅샷을 이용하는 방법도 있다.
일단은 Dummy로 받아 사용한다.
extension RMCharacterDetailViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .systemPink
return cell
}
}
Test
지금 거대한 핑크색 덩어리가 있는 걸 볼 수 있다. 사실 이건 20개의 item이 있다. 다만 간격이 없어서 이렇게 보일 뿐이다.
inset을 주자. 결과물을 보면 딱 20개다.
private func createSection(for sectionIndex: Int) -> NSCollectionLayoutSection {
...
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)
...
}
기본적으로 컴포지셔널 레이아웃은 자유롭게 형상을 만들 수 있기 때문에 테이블뷰처럼 만들 수 있다는 장점이 존재한다.
- 그럼 그냥 테이블 뷰 하면 되는거지 왜 굳이 새로 만들까? 사실 테이블뷰처럼 보이는 거지 테이블뷰가 아니기에 더 자유로운 UI 배치가 쉽게 가능하다.
이제 세가지 섹션을 만들어보자. 아래와 같이 sections를 만들어 사용할 수 있도록 public으로 선언한다.
final class RMCharacterDetailViewViewModel {
...
enum SectionType: CaseIterable {
case photo
case information
case episodes
}
public let sections = SectionType.allCases
...
}
DetailView에서 저 타입을 가져올 것이다. 그런데 현재 VM에 있는 정보를 V에서 사용 못 한다. 아직 VM 객체를 만들지 않았으니까
VM 객체를 만들고 configure 함수를 만들자. 해당 구조는 모든 Cell View에서도 따르는 양식이 될 것이다.
final class RMCharacterDetailView: UIView {
...
private var viewModel: RMCharacterDetailViewViewModel?
...
public func configure(with viewModel: RMCharacterDetailViewViewModel) {
self.viewModel = viewModel
}
}
실수!
createSection에서 뷰모델을 사용하기 전에 생각해 보자. 우리가 viewModel이 지금 있는지 알 수 있나?
모른다. 시점에 대한 문제가 존재하기 때문에 위에서는 저렇게 만들었지만 사실 init에서 생성을 시켜주는 작업이 필요하다.
- 다시 수정한 코드만 적으려고 하다가. 시행착오를 보면서 잘못된 방향성을 다시 생각해 보라고 다 적는다.
final class RMCharacterDetailView: UIView {
...
private let viewModel: RMCharacterDetailViewViewModel
init(frame: CGRect, viewModel: RMCharacterDetailViewViewModel) {
self.viewModel = viewModel
...
}
}
이제 View를 수정했으니 이를 바탕으로 VC을 잡아보자. detailView를 인스턴스를 만들어 사용한다.
final class RMCharacterDetailViewController: UIViewController {
private let viewModel: RMCharacterDetailViewViewModel
private let detailView: RMCharacterDetailView
init(viewModel: RMCharacterDetailViewViewModel) {
self.viewModel = viewModel
self.detailView = RMCharacterDetailView(frame: .zero, viewModel: viewModel)
...
}
...
}
이제 원래 하려던 setionTypes를 이용가능하게 됐다.
내부에 switch를 이용하여 해당하는 타입에 맞는 섹션을 표시해 주자. 여기서 일단 View에서 많은 작업들이 이뤄지고 있고 분명 View에 연관된 작업은 맞지만 너무 많은 로직들이 View에서 이뤄지고 있다. 나중에 VM으로 이동할 것이다.
각 case에서는 분리된 레이아웃 구성 메서드들을 리턴하는 구조를 가지게 된다. 일단 복사 붙여 넣기로 잔뜩 만들어두고 나중에 활용하면 되겠다.
final class RMCharacterDetailView: UIView {
...
private func createSection(for sectionIndex: Int) -> NSCollectionLayoutSection {
let sectionTypes = viewModel.sections
switch sectionTypes[sectionIndex] {
case .photo:
return createPhotoSectionLayout()
case .episodes:
return createEpisodeSectionLayout()
case .information:
return createInfoSectionLayout()
}
}
private func createPhotoSectionLayout() -> NSCollectionLayoutSection {
let item = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)))
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)
let group = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(150)),
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}
private func createInfoSectionLayout() -> NSCollectionLayoutSection {
let item = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)))
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)
let group = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(150)),
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}
private func createEpisodeSectionLayout() -> NSCollectionLayoutSection {
let item = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)))
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)
let group = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(150)),
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}
}
실행시켜 보면 별 차이가 없다. 이는 컬렉션 뷰에서 섹션 수를 지정해주지 않아서 생기는 문제다.
numberOfSections 메서드로 섹션 수만큼 화면에 나오게 만들고 셀 수는 10개로 하자 (보기 편하게)
그리고 각 섹션마다 색상을 달리하여 표시시키자
extension RMCharacterDetailViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return viewModel.sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if indexPath.section == 0 {
cell.backgroundColor = .systemPink
} else if indexPath.section == 1 {
cell.backgroundColor = .systemBlue
} else {
cell.backgroundColor = .systemGreen
}
return cell
}
}
Test
핑크는 아쉽게도 못 보여주지만 아래 존재한다. 이번에는 dummy 데이터로 대강 흐름을 잡았으니 다음에는 실제로 레이아웃을 구현해 보자
끝
'Swift > Rick & Morty' 카테고리의 다른 글
[iOS] Rick&Morty - #16 Character Detail ViewModels (0) | 2023.03.14 |
---|---|
[iOS] Rick&Morty - #15 CollectionView Layouts (0) | 2023.03.13 |
[iOS] Rick&Morty - #13 Character Detail View(2) (0) | 2023.03.11 |
[iOS] Rick&Morty - #12 Image Loader (0) | 2023.03.09 |
[iOS] Rick&Morty - #11 Pagination (0) | 2023.03.08 |