Search code examples
iosswiftrx-swiftrx-cocoarxdatasources

Implementing multiple sections with RxDatasources


I am trying to make multiple sections (two actually) using RxDatasources. Usually with one section, I would go like this:

Section model:

import Foundation

import RxDataSources

typealias NotificationSectionModel = AnimatableSectionModel<String, NotificationCellModel>


struct NotificationCellModel : Equatable, IdentifiableType {
    
    static func == (lhs: NotificationCellModel, rhs: NotificationCellModel) -> Bool {
        
        return lhs.model.id == rhs.model.id
    }
    
    var identity: String {
        return model.id
    }
    var model: NotificationModel
    var cellIdentifier = "NotificationTableViewCell"
}

then the actual model:

struct NotificationModel: Codable, Equatable {
    let body: String
    let title:String
    let id:String
}

And I would use that like this (in view controler):

private func observeTableView(){
        let dataSource = RxTableViewSectionedAnimatedDataSource<NotificationSectionModel>(
            configureCell: { dataSource, tableView, indexPath, item in
                if let cell = tableView.dequeueReusableCell(withIdentifier: item.cellIdentifier, for: indexPath) as? BaseTableViewCell{
                    cell.setup(data: item.model)
                    
                    return cell
                }
                return UITableViewCell()
            })
        
        notificationsViewModel.notifications
            .map{ notifications -> [NotificationCellModel] in
                return notifications.map{ NotificationCellModel( model: $0, cellIdentifier: NotificationTableViewCell.identifier) }
            }.map{ [NotificationSectionModel(model: "", items: $0)] }
            .bind(to: self.tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
    }

But how I would go with multiple sections, with different type of models/cells?


Solution

  • Here is a kind of worst case situation. You might be able to simplify this code depending on your use case:

    // MARK: Model Code
    struct ViewModel {
        let sections: Observable<[SectionModel]>
    }
    
    typealias SectionModel = AnimatableSectionModel<String, CellModel>
    
    enum CellModel: IdentifiableType, Equatable {
        case typeA(TypeAInfo)
        case typeB(TypeBInfo)
        
        var identity: Int {
            switch self {
            case let .typeA(value):
                return value.identity
            case let .typeB(value):
                return value.identity
            }
        }
        
        var cellIdentifier: String {
            switch self {
            case .typeA:
                return "TypeA"
            case .typeB:
                return "TypeB"
            }
        }
    }
    
    struct TypeAInfo: IdentifiableType, Equatable {
        let identity: Int
    }
    
    struct TypeBInfo: IdentifiableType, Equatable {
        let identity: Int
    }
    
    // MARK: View Code
    class Example: UIViewController {
        var tableView: UITableView!
        var viewModel: ViewModel!
        let disposeBag = DisposeBag()
        
        private func observeTableView(){
            let dataSource = RxTableViewSectionedAnimatedDataSource<SectionModel>(
                configureCell: { _, tableView, indexPath, item in
                    guard let cell = tableView.dequeueReusableCell(withIdentifier: item.cellIdentifier, for: indexPath) as? BaseCell else { fatalError() }
                    cell.setup(model: item)
                    return cell
                })
            
            viewModel.sections
                .bind(to: tableView.rx.items(dataSource: dataSource))
                .disposed(by: disposeBag)
        }
    }
    
    class BaseCell: UITableViewCell {
        func setup(model: CellModel) { }
    }
    final class TypeACell: BaseCell { }
    final class TypeBCell: BaseCell { }