컬렉션 뷰는 스크롤로 기본 옵션이 설정 되어 있다. 이것의 수직 수평을 바꾸는 방법과, 페이지를 넘기는 애니메이션을 주는 방법을 알아 보겠습니다.
위 사진에서 Scroll Direction
을 Horizontal
로 변경을 하면, 수평으로 스크롤 하는 효과를 낼 수 있고, 조금 밑에 Scrolling
섹션에서, Paging Enabled
를 체크하면 페이지를 넘기는 애니메이션을 줄 수 있다.
추가로 Indicators
섹션에 들어가서 Show ~~~ Indicator
의 체크를 해제하면, 넘어갈 때마다 보이는 스크롤바를 표시 하지 않는다.
/// 얼마나 스크롤이 되는지 알 수 있는 코드
/// CollectionView 는 ScrollView를 상속받고 있다.
extension OnboardingViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
}
}
이를 활용해서 페이지가 넘어간지 아닌지를 확인이 가능하다.
scrollView.contentOffset.x / self.collectionView.bounds.width
// 위 코드로 기기 디바이스의 width를 알 수 있다. 하나가 넘어가면 1, 두개는 2 이런식으로 나옴
/// 스크롤이 감속하면서 멈춤을 감지하는 함수
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index: Int = Int(scrollView.contentOffset.x / self.collectionView.bounds.width)
pageControl.currentPage = index
}
기존의 DataSource로 구현하는 방식은 점점 복잡한 구현이 생기면서, 이슈가 생기는 경우가 있었다. Controller, UI
가 들고 있는 데이터가 일치하지 않아 앱에서는 어느것이 맞는 데이터인지 알기가 어렵다.
따라서 SSOT
의 필요성이 증가하였다. 그래서 제안된 방식이 DiffableDatasource
이다
현재 UI State의 truth
를 가진 구조체. Indexpath를 쓰지 않고, Identifier로 구분한다. 섹션 및 아이템에 대해서 Unique ID를 사용한다. (Unique + Hashable, 따라서 Hashable을 준수하는 타입만 사용이 가능)
예시를 통해 살펴보자.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
// 기존의 [section [item]] 하나가 아닌 [section [item]][section [item]] 여러개 사용가능
// Item 으로 섹션과 아이템을 사용할 것을 명시 가능 가독성이 좋아짐
// Section과 Item은 있다는 것을 명시해줬음
// 먼저 섹션과 아이템을 선언해준다.
var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
// 일관성을 주기 위해 typealias를 사용 하였다.
typealias Item = AppleFramework
// 연관 값이 없는 enum은 자동으로 Hashable
// 만약 섹션이 더 많아지면 밑의 case를 가독성에 맞는 이름으로 추가 설정해주면 된다.
enum Section {
case main
}
// Presentation 부분
dataSource = UICollectionViewDiffableDataSource<Section,Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
// diffabledatasource 에서는 item이 실제 데이터이다 따라서
// let data = datalist[indexPath.item] 을 명시하지 않아도 된다.
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FrameworkCell", for: indexPath) as? FrameworkCell else {
return nil
}
cell.configure(item)
return cell
})
// Data 부분
// 섹션과 아이템을 snapshot에 넣어주고
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(list, toSection: .main)
// apply를 사용하여 새로운 Snapshot을 기존것으로 대체한다. 이 때 Animation은 System이 자동으로 적용한다.
dataSource.apply(snapshot)
// 밑에 나올 CompositionalLayout 부분
collectionView.collectionViewLayout = layout()
밑 부분에서 cellProvider
는 클로저이고, 기존의 셀 구성하는 코드를 작성하면 된다. 'apply를 사용하여 새로운 Snapshot을 기존것으로 대체한다. 이 때 Animation은 System이 자동으로 적용한다.' 부분의 동작은 다음 사진과 같다
참고) 기존의 프로토콜이던 datasource와는 다르게, diffableDatasource는 클래스라 선언을 해주어야 한다. 그리고 apply() 호출은 background queue에서 safe하다고 함
추가로 indexPath
로 부터 identifier
를 가져오는 API를 제공해주고, 해당 API의 시간복잡도는 상수이다.
// What About IndexPath-based APIs?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let identifier = dataSource.itemIdentifier(for: indexPath) {
// Do something
}
}
기존의 UICollectionViewFlowLayout
은 단순 디자인에서는 좋은 역할을 하였지만, 점점 복잡한 디자인이 되었을 경우에는 CustomLayout
을 매번 구현 해주어야만한다. 그래서 나온 개념이 CompositionalLayout
이다.
위 사진과 같이 섹션안에서 그룹과, 아이템으로 각각 나눌수 있어서 동적이고, 커스텀으로 레이아웃을 처리가 가능하다.
코드로 구성을 살펴보면,
// diffableDatasource 코드부분에서 마지막 부분, 따로 함수를 호출하였다 (내용이 길기 때문)
collectionView.collectionViewLayout = layout()
private func layout() -> UICollectionViewCompositionalLayout {
// fractionalWidth 0.33은 그룹 너비의 1/3 이라는 의미, 1은 그룹 높이를 그대로 쓴다는 의미
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// 여기서의 fractionalWidth는 section의 너비를 그대로 쓰고, 높이는 섹션 너비의 1/3 해서 쓰겠다는 의미
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.33))
// count는 그룹당 항목의 수를 명시한다.
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)
let section = NSCollectionLayoutSection(group: group)
// padding 값을 주는 부분, item, group에서도 가능
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
위 코드처럼 사용을 하고, 순차적으로 살펴보면 레이아웃은 섹션을 필요로하고, 섹션은 그룹을 필요로 하고, 그룹은 아이템과 그룹의 사이즈를 필요로하고, 아이템은 아이템의 사이즈를 필요로한다.
참조