최근 UITableView를 학습하면서, 테이블 뷰 셀의 데이터들을 UISearchBar를 이용해서 검색할 때 reload() 메소드를 이용해 검색 조건에 맞는 데이터만 보여주는 식으로 처리했다. 이는 데이터가 많아질 수록 성능 저하로 나타날 수 있을뿐만 아니라, 사용자의 UI 경험에도 좋은 영향을 주지 못한다.
그 동안 UITableViewDataSource
공식문서를 볼 때마다 항상 지나쳤던 UITableViewDiffableDataSoruces가 눈에 띄었고 부랴부랴 WWDC 2019
에서 발표된 영상을 찾아보았고, 곧바로 매료되었다.
특히 기본적으로 제공해주는 애니메이션이, 검색 기능을 사용할때 매우 유용할 것 같다.
UITableViewDiffableDataSources는 snapshot을 의미한다.
즉, 현재의 데이터들의 상태를 기억해둔다는 뜻이다.
그림을 통해 살펴보자.
스냅샷은 크게 3가지를 위해 도입되었다.
UITableViewDiffableDataSources
는 몇가지 프로토콜을 준수하는 자료형에만 이를 사용할 수 있도록 하였다.생각보다 쉽다 코드를 보자.
UITableViewDataSources와 다르게 프로토콜이 아닌 클래스이다. 하나 하나 살펴보자
enum Section: CaseIterable{
case main
}
Swift의 원시 자료형(Int, String.. 등등)은 모두 Hashable 프로토콜을 따르기때문에 일단 넣으면 된다!
또한 열거형의 경우 원시값이 Hashable 프로토콜을 따르는 경우 열거형 자체도 Hashable 프로토콜을 따르기때문에 상관없다!
var stringList: [String] = {
var str = [String]()
for x in 0...8{
str.append("\(x)번째 셀 입니다.")
}
return str
}()
String타입의 경우 Hashable 프로토콜을 따르므로 따로할 작업은 없다. 만약, 직접 데이터의 자료형을 구현해야한다면, Hashable 프로토콜을 따르도록 구현해야한다.
dataSource = UITableViewDiffableDataSource<Section, String>(tableView: tableView){
(tableView, indexPath, itemIdentifier) in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = self.stringList[indexPath.row]
cell.textLabel?.font = .systemFont(ofSize: 20)
return cell
}
테이블 뷰의 dataSource에 DiffableDataSource 인스턴스를 넣는다. 최초의 테이블 뷰 로딩 및, UI 업데이트가 이루어진 후에 어떻게 테이블 뷰 셀이 구성되는지를 정의한다고 생각하면된다. 이렇게 구성된 셀의 모습으로 테이블 뷰 내의 셀들이 재배치 되는식이다.
스냅샷 인스턴스를 생성하기위한 구조체다. 제네릭 타입의 조건은 UITableViewDiffableDataSources와 완전히 동일하므로 그대로 사용하면 된다. 이번 포스트에서는 appendItem 메소드와, apply 메소드만 알아보겠다.
@IBAction func change(_ sender: Any) {
// 빈 스냅샷 인스턴스 생성
var snapShot = NSDiffableDataSourceSnapshot<Section, String>()
// 스냅샷에 들어갈 테이뷰 뷰 섹션 추가.
snapShot.appendSections([.main])
var temp = [String]()
temp = stringList.shuffled()
// 스냅샷에 들어갈 셀 배열 추가.
snapShot.appendItems(temp)
// 스냅샷을 적용. UI 업데이트 시작
dataSource.apply(snapShot, animatingDifferences: true)
}
빈 스냅샷을 생성하면, 현재의 테이블 뷰의 모습이 찍히는 것이 아니다. 정말 테이블 뷰에 섹션과 셀이 없는 상태의 스냅샷 인스턴스가 생성된다. 이후, 내가 넣고 싶은 섹션과 셀을 위의 메소드에 인자로 전달한다.
이후, apply() 메소드를 호출하면 UI 업데이트가 진행된다.
import UIKit
class ListViewController: UITableViewController {
// 셀에 담길 문자열
var stringList: [String] = {
var str = [String]()
for x in 0...8{
str.append("\(x)번째 셀 입니다.")
}
return str
}()
// 섹션을 열거형으로 정의
enum Section: CaseIterable{
case main
}
var dataSource: UITableViewDiffableDataSource<Section, String>!
override func viewDidLoad() {
super.viewDidLoad()
dataSource = UITableViewDiffableDataSource<Section, String>(tableView: tableView)
{(tableView, indexPath, itemIdentifier) in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = self.stringList[indexPath.row]
cell.textLabel?.font = .systemFont(ofSize: 20)
return cell
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.stringList.count
}
@IBAction func change(_ sender: Any) {
// 빈 스냅샷 인스턴스 생성
var snapShot = NSDiffableDataSourceSnapshot<Section, String>()
// 스냅샷에 들어갈 테이뷰 뷰 섹션 추가.
snapShot.appendSections([.main])
var temp = [String]()
temp = stringList.shuffled()
// 스냅샷에 들어갈 셀 배열 추가.
snapShot.appendItems(temp)
// 스냅샷을 적용. UI 업데이트 시작
dataSource.apply(snapShot, animatingDifferences: true)
}
}