본문 바로가기
iOS/Swift

RxSwift UITableView (1) - One Section, One Cell, More Cell

by DnaJ 2019. 7. 15.
반응형

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로 구현해 줘야 할것 같지만 일반적으로 구현 하는 것과 동일하다.

따로 설명할게 없다.

Delegate setting
Delegate 구현 -Select, Cell Hieght

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을 반환을 해주고 있다.

클로저에는 해당값이 들어오게 된다.

rowindexPath의 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)
    }
}
반응형

댓글