본문 바로가기
iOS

[iOS] Diffable Datasource❓

by yangsubinn 2022. 3. 18.

Diffable Datasource가 뭐냐?


공식문서 해석

📄 UICollectionViewDiffableDataSource

데이터를 관리하고 collectionView에 cell을 제공하기 위해 사용되는 객체

@MainActor class UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable

collection view의 데이터와 UI 업데이트 관리를 더 간단하고 효율적인 방법으로 제공합니다.

또한 기존의 UICollectionViewDataSource의 프로토콜을 따르고 있기 때문에

해당 프로토콜의 메소드를 전부 실행할 수 있습니다.

 

collection view를 데이터로 채우는 방법
1. collection view와 diffable data source를 연결
2. collection view의 cell을 구성하기 위한 cell provider 실행
3. 현상황의 데이터 생성
4. UI에 데이터를 보여주기

 

collection view와 diffable data source를 연결하기 위해서는

init(collectionView: cellProvider:) initializer을 통해 diffable data source를 생성해줘야 합니다.

그리구 각 셀을 구성하여 UI에 데이터를 표시하는 방법을 결정하는 cell provider을 전달합니다.

dataSource = UICollectionViewDiffableDataSource<Int, UUID>(collectionView: collectionView) {
    (collectionView: UICollectionView, indexPath: IndexPath, itemIdentifier: UUID) -> UICollectionViewCell? in
    // Configure and return cell.
}

그리고 현재 상태의 데이터를 생성하고 snapshot을 구성하고 적용하여 데이터를 UI에 보여줍니다.

NSDiffableDataSourceSnapshot

 

⚡ collection view를 구성한 뒤 diffable data source를 변경하면 안됩니다.. 만약 초기 구성한 뒤, collection view가 새로운 data source가 필요하다면 새로운 collection view와 diffable data fource를 생성해주세요

 

❓기존 Datasource는 어쩌고, , ,  왜 만든거지?

서비스 내에서 컬렉션뷰 새로고침 버튼을 생각해보면..

refresh 버튼을 누르면 서버 통신을 한 뒤, collectionView를 업데이트 해줍니다.

이 때 우리는 새로 받아온 데이터를 UI에 적용해주고 마지막에 꼭 reloadData()를 해줍니다!

이 친구를 안해주면 새로 데이터를 아무리 받아왔어도 적용되지 않기 때문이져!

 

근데 이 reloadData()를 써주면 애니메이션 효과가 나타나지 않습니다

그동안 봐왔던 것처럼 그냥 띡- 데이터가 변경되는 것을 보실 수 있습니다!

 

💡 Diffable DataSource는 자연스러운 애니메이션을 통해 사용자에게 기존보다 좋은 UX를 제공합니다

 

reloadData() 사용 X

 추가된 데이터가 갱신되지 않습니다

 

 

reloadData() 사용 O

→ 추가된 데이터 갱신!

 

 


 

 

Diffable Datasource 사용법


예시 1️⃣

위의 간단한 collection view에 적용해보겠슴니다

아래의 기존 코드를 주석 처리 해주고 새로운 diffableDataSource를 채택해주려 합니다!

**// 1️⃣**
// collectionView.dataSource = self

// 2️⃣
// MARK: - UICollectionViewDataSource
//extension SideCollectionVC: UICollectionViewDataSource {
//    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//        return colors.count
//    }
//}

**// 3️⃣ cell도 diffableDataSource를 통해 채택하기 때문에 주석처리했습니다**
//    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SideCVC.identifier, for: indexPath) as? SideCVC else { return UICollectionViewCell() }
//
//        cell.backgroundColor = colors[indexPath.item]
//        cell.layer.cornerRadius = 26
//
//        return cell
//    }

// 4️⃣
// collectionView.reloadData()

 

일단 생성할 diffableDataSource과 Seciton을 선언해줬슴니다

그리구 전 section에 UIColor을 아이템으로 넣을거라 ItemIdentifierType 에 UIColor를 넣어줬습니다

enum Section: CaseIterable {
		case main
}

private var diffableDatasource: UICollectionViewDiffableDataSource<Section, UIColor>!

 

cell을 선언하고 구성한 뒤, 구성한 diffableDatasource를 채택해줬습니다.

self.diffableDatasource = UICollectionViewDiffableDataSource<Section, UIColor> (collectionView: self.collectionView) {(UICollectionView, IndexPath, String) -> UICollectionViewCell? in
    guard let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: SideCVC.identifier, for: IndexPath) as? SideCVC else { return UICollectionViewCell() }
    
    cell.backgroundColor = self.colors[IndexPath.item]
    cell.layer.cornerRadius = 26
		
		return cell
}
collectionView.dataSource = diffableDatasource

 

그리구 collectionView에 데이터를 넣어주는 setData() 함수를 구성했습니당

변경될 collectionView의 상태를 snapshot 찍은 뒤, 그 snapshot을 diffableDatasource에 apply 해주면 데이터가 추가되고, 뷰에 나타납니다.

기존과 비교해보면 데이터를 넣어주고 reload해주는 과정이 snapshot을 apply하는 과정과 같은 것 같네여..!

// DiffableDataSourceSnapshot
private func setData(arr: [UIColor]) {
    var snapshot = NSDiffableDataSourceSnapshot<Section, UIColor>()
    snapshot.appendSections([.main])
    snapshot.appendItems(arr)
    self.diffableDatasource.apply(snapshot, animatingDifferences: true)
}

 

diffableDataSource의 자연스러운 애니메이션을 한껏 느껴보기 위해

기존 컬렉션뷰에는 아무런 데이터도 넣어두지 않은 상태에서 시작하여, 버튼을 눌렀을때 데이터가 추가되면서 나타나도록 했습니다!

tryCount는 첫번째 append와 두번째 append를 구분하기 위해 사용한 프로퍼티입니다

@objc
func touchAppendButton() {
		if tryCount == 1 {
		    colors.append(contentsOf: newColors)
    }
    setData(arr: colors)
    tryCount += 1
}

 

 

결과물은 이렇습니다!

기존의 data source를 사용해서 reload하던 것과 달리 자연스럽게 스르륵 들어오는 것을 확인할 수 있슴다

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

예시 2️⃣

새로고침 버튼을 누르면 함께하는 스파커들 아래의 컬렉션뷰에 들어가는 데이터 서버 통신이 이루어지고, 갱신되는 부분입니다.

기존의 dataSource로 구성되어있던 부분을 diffableDataSource로 변경했습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

먼저 컬렉션뷰를 관리하는 VC에 diffable datasource에서 사용할 section enum을 선언해줬습니다.

// DiffableDataSource에서 사용할 section enum 선언
enum Section: CaseIterable {
    case main
}

 

함께하는 스파커들의 데이터 모델인 Member를 컬렉션뷰에 적용할 것이기 때문에 ItemIdentifierType에 Member이라는 데이터 모델을 넣어줬습니다.

private var diffableDataSource: UICollectionViewDiffableDataSource<Section, Member>!

이때 ItemIdentifierType는 hashable한 형태이어야 하기 때문에 타입이 hashable하지 않다는 에러 메세지가 뜨게 됩니다!

그래서 Member이라는 데이터 모델을 hashabel한 형태로 변형하는 작업이 필요합니다.

// Hashable 채택
struct Member: Codable, Hashable {
		...
		
		/// struct를 hashable로 변환.
    /// hashValue가 고유값인지 식별해주는 함수 (Equatable protocol)
    static func == (lhs: Member, rhs: Member) -> Bool {
        lhs.userID == rhs.userID
    }

    /// combine 메소드를 통해 식별할 수 있는 identifier 값 전달
    /// hasher이 hashValue를 생성해줌.
    func hash(into hasher: inout Hasher) {
        hasher.combine(userID)
    }
}

 

그리구 다시 VC로 돌아와서 diffableDataSource를 생성하고 컬렉션뷰에 채택해줍니다!

private func setCollectionView() {
        // diffableDatasource에서 collectionView, cell 채택
        self.diffableDataSource = UICollectionViewDiffableDataSource<Section, Member>(collectionView: self.collectionView) {(_ UICollectionView, IndexPath, _ Member) -> UICollectionViewCell? in
            guard let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: Const.Cell.Identifier.waitingFriendCVC, for: IndexPath) as? WaitingFriendCVC else { return UICollectionViewCell() }

            let name = self.members[IndexPath.item].nickname
            let imagePath = self.members[IndexPath.item].profileImg ?? ""
            cell.initCell(name: name, imagePath: imagePath)

            return cell
        }
        collectionView.dataSource = diffableDataSource
				...
}

 

아래처럼

기존의 reloadData() 대신 snapshot을 찍어서 적용해주도록 setMemberData() 함수를 변경한 뒤,

서버통신 후 해당 함수를 실행시키도록 하면

추가된 멤버가 있는 경우 해당 멤버의 정보가 스르륵 들어오는 것을 볼 수 있습니다.

		/// 대기방 멤버 갱신하는 함수
    private func setMemberData() {
				// 기존의 dataSource에서 reloadData로 갱신하던 방법
//        collectionView.reloadData()

        // 새로운 상태의 snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Member>()
        snapshot.appendSections([.main])
        snapshot.appendItems(members)

        // 변경된 데이터 적용
        self.diffableDataSource.apply(snapshot, animatingDifferences: true)
    }

 

 

 


 

참고자료

[iOS - swift] 2. Diffable Data Source - UICollectionViewDiffableDataSource, UICollectionViewCompositionalLayout (컬렉션 뷰)

Apple Developer Documentation

[WWDC20] Advances in diffable data sources

Diffable Datasource