
프로젝트를 진행하면서 네이티브 화면을 구성하며 참 많이 사용하는 것들이 UITableView, UICollectionView 들인것 같다.
하지만 아직 UITableView, UICollectionView 의 기본적인 사용법이야 알지만, 깊게 이해하고 있는것 같지가 않아,
정리를 하며 이해해나가고자 해당 포스팅을 계획하였다. 우선 UITableView 부터!
RxSwift 와 함께 사용했을때, 그리고 Section의 갯수를 1개로 고정 OR Section's Row 수를 1개로 고정할 경우 사용하기는 쉬우나,
Section의 갯수와 Section 별 Row 의 갯수가 유동적으로 변한다고 할때도 사용할 수 있어야 할것이다.
우선 SampleViewController 가 정의되어 있고, 해당 ViewController 에서 tableView를 하나 사용중이다.
그럼 다음과 같은 Delegate, DataSource 를 일반적(?), 필수적으로 정의할 것이다.
extension SampleViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // optional 없으면 알아서 return 1
}
// 해당 섹션에 몇 개의 row를 표현해 주면 되니?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// TODO:
return UITableViewCell()
}
}
이것은 아주 간단한 사용법이고, 여기선 RxSwift, ReusableKit, RxDataSources를 이용하여 유동적으로 Section의 수와 Section별 row 의 수도 변하는 경우에 사용하는 예제를 정리하고 한다.
예제의 상황은 이러하다.
홍길동은 사과은행(Apple Bank)을 주거래 은행으로 사용하고 있고, 사과은행의 앱에서 오픈뱅킹을 등록하여 모든 금융기관의 계좌들을 한 앱에서 관리하고 있다.
사과은행의 전계좌조회 화면을 구성한다고 가정해보자.
import RxSwift
import UIKit
import ReusableKit
import RxDataSources
import RxRelay
final class AllAcctsViewController: BaseViewController {
typealias DataSource = RxTableViewSectionedReloadDataSource<AllAcctsSection>
private var dataSource: DataSource?
private var acctsSection: [AllAcctsSection] = []
init() {
super.init()
dataSource = DataSource(
configureCell: { [weak self] _, tableView, indexPath, item -> UITableViewCell in
switch item {
case let .apple(acct):
let cell = tableView.dequeue(Reusable.cell, for: indexPath)
return cell
case let .openBank(acct):
let cell = tableView.dequeue(Reusable.cell, for: indexPath)
return cell
case .empty:
let cell = tableView.dequeue(Reusable.empty, for: indexPath)
return cell
}
},
canEditRowAtIndexPath: { (_, _) in
return true
},
canMoveRowAtIndexPath: { _, _ in
return true
})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
enum Reusable {
static let cell = ReusableCell<AllAcctsViewCell>(nibName: AllAcctsViewCell.nibName)
static let empty = ReusableCell<AllAcctsEmptyViewCell>(nibName: AllAcctsEmptyViewCell.nibName)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.do {
$0.bounces = false
$0.separatorStyle = .none
$0.register(Reusable.cell)
$0.register(Reusable.empty)
$0.rx.setDelegate(self).disposed(by: disposeBag)
}
bindTableView()
}
private func bindTableView() {
guard let dataSource = datsSource else { return }
let behaviorRelay = BehaviorRelay<[AllAcctsSection]>(value: self.acctsSection)
behaviorRelay.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
}
@IBOutlet weak var tableView: UITableView!
}
자 RxTableViewSectionedReloadDataSource<AllAcctsSection> ⇒ 이 부분에 집중하자
open class RxTableViewSectionedReloadDataSource<Section: SectionModelType>
: TableViewSectionedDataSource<Section>
, RxTableViewDataSourceType {
public typealias Element = [Section]
open func tableView(_ tableView: UITableView, observedEvent: Event<Element>) {
Binder(self) { dataSource, element in
#if DEBUG
dataSource._dataSourceBound = true
#endif
dataSource.setSections(element)
tableView.reloadData()
}.on(observedEvent)
}
}
RxDataSources 의 RxTableViewSectionedReloadDataSource 클래스는 위와 같다.
우린 SectionModelType으로 AllAcctsSection을 쓰겠다는 것이다.
말 그대로 SectionModel 을 정의해서 이걸로 쓰겠어! 하여 AllAcctsSection을 정의하고,
tableView.rx.items 에 bind 하여야 한다. (bindTableView)
Observable을 변수 형태로 들고 다니며 사용하는 Relay, Subject 의 개념도 다시 한번 사용해보는 계기가 되었다 (BehaviorRelay)
하지만 Relay, Subject 의 개념은 더 깊숙히 알아봐야 할것 같다.. (내가 이해하고 있는 게 올바른 개념이 아닌 느낌이랄까?)
마지막으로 SectionModel 을 정의한 AllAcctsSection의 코드와 SectionModelType 의 정의에 대한 코드를 추가한다.
import Foundation
public protocol SectionModelType {
associatedtype Item
var items: [Item] { get }
init(original: Self, items: [Item])
}
import RxDataSources
enum AllAcctsType {
case apple
case openBank
case empty
}
enum AllAcctsItem {
case apple(Account)
case openBank(OpenBankAccount)
case empty
}
struct AllAcctsSection {
var kind: AllAcctsType
var items: [AllAcctsItem]
}
extension AllAcctsSection: SectionModelType {
typealias Item = AllAcctsItem
init(original: AllAcctsSection, items: [Item]) {
self = original
self.items = items
}
}
추후 tableView.rx.items 에 bind 하는 것이 현재 포스팅에 사용한
tableView.rx.items(dataSource: dataSource)) 말고도 몇가지가 더 있는데, 이에 대한 부분은
아래 주소를 참조하자!
'iOS_RxSwift' 카테고리의 다른 글
| Subject, Relay 에 대한 활용 및 이해도 정리 (0) | 2022.01.24 |
|---|---|
| RxSwift + ReactorKit 에러 트래킹 Custom 방식에 대한 정리 (0) | 2022.01.14 |
| RxSwift ErrorTracker GitHub 코드 정리 (0) | 2022.01.11 |
| return Single<> 비동기 처리 (0) | 2022.01.11 |
| RxSwift ActivityIndicator GitHub 코드 정리 (0) | 2022.01.11 |