WWDC 19) Advances in UI Data Sources
Advances in UI Data Sources - WWDC19 - Videos - Apple Developer
Use UI Data Sources to simplify updating your table view and collection view items using automatic diffing. High fidelity, quality...
developer.apple.com
๐ก
- UI Diffable DataSource๋ฅผ ์ฌ์ฉํ๋ฉด automatic diffing(์๋ ๋น๊ต)๋ฅผ ์ฌ์ฉํ์ฌ CollectionView, TableView ์ ๋ฐ์ดํธ(reload)๋ฅผ ๊ฐ์ํํ ์ ์์ต๋๋ค.
- ์ธํธ(์ ๋ฐ์ดํฐ) ๋ณ๊ฒฝ์ ๋ํ ๊ณ ํ์ง ์ ๋๋ฉ์ด์ ์ด ์๋์ผ๋ก ์ ๊ณต๋๋ฉฐ ์ถ๊ฐ ์ฝ๋๊ฐ ํ์ํ์ง ์์ต๋๋ค!
- ์ด ๊ฐ์ ๋ ๋ฉ์ปค๋์ฆ์ ๋๊ธฐํ ๋ฒ๊ทธ, ์์ธ ๋ฐ ์ถฉ๋์ ์์ ํ ๋ฐฉ์งํฉ๋๋ค!
- Identifier ๋ฐ SnapShot์ ์ฌ์ฉํ๋ ์ด ๊ฐ์ํ๋ Diffable DataSource๋ฅผ ํตํด ๊ฐ๋ฐ์๋ UI ๋ฐ์ดํฐ ๋๊ธฐํ์ ๊ดํ ์ธ๋ฐํ ๋ถ๋ถ ๋์ ์ฑ์ ๋์ ๋ฐ์ดํฐ์ ์ฝํ ์ธ ๋ถ๋ถ์ ์ง์คํ ์ ์์ต๋๋ค.
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์
- UI์ Controller ์ฌ์ด ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ์ ๋ทฐ๊ฐ์ ์๊ฐ ์ฐจ์ด ๋ฐ์ ์ ์ด์ ๋ํ ์ฒ๋ฆฌ์ ์ด๋ ค์


๐ก ๋ทฐ ์ด๊ธฐํ์ UI(ํ ์ด๋ธ ๋ทฐ, ์ฝ๋ ์ ๋ทฐ) ์์ญ์ด ์ ์์ดํ ์ ๊ฐ์๋ฅผ ๋ฌป์ง๋ง ์ปจํธ๋กค๋ฌ๋ ์น์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ์ค…
- ๋์ค์ UI์ ๋ฐ์ดํฐ ๊ฐ์ด ๋ฐ๋์ ์๋ ค์ค์ผํจ
- ์ค์๊ฐ ์ ๋ฐ์ดํธ ์ฌ์ด์ ์ ๊ณผ ์น์ ์ View์์์ ์ญ์ , ์์ฑ์ ๋์ ์ธ ๋ณํ ๋์ ์ด๋ ค์
์ค์๊ฐ ์์ดํ์ด ํ์ํ๋ ํ ์ด๋ธ ๋ทฐ๋ฅผ ๊ธฐ์กด์ ๋ฐฉ์์ผ๋ก ๋ณด์ฌ์ฃผ๊ธฐ๋ ๋๋ฌด ๋ณต์กํด์ง๋ค..!


DiffableDataSource
UI State(ํ ์ด๋ธ ๋ทฐ, ์ฝ๋ ์ ๋ทฐ ์์ดํ ๋ฐ์ดํฐ)์ ๋ํ ์ ์ธํ(Declarative)์ ์ธ ์ ๊ทผ
Diffable Data Source ์ ํ
- UICollectionViewDiffableDataSource ⇒ ์ฝ๋ ์ ๋ทฐ ๋์
- UITableViewDiffableDataSource ⇒ ํ ์ด๋ธ ๋ทฐ ๋์
- NSCollectionViewDiffableDataSource ⇒ ๋งฅ์ ์กด์ฌํ๋ ๋ฆฌ์คํธ ํ์ ์ ํ
Order of use) ์ฌ์ฉ ๊ท์น ํน์ ์์
- diffable ๋ฐ์ดํฐ ์์ค๋ฅผ ์ปฌ๋ ์
๋ทฐ์ ์ฐ๊ฒฐํ์ธ์.
⇒ diffable datasource ์ธ์คํด์ค ์์ฑํ๊ธฐ - ์
๊ณต๊ธ์์ ๋ํ ๊ตฌํ
⇒ ์ ๋ฑ๋กํ๊ธฐ - ๋ฐ์ดํฐ์ ํ์ฌ ์ํ๋ฅผ ์์ฑ
⇒ ์ด๋ฏธ ์กด์ฌํ๋ ๋ฐ์ดํฐ์์ ๊ฐ์ ธ์ด - UI์ ๋ฐ์ดํฐ๋ฅผ ํ์ํฉ๋๋ค.
⇒ datasource์ ํ์ฌ ๋ฐ์ดํฐ์ ์ํ๋ฅผ ์ ์ฉ์ํด
Snapshots
- ํด๋์ค: NSDiffableDataSourceSnapshot
- ํ์ฌ UI State์ ๋ํ ์๊ฐ์ ์ธ ๊ฐ
- ์น์
๊ณผ ๊ทธ ํ์์ ์์ดํ
๋ค์ Unique identifiers๋ก ๊ตฌ๋ถํจ
⇒ ๊ฐ๊ฐ์ ๋ฐ์ดํฐ๋ Identifiable ํ๋กํ ์ฝ์ ์ง์ผ์ผํ๋ค๋ ๋ป - ๊ฐ๊ฐ์ ์ ๋ฐ์ดํฐ๋ฅผ IndexPaths๋ก ์ง์ ์ ๊ทผํด์ ๋ทฐ์ ํ์ํ ํ์๊ฐ ์์

ํ์ฌ์ ๊ฐ๋ค์ ๊ฐ๊ณ ์๋ DiffableDataSource์ ์กด์ฌํ๋ ์ค๋ ์ท์์ ์๋ก์ด ์ค๋ ์ท์ ์ ์ฉ(apply)ํ๋ ๋ฐฉ์์ผ๋ก ์ ๋ฐ์ดํธ ํ๋ค.
⇒ dataSource์ applyํจ์์ snapshot ์ธ์คํด์ค๋ฅผ ๋ฃ์ผ๋ฉด ๊ทธ ๋ฐ์ดํฐ์ ๋ง๋ ์
๋ทฐ๋ฅผ ๋ณด์ฌ์ค๋ค.
⇒ dataSource.apply(snapshot,animating: false)
Required) ์ฌ์ฉํ๊ธฐ ์ํด ์ค์ํด์ผํ๋ ์ฌํญ
- Section์ผ๋ก ์ฌ์ฉํ ํ์
๊ณผ Item์ผ๋ก ์ฌ์ฉํ ํ์
์ ์ค์ ํ๋ค.
- Hashable ์ค์ ๋ฐฉ๋ฒ ์ฝ๋
struct MyModel: Hashable { let identifier = UUID() func hash(into hasher: inout Hasher) { hasher.combine(identifier) } static func == (lhs: MyModel, rhs: MyModel) -> Bool { return lhs.identifier == rhs.identifier } }- โ์ด๋ฐ ํ์ ์ Hashable ํ๋กํ ์ฝ์ ์ค์ํด์ผํ๋ค.
- ๊ธฐ์กด DataSource ๋ฐ์ธ๋ฉ ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ง ์๋๋ค
self.tableView.dataSource = self← ์ด๊ฑฐ ์๋จ!!
- CollectionView, TableView์ ์ ์ ํ์ํ ์ ์์ดํ ์ ๋ฏธ๋ฆฌ ๋ฑ๋ก์์ผ ๋๋๋ค.
- Snapshot apply๋ main queue, background queue ๋ ๋ค ์ ์ฉํ์ง๋ง, ํ๋์ ๋ฐ์ดํฐ ์์ค์๋ ํ๋์ ํ์์๋ง apply ํ ๊ฒ
๊ฐ๋จํ ํ ์ด๋ธ ๋ทฐ์ ์ ์ฉํ๊ธฐ

๐ก ๋ชจ๋ ํ๋์ ์ธ ๊ธฐ๋ฒ์ ์ ์ฉํ ๊ฒ์ด ์๋ ๋ฐ์ดํฐ ์์ค์ ์ผ๋ถ ๊ธฐ๋ฒ๋ง ์ ์ฉํจ..!
- ํ ์ด๋ธ ๋ทฐ ์ ์ ํ์ ๊ธฐ์กด์ ์ ์ ์กด์ฌํ ๊ธ์ ์ ์ฒด๋ฅผ ๋ณผ ์ ์๊ฒ ๋ง๋ ์ฝ๋
ํ ์ด๋ธ ๋ทฐ ์ ์ฝ๋
import UIKit
import SnapKit
class CustomTableViewCell: UITableViewCell {
let label = UILabel()
let button = UIButton()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configure()
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(){ // ์
์ configure์ ์ฃผ์์ฌํญ
self.contentView.addSubview(label)
self.contentView.addSubview(button)
self.contentView.backgroundColor = .yellow
label.font = .monospacedDigitSystemFont(ofSize: 17, weight: .medium)
let config = UIButton.Configuration.filled()
button.configuration = config
}
func setConstraints(){
button.snp.makeConstraints { make in
make.width.equalTo(80)
make.trailingMargin.equalTo(contentView)
make.top.equalTo(contentView).offset(4)
}
label.snp.makeConstraints { make in
make.verticalEdges.equalTo(contentView).inset(16)
make.top.leadingMargin.equalTo(contentView)
make.trailing.equalTo(button.snp.leading).offset(-8)
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
ํ ์ด๋ธ ๋ทฐ ์ฝ๋
import UIKit
import SnapKit
struct Sample: Identifiable,Hashable{ // ํ
์ด๋ธ ๋ทฐ ์์ดํ
let id = UUID()
let text: String
var isExpand: Bool
}
class CustomTableVC: UIViewController{
let tableView = UITableView()
enum Section{ case main } // ํ
์ด๋ธ ๋ทฐ ์น์
... ์ฌ์ค ํ์ ์์ง๋ง ๋ฐ์ดํฐ ์์ค์ ํ์ํด์ ๊ฐ๋จํ๊ฒ ๋ง๋ฌ
var dataSource: UITableViewDiffableDataSource<Section,Sample>?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
self.view.backgroundColor = .white
tableView.snp.makeConstraints { $0.edges.equalTo(view.safeAreaLayoutGuide) }
configureTableView()
}
}
extension CustomTableVC{
func configureTableView(){
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
tableView.delegate = self // ํ
์ด๋ธ ๋ทฐ์ delegate ์ถ๊ฐํ๊ธฐ
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "customCell") // ํ
์ด๋ธ ๋ทฐ์ ๋ณด์ฌ์ค ์
๋ฑ๋กํ๊ธฐ
/// ํ
์ด๋ธ ๋ทฐ๊ฐ ๋ฐ๋ผ์ผํ ๋ฐ์ดํฐ ์์ค์ ๋ํ ์ ์ธ, ๊ธฐ์กด ๋ฐ์ดํฐ ์์ค์ cellForRowAt๊ณผ ๋น์ทํจ
/// ์ ๋ค๋ฆญ์์ ์น์
์ ํ์
๊ณผ ์์ดํ
์ ํ์
์ ๋ฏธ๋ฆฌ ์๋ ค์ค์ผํจ,
/// ํํ ํด๋ก์ ธ์์ ํ
์ด๋ธ ๋ทฐ, indexPath, ์์์ ์ ์ธํ ์์ดํ
์ ๊ตฌ์กฐ์ฒด์ ๋ํ ์ธ์คํด์ค๋ฅผ ๋ฐํํจ
/// ํน์ ์์์ ์์ดํ
์ ๋ํ ์ ๋ณด๋ฅผ ํด๋ก์ ธ์์ ์๊ณ ์๋ค๋๊ฒ ๊ธฐ์กด์ cellForRowAt๊ณผ ๋ค๋ฆ!!
self.dataSource = UITableViewDiffableDataSource<Section,Sample>(tableView: tableView) { tableView, indexPath, item in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as? CustomTableViewCell else {return .init()}
cell.label.text = item.text
cell.label.numberOfLines = item.isExpand ? 0 : 2
cell.button.setTitle("\(indexPath.row) ํ์ด", for: .normal)
return cell
}
// ์ด๊ธฐ์ ์์ฑํ ์ค๋
์ท -> ์ด ์ค๋
์ท์ ๋ฐ์ดํฐ ์์ค์ ์ ์ฉ์ํด
var snapShot = NSDiffableDataSourceSnapshot<Section, Sample>()
snapShot.appendSections([.main]) // ์น์
์ถ๊ฐํ๊ธฐ
// ์์ ์
์์ดํ
๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ธฐ
let samples:[Sample] = (0...30).map { val in Sample(text: "์ค๋๋ง์ ๊ณต์ง๋ก ๋์์์ต๋๋ค.\n\n๊ฐ์กฑ์ฌํ ์ ๋ค๋
์์ต๋๋ค.\n\n์ ์ง๋ด์
จ๋์? ์์ฒญ์: ๋ค ์ ์ง๋์ต๋๋ค.\n\n๊ทธ๋ ๊ตฐ์. ์์ผ๋ก๋ ์ ์ง๋ด์ญ์์ค.\n\n์ฌ๋ฐ๋ ์๋์ ๋ง์ด ๋ชจ์ญ๋๋ค. ๊ธฐ๋ํด ์ฃผ์ธ์. ์์์ผ๋ ๋ง๋์~", isExpand: false)}
snapShot.appendItems(samples)
self.dataSource?.apply(snapShot)
}
}
extension CustomTableVC:UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/// 1. ๊ธฐ์กด์ ๋ฐ์ดํฐ ์์ค์ ์ ํํ indexPath์ ๋ฐ์ดํฐ(์์ดํ
)๋ฅผ ๊ฐ์ ธ์ด
/// 2. ํ์ฌ ๋ฐ์ดํฐ ์์ค์ ์ค๋
์ท์์ ๋ฐ์ดํฐ(์์ดํ
)๋ค ๋ฐฐ์ด์ ๊ฐ์ ธ์ด (๊ตฌ์กฐ์ฒด ๋ฆฌ์คํธ)
/// 3. ์์ดํ
๋ฐฐ์ด ์ค์ ์ ํํ ์์ดํ
์ ์์๋ฅผ ์์๋
guard let item = dataSource?.itemIdentifier(for: indexPath),
var snapshotItems:[Sample] = dataSource?.snapshot().itemIdentifiers,
let idx = snapshotItems.firstIndex(of: item) else {return}
snapshotItems[idx].isExpand.toggle() // ์ด ์์ดํ
์์ ๋ฐ๋ ๋ฐ์ดํฐ
// ์๋ก์ด ์ค๋
์ท ์์ฑํ๊ธฐ
var snapsot = NSDiffableDataSourceSnapshot<Section, Sample>()
snapsot.appendSections([.main])
snapsot.appendItems(snapshotItems)
// ๋ฐ์ดํฐ ์์ค์ ์๋ก์ด ์ค๋
์ท ์ ์ฉ, ์ด์ ์ค๋
์ท๊ณผ ๋ค๋ฅธ ์ ์ด ์๋ค๋ฉด ์ ๋๋ฉ์ด์
์คํ
dataSource?.apply(snapsot,animatingDifferences: true)
}
}'์ด๋ชจ์ ๋ชจ > UIKit' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| Modern UIKit Collection, TableView #2-2 (0) | 2023.09.03 |
|---|---|
| Modern UIKit Collection, TableView #2-1 (0) | 2023.09.03 |
| WWDC) Customize and resize sheets in UIKit (0) | 2023.08.02 |
| ํ ์ด๋ธ ์ ๋ด๋ถ UIButton์ addTarget์ ๊ณ์ ํด๋ ๋๋์ด์ (0) | 2023.08.01 |
| UICollectionViewFlowLayout์ ์์ดํ ๊ฐ ๊ฐ๊ฒฉ์ ์ฃผ๋ ์ด์ (0) | 2023.07.31 |
๋๊ธ