공부한 것들 기록하려고 합니다 . . !
혼자 공부한 것이므로 틀린 내용이 있을 수 있습니다 . .
iOS - RxDataSources
프로젝트를 진행하면서 collectionView를 구현하는데 RxDataSources를 사용하였는데요.
기존에 DataSource를 사용하던 방식과 달리 따로 프로토콜을 채택하여 구현해줄 필요없이 데이터 바인딩을 통해 collectionView를 구현할 수 있는 방법입니다 . . !
저는 ReactorKit을 사용하고 있기 때문에 RxDataSources를 사용하여 각각의 Cell에 전용 Reactor를 생성하여 Cell이 UI 업데이트를 할 때, 이 Reactor 인스턴스를 받아서 사용하도록 하였습니다.
lazy var monthDataSource = DataSource { [weak self] _, collectionView, indexPath, item -> UICollectionViewCell in
switch item {
case let .month(reactor):
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: MonthCollectionViewCell.self), for: indexPath) as? MonthCollectionViewCell else { return .init() }
cell.reactor = reactor
return cell
}
}
이런식으로 각 cell마다 reactor를 적용했습니다 !
사용방법
1. Cell과 CellReactor를 정의합니다.
import ReactorKit
class MonthCollectionViewCellReactor: Reactor {
enum Action { }
enum Mutation {}
struct State {
var day: String
}
var initialState: State
init(state: State) {
self.initialState = state
}
}
저는 달력을 구현하였기 때문에 해당 일을 받아올 String 타입의 day를 State에 선언하였습니다.
import UIKit
import ReactorKit
class MonthCollectionViewCell: BaseCollectionViewCell, View {
// MARK: - Properties
typealias Reactor = MonthCollectionViewCellReactor
// MARK: - UI Components
...
// MARK: - Methods
...
func bind(reactor: Reactor) {
dayLabel.text = reactor.currentState.day
dayRoundView.isHidden = reactor.currentState.day == ""
}
}
Cell에서는 bind 메서드를 구현하여 UI를 업데이트 해줍니다.
reactor.currentState를 통해 현재 state를 받아옵니다.
2. SectionModel을 정의해줍니다.
import RxDataSources
typealias MonthSectionModel = SectionModel<MonthSection, MonthItem>
enum MonthSection {
case month([MonthItem])
}
enum MonthItem {
case month(MonthCollectionViewCellReactor)
}
Section과 Item에서 사용할 때는 enum으로 선언하고, enum으로 구분 후 associate type으로 모델을 선언합니다.
MonthSection은 Section에 사용될 타입이고, MonthItem은 Item에 사용될 타입입니다.
지금 저는 하나의 Section만 사용중이므로 하나만 선언하였습니다.
extension MonthSection: SectionModelType {
typealias Item = MonthItem
var items: [Item] {
switch self {
case let .month(items):
return items
}
}
init(original: MonthSection, items: [MonthItem]) {
switch original {
case let .month(items):
self = .month(items)
}
}
}
해당 MonthSection은 RxDataSources의 모델로 사용하기 위해 SectionModelType 프로토콜을 준수하며 Item과 items를 정의해줘야 합니다.
3. RxDataSources 사용
// MARK: - HomeViewController
// dataSource 생성하고, MonthSectionModel 타입으로 전달
typealias DataSource = RxCollectionViewSectionedReloadDataSource<MonthSectionModel>
lazy var monthDataSource = DataSource { [weak self] _, collectionView, indexPath, item -> UICollectionViewCell in
switch item {
case let .month(reactor):
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: MonthCollectionViewCell.self), for: indexPath) as? MonthCollectionViewCell else { return .init() }
cell.reactor = reactor
return cell
}
}
func bind(reactor: HomeReactor) {
// collectionView에 bind 해주기
reactor.state
.map(\.monthSections)
.bind(to: monthView.collectionView.rx.items(dataSource: monthDataSource))
.disposed(by: disposeBag)
}
RxCollectionViewSectionedReloadDataSource<MonthSectionModel>으로 사용합니다.
collectionView에 해당 MonthSectionModel 객체를 바인딩합니다.
// MARK: - HomeReactor
func makeSections() -> [MonthSectionModel] {
let days = getDays()
let items = days.map { (day) -> MonthItem in
return .month(MonthCollectionViewCellReactor(state: .init(day: day)))
}
let section = MonthSectionModel.init(model: .month(items), items: items)
return [section]
}
Reactor에서 해당 MonthSectionModel을 Observable로 정의합니다.
참고
'iOS > ReactorKit' 카테고리의 다른 글
[ReactorKit] ReactorKit의 transform() 사용하기 (0) | 2022.12.06 |
---|---|
[ReactorKit] ReatorKit 살펴보기 (0) | 2022.11.10 |