Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] RxDataSources로 TableView 구현 방법 (RxTableViewSectionedReloadDataSource) 본문

iOS 응용 (swift)

[iOS - swift] RxDataSources로 TableView 구현 방법 (RxTableViewSectionedReloadDataSource)

jake-kim 2021. 12. 1. 22:31

* Section별, Item별로 dataSource 사용 모델 구현 패턴 방법은 이곳 참고


RxTableViewSectionedReloadDataSource 사용

Cocoa Pods 종속성


pod 'RxSwift'
pod 'RxCocoa'
pod 'RxDataSources'

모델 정의


  • RxTableViewSectionedReloadDataSource를 사용하여 DataSource에서 사용되는 데이터형은 `AnimatableSectionModelType`를 준수하는 모델이어야 가능

AnimatableSectionModelType

  • AnimatableSectionModelType은 SectionModelType을 준수하는 프로토콜
    • original에 해당되는 모델은 Section에 해당되고, items에 해당되는 인수는 rows값

SectionModelType

  • AnimatableSectionModelType을 준수하는 모델 정의하기 전에 [Item] 타입에 들어갈 row 모델 먼저 정의
    • Item은 IdentifiableType, Equatable을 준수하는 모델
import RxDataSources

struct MyModel {
    var message: String
    var isDone: Bool = false
}

extension MyModel: IdentifiableType, Equatable {
    var identity: String {
        // 예제 편의를 위해 UUID().uuidString을 사용했고, 실제에서는 데이터를 구분하는 id값 사용을 지향
        return UUID().uuidString
    }
    
}
  • AnimatableSectionModelType을 준수하는 모델 정의
struct MySection {
    var headerTitle: String
    var items: [Item]
}

extension MySection: AnimatableSectionModelType {
    typealias Item = MyModel
    
    var identity: String {
        return headerTitle
    }
    
    init(original: MySection, items: [MyModel]) {
        self = original
        self.items = items
    }
}

RxTableViewSectionedReloadDataSource 구현

  • ViewController 준비
import UIKit
import RxDataSources
import RxCocoa
import RxSwift

class ViewController: UIViewController {

}
  • Section 데이터 준비
var sections = [
    MySection(headerTitle: "첫 번째", items: [MyModel(message: "jake"), MyModel(message:"jake의 iOS")]),
    MySection(headerTitle: "두 번째", items: [MyModel(message:"jake의 iOS앱 개발 알아가기"), MyModel(message:"jake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기")]),
    MySection(headerTitle: "세 번째", items: [MyModel(message:"jake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기")])
]
  • RxTableViewSectionedReloadDataSource 구현 
    • 주의) 해당 코드에서는 row 데이터 (item)만 적용가능
    • section데이터는 dataSource.titleForHeaderInSection으로 따로 설정
/// row 데이터 적용 (section은 dataSource.titleForHeaderInSection으로 설정)
var dataSource = RxTableViewSectionedReloadDataSource<MySection> { dataSource, tableView, indexPath, item in
    let cell = tableView.dequeueReusableCell(withIdentifier: MyTableViewCell.className, for: indexPath) as! MyTableViewCell
    cell.bind(mySectionItem: item)
    return cell
}
  • section 데이터 반영
// viewDidLoad()에서 호출
private func setupTableViewDataSource() {
    dataSource.titleForHeaderInSection = { dataSource, index in
        return dataSource.sectionModels[index].headerTitle
    }
}
// viewDidLoad()에서 호출
private func setupViews() {
    let myTableViewCellNib = UINib(nibName: MyTableViewCell.className, bundle: nil)
    tableView.register(myTableViewCellNib, forCellReuseIdentifier: MyTableViewCell.className)
    tableView.rx.setDelegate(self)
        .disposed(by: disposeBag)
}

extension ViewController: UITableViewDelegate {   
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        // RxTableViewSectionedReloadDataSource는 일반 dataSourec배열처럼 index로 접근 가능
        let data = dataSource[indexPath]
        let myModel = MyModel(message: data.message)

        let calculatedHeight = MyTableViewCell.height(width: tableView.layer.frame.size.width, myModel: myModel)
        return calculatedHeight
    }
}
  • BehaviorRelay를 이용하여 tableView에 데이터 소스 맵핑
var sectionSubject = BehaviorRelay(value: [MySection]())

...

// viewDidLoad()에서 호출
private func setupTableViewDataSource() {
    sectionSubject.accept(sections) // <-
    
    dataSource.titleForHeaderInSection = { dataSource, index in
        return dataSource.sectionModels[index].headerTitle
    }
}

...

// viewDidLoad()에서 호출
private func bindSubject() {
    sectionSubject
        .bind(to: tableView.rx.items(dataSource: dataSource))
        .disposed(by: disposeBag)
}

* 전체 소스 코드: https://github.com/JK0369/ExDynamicHeightCell/tree/RxDataSource

제네릭을 사용한 Section 모델 정의


  • RxDataSources내부에 존재하는 SectionModel<Section, ItemType>모델을 사용

  • 사용할 모델 정의
    • ViewController에서 MySection으로 접근하여 사용
import RxDataSources

public typealias MySection = SectionModel<String, MyModel>

public struct MyModel {
    var message: String
    var isDone: Bool = false
}

extension MyModel: IdentifiableType {
    public var identity: String {
        return UUID().uuidString
    }
    
}
  • ViewController에서 쓰임
var sections = [
    MySection(model: "첫 번째", items: [MyModel(message: "jake"), MyModel(message:"jake의 iOS")]),
    MySection(model: "두 번째", items: [MyModel(message:"jake의 iOS앱 개발 알아가기"), MyModel(message:"jake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기")]),
    SectionModel(model: "세 번째", items: [MyModel(message:"jake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기\njake의 iOS앱 개발 알아가기")])
]

/// row 데이터 적용 (section은 dataSource.titleForHeaderInSection으로 설정)
var dataSource = RxTableViewSectionedReloadDataSource<MySection> { dataSource, tableView, indexPath, item in
    let cell = tableView.dequeueReusableCell(withIdentifier: MyTableViewCell.className, for: indexPath) as! MyTableViewCell
    cell.bind(model: item)
    return cell
}

...

* 전체 소스 코드: https://github.com/JK0369/ExDynamicHeightCell

cf) ReactorKit + RxDataSource 편리하게 사용 방법: https://ios-development.tistory.com/796

 

* 참고

- https://github.com/RxSwiftCommunity/RxDataSources

 

 

Comments