ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RxSwift로 UITableView 구현하기
    🧑‍💻/Swift 2021. 8. 7. 19:28
    반응형

    RxSwift를 사용하면 Delegate와 Delegate 프로토콜을 채택하여 구현했을 때 보다 코드의 양이 크게 줄어듭니다.
    이 외에도 Thread 사용이 간편하다는 점 등 여러 장점이 있는데요.
    먼저 Podfile에 RxSwift, RxCocoa를 추가하고 시작해 봅시다.


    UITableViewDataSource

    TableView를 구성하는 Cell 데이터를 관리하기 위한 프로토콜입니다.
    스토리보드에 설정한 UITableView의 dataSource 연결을 해제합니다.
    코드로 구현했었다면 UITableViewDataSource 프로토콜 연결을 제거하고 tableView.dataSource = self 코드를 제거합니다.

    1개의 Section으로 구성된 기본 TableView

    UITableViewDataSource에서 tableView(_:cellForRowAt:)로 구현하던 것을 RxCocoa의 tableView.rx.item로 구현했습니다.
    Post 목록이 변경될 때마다 Cell을 새롭게 구성하므로 tableView.reloadData()가 호출되는 효과가 있습니다.

    class ViewController: UIViewController {
    	
        @IBOutlet weak var tableView: UITableView!
        var posts = BehaviorRelay<[Post]>(value: [])
        var disposeBag = DisposeBag()
        
        override func viewDidLoad() {
        	
            // Cell 등록
            let nibName = UINib(nibName: PostListTableViewCell.nibName, bundle: nil)
            tableView.register(nibName, forCellReuseIdentifier: PostListTableViewCell.cellID)
            tableView.rowHeight = UITableView.automaticDimension
            
            // Cell 그리기
            posts
            .observe(on: MainScheduler.instance)
            .bind(to: tableView.rx.items(cellIdentifier: PostListTableViewCell.cellID, cellType: PostListTableViewCell.self)) { index, item, cell in
                cell.bind(post: item)
            }
            .disposed(by: disposeBag)
        }
        
        // ...
    }

     

    Cell이 재사용되므로 UITableViewCell 구현부에 prepareForReuse()를 override 합니다.
    모든 subscribe를 종료할 수 있도록 전역변수로 선언한 disposeBag을 재할당해줍니다.

    override func prepareForReuse() {
        super.prepareForReuse()
    
        disposeBag = DisposeBag()
    }

     

    여러 Section으로 구성된 TableView

    Section이 여러 개로 구성되어 있는 TableView를 구현할 땐, RxDataSources를 사용합니다.
    Section이 1개였을 때와 달리 조금 복잡하네요.

    1. Podfile에 RxDataSources 추가

    저장하고 pod install 후 워크스페이스를 새로 엽니다.

     

    2. SectionModelType 추가

    Section을 구성하는 구조체를 생성합니다. 저는 날짜별로 Todo 목록을 표시하려 다음과 같이 구성했습니다.

    import Foundation
    import RxDataSources
    
    struct Task {
        var date: String
        var items: [Todo]
    }
    
    extension Task: SectionModelType {
        typealias Item = Todo
        
        init(original: Task, items: [Item]) {
            self = original
            self.items = items
        }
    }

     

    3. DataSource 추가

    Cell을 그리기 위한 DataSource를 생성합니다.
    titleForHeaderInSection으로 tableView(_:titleForHeaderInSection:)를 대체할 수 있습니다.

    주의할 점은 sections에 dataSource를 설정했음에도 TableView에 DataSource를 설정하면 오류가 발생합니다.

    tableView.dataSource = dataSource

    RxCocoa/DelegateProxyType.swift:345: Assertion failed: Proxy changed from the time it was first set.

     

    class ViewController: UIViewController {
    
        @IBOutlet weak var tableView: UITableView!
        
        private var sections: BehaviorRelay<[Task]> = BehaviorRelay(value: [])
        private var dataSource: RxTableViewSectionedReloadDataSource<Task>!
        var disposeBag = DisposeBag()
        
        override func viewDidLoad() {
        
            // Cell 등록
            let nibName = UINib(nibName: TodoTableViewCell.nibName, bundle: nil)
            tblTodo.register(nibName, forCellReuseIdentifier: TodoTableViewCell.identifier)
            tblTodo.rowHeight = UITableView.automaticDimension
            
        	// Cell 그리기
            dataSource = RxTableViewSectionedReloadDataSource<Task> { dataSource, tableView, indexPath, item in
                let cell = tableView.dequeueReusableCell(withIdentifier: TodoTableViewCell.identifier, for: indexPath) as! TodoTableViewCell
                
                // cell 설정
                cell.bind(task: item)
                
                return cell
            }
            
            // Section 타이틀 
            dataSource.titleForHeaderInSection = { ds, index in
                return ds.sectionModels[index].date
            }
            
            // 모든 Cell에 DataSource 연결
            sections.asDriver()
                .drive(tableView.rx.items(dataSource: dataSource))
                .disposed(by: disposeBag)
        
        }
        
        // ...
    }

     

    4. Section 설정

    Section을 BehaviorRelay로 선언했으므로 accept로 데이터를 전달합니다.
    특정 이벤트나 데이터 값에 따라 Section을 달리 보여주고 싶다면 sections.accept를 호출해주면 됩니다.
    기존에 있던 TableView의 reloadData()를 사용하지 않아도 자동으로 갱신됩니다.

    sections.accept([Task(date: "2021-08-07", items: []),
                     Task(date: "2021-08-08", items: [])])

     


    UITableViewDelegate

    TableView에 대한 사용자 이벤트를 관리하기 위한 프로토콜입니다.
    기존에 UITableViewDelegate를 연결하여 처리하던 작업을 RxCocoa를 사용해 바꿔봅니다.
    스토리보드에 설정한 UITableView의 delegate 연결을 해제합니다.
    코드로 구현했었다면 UITableViewDelegate 프로토콜 연결을 제거하고 tableView.delegate = self 코드를 제거합니다.

     

    Cell 선택

    itemSelected는 선택한 Cell의 indexPath를 return 하고, modelSelected는 선택한 Cell의 Model을 return 합니다.
    Zip Operator를 사용하면 Cell 선택 시 indexPath와 Model을 묶어 한번에 처리할 수 있습니다.

    /* Cell 선택 */
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    	// ...
    }
    
    Observable.zip(tableView.rx.modelSelected(Item.self), tableView.rx.itemSelected)
        .bind { (item, indexPath) in
            // ...
        }
        .disposed(by: disposeBag)

     

    Cell 삭제

    /* Cell 삭제 */
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    	// ...
    }
    
    tableView.rx.itemDeleted
      .bind { indexPath in
         // ...
      }
      .disposed(by: disposeBag)

     

    Cell 이동

    /* Cell 순서 변경 시 조정 */
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    	// ...
    }
    
    tableView.rx.itemMoved
      .bind { sourceIndexPath, destinationIndexPath in
      	// ...
      }
      .disposed(by: disposeBag)

     


    프로젝트 진행하면서 사용한 TableView API들로 포스팅 해 보았는데 추후에 더 추가하려 합니다.
    자세한 코드 내용은 저의 Github에서 참고하세요.

    https://github.com/bigtoy2645/todoList-iOS

     

    GitHub - bigtoy2645/todoList-iOS: A simple Todo application in Swift

    A simple Todo application in Swift. Contribute to bigtoy2645/todoList-iOS development by creating an account on GitHub.

    github.com

     

    참고

    반응형

    댓글

Designed by Tistory.