์๋ ํ์ธ์.
์ค๋์ RxDataSource์ ๋ํด์ ์ ๋ฆฌํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
RxDataSource๋ฅผ ํ์ตํ๊ณ ๊ฐ๋จํ ์์ ๋ฅผ ๋ง๋ค์ด๋ดค์ต๋๋ค.
RxDataSource๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ RxSwift๋ฅผ ๊ณต๋ถํ๊ธฐ ์์ํ ๋๋ถํฐ ์๊ณ ๋ ์์์ง๋ง
ํ์ฌ๋ ๊ฐ์ธ ํ๋ก์ ํธ์์ ์ฌ์ฉํด ๋ณธ ์ ์ด ์์์ต๋๋ค.
์ด๋ฒ์ ์ฌ๋ฌ ๊ฐ์ ์น์ ์ด ํ์ํ ์์ ์ด ์์ด ๊ฐ๋จํ๊ฒ ํ์ตํ๊ณ ์์ ๋ฅผ ํตํด์ ์ ๋ฌธํ๋ ์๊ฐ์ ๊ฐ์ก์ต๋๋ค.
RxDataSource?
๊ธฐ์กด์ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด ( RxSwift, RxCocoa ) ์๋์ ๊ฐ์ต๋๋ค.
tasks
.bind(to: taskTableView.rx.items(cellIdentifier: TaskTableViewCell.identifier, cellType: TaskTableViewCell.self)) { (index, item, cell) in
cell.setDataSource(task: item)
}
.disposed(by: disposeBag)
DataSource๋ฅผ ์ฑํํ์ง ์์๋ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ์ฝ๋๋ฅผ ๊ด๋ฆฌํ๊ธฐ ํธํ ์ฅ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
ํ์ง๋ง ์ ์ ๊ฒฝ์ฐ์ฒ๋ผ ์ฌ๋ฌ ๊ฐ์ ์น์ ์ ๊ตฌํํด์ผ ํ๋ค๋ฉด ์์ ์ฝ๋๋ก๋ ๋์์ด ์ด๋ ต์ต๋๋ค.
์ถ๊ฐ์ ์ผ๋ก ์์ ์ฝ๋๋ reloadData๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ธ๋ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์
์ ๋๋ฉ์ด์ ์ ์ถ๊ฐํ ์๊ฐ ์๋ ๋จ์ ๋ ์กด์ฌํฉ๋๋ค.
์ด์ ๋ํ ๋ณด์์ ์ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ด ๋ฐ๋ก RxDataSource์ ๋๋ค.
๊ธฐ์กด์ rx.items์ ์ ๊ทผํ๋ฉด ๋ ๊ฐ์ ์๋์์ฑ์ด ๋ฑ์ฅํ๋๋ฐ
ํ๋๋ cellIdentifier์ด๊ณ ํ๋๋ dataSource์ ๋๋ค.
์ด dataSource์ ๋ค์ด๊ฐ๋ ๊ฒ์ ๊ตฌํํด ์์ ๋จ์ ์ ํด๊ฒฐํ ์ ์์ต๋๋ค.
์ฌ์ฉ๋ฐฉ๋ฒ
RxDataSource์๋ UITableview์ ๋ํ RxTableViewSectionedReloadDataSource๋ฅผ, UICollectionview์๋ RxCollectionViewSectionedReloadDataSource๋ผ๋ ํด๋์ค๋ฅผ ์ ๊ณตํจ์ผ๋ก์จ ์น์ ์ DataSource๋ฅผ ๊ด๋ฆฌํ ์ ์๊ฒ ๋์์ค๋๋ค. ์ฌ๊ธฐ์ reload๊ฐ ์๋ animated๋ก ๋ณ๊ฒฝ๋ ํด๋์ค๊ฐ ์กด์ฌํ๋๋ฐ ์ด๋ ์์ ๋งํ ๊ฒ์ฒ๋ผ ์ ๋๋ฉ์ด์ ์ ์ถ๊ฐํ๊ฑฐ๋ ์ค์ ํ ์ ์๊ฒ ๋์์ฃผ๋ ํด๋์ค์ ๋๋ค.
DataSource๊ฐ ์ด๋ค ํํ๋ก ๋์ด์๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Item>>(
configureCell: { dataSource, tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = item.title
return cell
}
)
SectionModel์ด๋ผ๋ ๊ตฌ์กฐ์ฒด๋ฅผ ํตํด ํด๋น ์น์ ์ ๋ชจ๋ธ ๋ฐ์ดํฐ๋ฅผ ์ ์ํ ์ ์์ต๋๋ค.
๋ฌผ๋ก ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ตฌ์กฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ ์์ฒด์ ์ผ๋ก ์ปค์คํ ํ ๋ชจ๋ธ์ด ํ์ํ๋ค๋ฉด
public protocol SectionModelType {
associatedtype Item
var items: [Item] { get }
init(original: Self, items: [Item])
}
์์ ํ๋กํ ์ฝ์ ์ฑํํ๊ณ ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
์๋๋ ์์ ์ฝ๋์ ๋๋ค.
struct CustomData {
let id: String
let name: String
}
struct CustomDataSection {
var header: String
var items: [CustomData]
}
extension CustomDataSection: SectionModelType {
typealias Item = CustomData
init(original: CustomDataSection, items: [CustomData]) {
self = original
self.items = items
}
}
์ด๋ ๊ฒ ์น์ ๋ชจ๋ธ์ ๊ตฌํํ๋ค๋ฉด ์์ ์ค๋ช ํ DataSource ์ฝ๋์ ๊ทธ๋๋ก ์ ์ฉํด ์ฌ์ฉํ ์ ์์ต๋๋ค.
์์ : ํ ์ผ ๋ชฉ๋ก ์ฑ
TableView์ personal, work, shopping์ด๋ผ๋ ์น์ ์ ๊ฐ์ง ํ ์ผ ๋ชฉ๋ก์ ๊ฐ๊ฐ ๊ฐ์ง๊ณ ์๋
๊ฐ๋จํ ์ฑ์ ์์๋ก ๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
์ผ๋จ ๊ฐ์ฅ ๋จผ์ ํ ์ผ ๋ชฉ๋ก ๋ชจ๋ธ์ ์ ์ํฉ๋๋ค.
Task - ๋ชจ๋ธ
struct Task: IdentifiableType, Equatable {
let id: Int
let title: String
let category: TaskCategory
var identity: Int {
return self.id
}
}
enum TaskCategory: String, IdentifiableType {
case personal
case work
case shopping
var identity: String {
return self.rawValue
}
}
์์ ๋งํ personal, work, shopping ์ธ ๊ฐ์ง์ ์น์ ๋ ์ด๊ฑฐํ์ผ๋ก ์ถ๊ฐํฉ๋๋ค.
TaskViewModel - ๋ทฐ๋ชจ๋ธ
class TaskViewModel {
private let tasks = BehaviorRelay<[AnimatableSectionModel<TaskCategory, Task>]>(value: [])
func fetchTasks() -> Observable<[AnimatableSectionModel<TaskCategory, Task>]> {
let personalTasks: [Task] = [
Task(id: 1, title: "์ฐ์ ์ฌ๊ธฐ", category: .personal),
Task(id: 2, title: "์น์ฆ ์ฌ๊ธฐ", category: .personal),
]
let workTasks: [Task] = [
Task(id: 3, title: "๋ธ๋ก๊ทธ ํฌ์คํ
", category: .work),
Task(id: 4, title: "ํ์ ์ฐธ์ฌ", category: .work),
]
let shoppingTasks: [Task] = [
Task(id: 5, title: "๊ฐ๋ฐฉ ์ฌ๊ธฐ", category: .shopping),
Task(id: 6, title: "์์ดํฐ15 ์ฌ๊ธฐ", category: .shopping),
]
let sections: [AnimatableSectionModel<TaskCategory, Task>] = [
AnimatableSectionModel(model: .personal, items: personalTasks),
AnimatableSectionModel(model: .work, items: workTasks),
AnimatableSectionModel(model: .shopping, items: shoppingTasks),
]
tasks.accept(sections)
return tasks.asObservable()
}
}
๊ฐ๊ฐ์ ์น์ ๋ณ๋ก ์์ดํ ์ ์์ฑํ๊ณ ๋ฐฉ์ถํฉ๋๋ค. ( ์๋ฒ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ๋ฐฉ์ถํฉ๋๋ค. )
AnimatableSectionModel์ ์ฌ์ฉํ์ ๋ ๋๊ณ SectionModel์ ์ฌ์ฉํ์ ๋ ๋ฌด๋ฐฉํฉ๋๋ค.
TaskViewController - ๋ทฐ
private func bind() {
let dataSource = RxTableViewSectionedAnimatedDataSource<AnimatableSectionModel<TaskCategory, Task>>(
configureCell: { dataSource, tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
cell.configure(task: item)
return cell
},
titleForHeaderInSection: { dataSource, index in
return dataSource.sectionModels[index].model.rawValue
}
)
viewModel.fetchTasks()
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
์ด๋ ๊ฒ ์์ฑํ๊ฒ ๋๋ฉด ์ฌ๋ฌ๊ฐ์ ์น์ ์ ํ ํ ์ด๋ธ๋ทฐ์ ํ์ํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ ๋๋ฉ์ด์ ์ฒ๋ฆฌ๋ ํ ์ ์๋ค๋ ์ฅ์ ๋ ๊ฐ์ง๊ณ ์์ผ๋
๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ฉด ์ถ๊ฐ, ๋ฆฌ๋ก๋ ๊ทธ๋ฆฌ๊ณ ์ญ์ ์ ๊ดํ ์ ๋๋ฉ์ด์ ์ด ํจ๊ป ๋์ํ ์ ์๊ฒ ๋ฉ๋๋ค.
dataSource.animationConfiguration = AnimationConfiguration(insertAnimation: .fade, reloadAnimation: .fade, deleteAnimation: .fade)
๊ฐ๋จํ ์์ ๋ฅผ ํตํด์ RxDataSource์ ์ ๋ฌธํด ๋ดค์ต๋๋ค.
๋์์ด ๋์ จ์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค.
๊ทธ๋ผ ์ด๋ง ๐๐ป ๐๐ป ๐๐ป