본문 바로가기

WWDC

TableView에 animation 추가하기

 

 

 

 

 

tableView에 모델을 바인딩하면서 발생한 문제점을 발견했다.

모델이 변경되었을 때 테이블뷰에 대한 애니메이션 처리를 핸들링할 수 없었고

tableView 전체를 리로드 했다.

 

 

검색을 통해서 performbatchupdates()로 구현할 수 있다는 걸 알았지만,

혹시나 하는 마음에 WWDC에 tableView를 검색했다.

2019년 영상에서 tableView, collectionView의 애니메이션을 쉽게 구현할 수 있는

새로운 친구를 확인했다.

 

 

오늘 소개할 친구는 UITableVIewDiffableDataSource다.

 

 

 

 

 

 

 

기존에 사용했던 DataSource는 Controller에서 데이터를 변경하고 UI에게 변경했다는 걸 알렸다.

잘못 업데이트할 경우에 에러가 발생한다. 특히 IndexPath를 잘못 사용했을 때 빈번하게 앱 크러쉬가 발생한다.

 

 

 

DiffableDataSource는 performBatchUpdates()를 사용하지 않기 때문에 복잡하지 않고,

apply()를 사용해서 구현하면 된다.

 

 

 

snapshot은 현재 UI의 상태를 나타낸다.

(더 이상의 IndexPaths는 없다고 했지만, 배열 i번째 모델이 바뀔 경우에는 indexPath를 사용해야 할 거 같다.)

 

 

여기까지가 전반적인 UITableViewDiffableDataSource를 사용하는데 필요한 개념이다.

실제로 어떻게 구현하는지 살펴보겠다.

 


 

UITableViewDiffableDataSource은 2가지 제네릭 타입을 갖고 있다.

Section은 TableView의 SectionSectionIdentifierType을 말하며 ItemIdentifierType은 셀 하나에 매핑될 모델을 뜻한다.

두 개의 제네릭 모두다 Hashable 프로토콜을 채택하고 있는데 그 이유는 뒤에서 나올 스냅샷을 생성하고 비교할 때

모델이 변경되었는지 파악하기 위해서이다.

class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject 
where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable

 

 

초기화 시킬 때 tableView를 넣어주고, 후행 클로저에서는

func tableView(_ tableView:, cellForRowAt:) 에서 구현해줬던 부분을 구현한다.

lazy var viewModel: UITableViewDiffableDataSource<Section, Item> = .init(tableView: tableView) { tableView, indexPAth, item in
    // configure cell.. 
    return cell
}

 

 

model이 변경될 때마다 아래 메소드를 호출하면

이전 snapshot과 변경된 snapshot을 비교해서 tableView 애니메이션을 처리해준다.

func applySnapshot(_ animatingDifferences: Bool = true) {
	var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
	snapshot.appendSections(section)
	snapshot.appendItems(item)
	viewModel.apply(snapshot, animatingDifferences: animatingDifferences)
}

 

 

마지막으로 제네릭이 Hashable을 채택한 이유는 snapshot을 비교할 때 hashValue를 사용하는데

어떤 값들을 조합해서 hash할건지 프로그래머가 지정해줄 수 있다.

struct Developer: Hashable {
    var id: UUID = .init()
    var name: String
    var age: Int
    
     func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(name)
        hasher.combine(age)
    }
}

 

 

가장 중요한 점!!! 

applySnapshot()은 background queue에서 호출하는 게 좋다. (모델이 많아질 경우를 대비해서)

API가 똑똑하기 때문에 UI를 변경하는 로직은 main queue에서 동작하도록 구현이 되어있다.

사용할 때 main 과 background 둘 다 상관없지만 한 곳에서만 호출하라고 언급했다.

 

 

 

 

 

기존에 사용했던 UITableViewDataSource 메소드를 사용하기 위해서는 UITableViewDiffableDataSource를 상속받는 클래스를 만들고 

오버라이드 하는 방식으로 사용할 수 있다.

 

 

IndexPath를 사용하지 않고

viewModel.itemIdentifier(for: indexPath)를 사용하면

해당 열에 할당된 모델을 사용할 수 있다.

 

 

performBatchUpdates를 사용하지 않고도

apply()만 호출하면 테이블 뷰와 관련된 애니메이션을 처리해준다.

iOS 13 부터 적용가능하기 떄문에 이전 버전에서는 사용할 수 없다.

Github: github.com/GangWoon/DiffableDataSource