RxSwift UITableView (1) - One Section, One Cell, More Cell
UITableView관한 포스팅이다.
UITableView로 MVVM패턴을 많이 적용하는 예제들이 많다. 하지만 이번 포스팅은 MVVM패턴은 1도 사용하지 않았다.
UITableView를 어떻게 사용하는 것 부터가 먼저라고 생각한다.
제일 하단에 전체 코드를 복붙 해놓았다.
기본적인 준비코드다.
UIViewController를 상속받고 UITableViewDeletgate를 구현하게 된다.
UITableViewDataSource는 BehaviorRelay를 구독하여 구현할 것이기 때문에 따로 추가하지 않는다.
class SampleTableViewController: UIViewController, UITableViewDelegate {
@IBOutlet var tableView: UITableView!
var tableViewItems = BehaviorRelay(value: [String]())
let disposeBag = DisposeBag()
}
Delegate Rx로 셋팅해주기
Rx로 set을 해주고 Rx를 사용하기 때문에 Rx로 구현해 줘야 할것 같지만 일반적으로 구현 하는 것과 동일하다.
따로 설명할게 없다.
override func viewDidLoad() {
super.viewDidLoad()
tableView.rx
.setDelegate(self)
.disposed(by: disposeBag)
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = self.tableView.cellForRow(at: indexPath) as? SampleTableViewCell
guard cell != nil else {
return
}
print("didSelectRowAt : + \(String(describing: cell?.label.text))")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
UITableViewDataSource 설정하기
One Section - One Cell UITableViewDataSource
1개의 Cell Type만 사용 하는 UITableViewDataSource설정이다.
tableViewItems의 시퀀스를 UITableView에 바인딩 하고 있다.
bind to : ~에 묶다
ex) bind to UITableView : UITableView에 묶다
private func initDataSource(_ nibName: String) {
self.tableViewItems
.asObservable()
.bind(to: tableView.rx.items(cellIdentifier: nibName, cellType: SampleTableViewCell.self))
{ row, element, cell in
cell.label.text = "\(element) + row : \(row)"
}.disposed(by: disposeBag)
}
UITableview.rx.items(cellIdentifier:String cellType:Cell.Type)는 Disposable을 반환을 해주고 있다.
클로저에는 해당값이 들어오게 된다.
row : indexPath의 row값이 들어온다.
element : tableViewItems[row] -> 해당 row의 item이 들어온다.
cell : SampleTableViewCell -> SampleTableViewCell의 cellType을 넣어줬기 때문에 SampleTableViewCell이 생성되서 들어온다.
해당 클로저는
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 와 같은 역할을 하게 된다.
UITableview.rx.items(cellIdentifier:String cellType:Cell.Type) 구현부
/**
Binds sequences of elements to table view rows.
- parameter cellIdentifier: Identifier used to dequeue cells.
- parameter source: Observable sequence of items.
- parameter configureCell: Transform between sequence elements and view cells.
- parameter cellType: Type of table view cell.
- returns: Disposable object that can be used to unbind.
Example:
let items = Observable.just([
"First Item",
"Second Item",
"Third Item"
])
items
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}
.disposed(by: disposeBag)
*/
public func items<S: Sequence, Cell: UITableViewCell, O : ObservableType>
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
-> (_ source: O)
-> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void)
-> Disposable
where O.E == S {
return { source in
return { configureCell in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { tv, i, item in
let indexPath = IndexPath(item: i, section: 0)
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
return self.items(dataSource: dataSource)(source)
}
}
}
One Section - More Cell UITableViewDataSource
한 Section안에서 여러 Cell Type이 필요한 경우가 발생한다. 하지만 위의 코드는 한가지 Cell Tyep만 지원하기 때문에 사용할 수 없다.
하기 코드는 한 Section안에서 여러 Type의 Cell을 사용 할수 있는 코드다.
private func initDataSource() {
self.tableViewItems
.asObservable()
.bind(to: tableView.rx.items) { (tableView, row, element) -> UITableViewCell in
let cell: SampleTableViewCell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell") as? SampleTableViewCell ?? SampleTableViewCell()
cell.label.text = "\(element) + row : \(row)"
return cell
}.disposed(by: disposeBag)
}
.bind(to: tableView.rx.items) { ....}
tableViewItems의 시퀀스를 tableview.rx.items(_ source: )에 bind하고 있다.
클로저에는 해당값이 들어오게 된다.
tableView : UITableView
row : indexPath의 row값이 들어온다.
element : tableViewItems[row] -> 해당 row의 item이 들어온다.
한가지 Type의 Cell만 사용할경우 조금 달라진다.
클로저 내부에서 Cell을 생성해 줘야 한다.
let cell = tableView.dequeueReusableCell(withIdentifier: Identifier) ?? UITableViewCell()
NSIndexPath가 내려오지 않기때문에 tableView.dequeueReusableCell(withIdentifier: String) 을 사용해야한다.
nil이 반환될 수 있기 때문에 nil일경우 UITableViewCell을 생성해야 한다.
cell을 클로저에서 생성하기 때문에 특정 조건에 따라 Cell Type을 변경할 수 있다.
UITableView.rx.items(_ source:) 의 구현부
/**
Binds sequences of elements to table view rows.
- parameter source: Observable sequence of items.
- parameter cellFactory: Transform between sequence elements and view cells.
- returns: Disposable object that can be used to unbind.
Example:
let items = Observable.just([
"First Item",
"Second Item",
"Third Item"
])
items
.bind(to: tableView.rx.items) { (tableView, row, element) in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = "\(element) @ row \(row)"
return cell
}
.disposed(by: disposeBag)
*/
public func items<S: Sequence, O: ObservableType>
(_ source: O)
-> (_ cellFactory: @escaping (UITableView, Int, S.Iterator.Element) -> UITableViewCell)
-> Disposable
where O.E == S {
return { cellFactory in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S>(cellFactory: cellFactory)
return self.items(dataSource: dataSource)(source)
}
}
전체 코드
한가지 Cell Tyep만 사용하는것과 여러 Cell Type을 사용하는 것을 한곳에 넣어두었다.
필요에 따라 주석 or 삭제 처리를 하면된다.
import UIKit
import RxCocoa
import RxSwift
class SampleTableViewController: UIViewController, UITableViewDelegate {
@IBOutlet var tableView: UITableView!
var tableViewItems = BehaviorRelay(value: [String]())
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
initNavigation()
initData()
tableView.rx
.setDelegate(self)
.disposed(by: disposeBag)
let nibName = "SampleTableViewCell"
registCell(nibName)
initDataSource(nibName)
}
private func initNavigation() {
self.navigationItem.hidesBackButton = true
self.navigationItem.rightBarButtonItem = getDeleteButton()
self.navigationItem.leftBarButtonItem = getBackButton()
}
private func initData() {
let tmep = ["item0", "item1", "item2", "item3", "item4", "item5"]
tableViewItems.accept(tmep)
}
private func registCell(_ nibName: String) {
let nib = UINib(nibName: nibName, bundle: nil)
tableView.register(nib, forCellReuseIdentifier: nibName)
}
private func initDataSource(_ nibName: String) {
// one cell -> 특정 cell 하나만 사용할 경우 아래 코드(more cell) 삭제 or 주석
self.tableViewItems
.asObservable()
.bind(to: tableView.rx.items(cellIdentifier: nibName, cellType: SampleTableViewCell.self)) { row, element, cell in
cell.label.text = "\(element) + row : \(row)"
}.disposed(by: disposeBag)
// more cell -> 여러 cell을 사용할 경우 위의 코드(one cell) 삭제 or 주석
self.tableViewItems
.asObservable()
.bind(to: tableView.rx.items) { (tableView: UITableView, index: Int, element: String) -> UITableViewCell in
let cell: SampleTableViewCell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell") as? SampleTableViewCell ?? SampleTableViewCell()
cell.label.text = "\(element) + row : \(index)"
return cell
}.disposed(by: disposeBag)
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = self.tableView.cellForRow(at: indexPath) as? SampleTableViewCell
guard cell != nil else {
return
}
print("didSelectRowAt : + \(String(describing: cell?.label.text))")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
extension SampleTableViewController {
func getDeleteButton() -> UIBarButtonItem {
let button = UIButton()
button.setTitle("삭제", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.rx.tap.bind { [weak self] _ in
var values = self?.tableViewItems.value ?? [String]()
guard values.count != 0 else {
return
}
values.remove(at: 0)
self?.tableViewItems.accept(values)
}.disposed(by: disposeBag)
return UIBarButtonItem(customView: button)
}
func getBackButton() -> UIBarButtonItem {
let button = UIButton()
button.setTitle("뒤로", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.rx.tap.bind { [weak self] in
self?.parent?.dismiss(animated: true, completion: nil)
}.disposed(by: disposeBag)
return UIBarButtonItem(customView: button)
}
}
'iOS > Swift' 카테고리의 다른 글
RxSwift Realm(3) - realm변경 감지 (Observable.changeset) (0) | 2019.08.03 |
---|---|
RxSwift UITableView (2) - More Section (1) | 2019.07.20 |
RxSwift UITextField (0) | 2019.06.01 |
RxSwift property observe (BehaviorRelay, Variable) (0) | 2019.05.25 |
RxSwift 객체 구독하기 (Observable) (0) | 2019.05.18 |
댓글