[TIL]07.01

rbw·2022년 7월 2일
0

TIL

목록 보기
30/98

컬렉션 뷰에서 스크롤 효과들

컬렉션 뷰는 스크롤로 기본 옵션이 설정 되어 있다. 이것의 수직 수평을 바꾸는 방법과, 페이지를 넘기는 애니메이션을 주는 방법을 알아 보겠습니다.

위 사진에서 Scroll DirectionHorizontal로 변경을 하면, 수평으로 스크롤 하는 효과를 낼 수 있고, 조금 밑에 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
}

DiffableDataSource 등장

기존의 DataSource로 구현하는 방식은 점점 복잡한 구현이 생기면서, 이슈가 생기는 경우가 있었다. Controller, UI가 들고 있는 데이터가 일치하지 않아 앱에서는 어느것이 맞는 데이터인지 알기가 어렵다.

따라서 SSOT의 필요성이 증가하였다. 그래서 제안된 방식이 DiffableDatasource이다

Snapshot의 도입

현재 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
    }
}

CompositionalLayout의 등장

기존의 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
    }

위 코드처럼 사용을 하고, 순차적으로 살펴보면 레이아웃은 섹션을 필요로하고, 섹션은 그룹을 필요로 하고, 그룹은 아이템과 그룹의 사이즈를 필요로하고, 아이템은 아이템의 사이즈를 필요로한다.


참조

https://inuplace.tistory.com/929

https://velog.io/@wansook0316/Diffable-DataSource

profile
hi there 👋

0개의 댓글