Anish
Anish

Reputation: 2927

UICollectionView databinding using RxSwift - iOS

I have a collectionview populated with data models. I am trying to update the bool property of the nested model when user taps on collectionview cell. In turn, the collectionview should reload and cell should be updated w.r.t to bool property. But the property changes in the model is not updating the collectionview.

//Model

struct MultiSelectionQuestionModel {
  var header: String
  var items: [Item]
}

extension MultiSelectionQuestionModel: SectionModelType {
  typealias Item = MultiSelectionAnswerModel

   init(original: MultiSelectionQuestionModel, items: [Item]) {
        self = original
        self.items = items
  }
}

struct MultiSelectionAnswerModel {
    var text: String
    var isSelected: Bool = false //property to be updated
    var cellType: CellType = .CustomType
}

//CollectionView methods

func populateCells() {
     let dataSource = RxCollectionViewSectionedReloadDataSource
                    <MultiSelectionQuestionModel>(
                configureCell: { (_, collectionView, indexPath, item) in
                    guard let cell = collectionView
                        .dequeueReusableCell(withReuseIdentifier: item.cellType.rawValue, for: indexPath) as? MultiSelectionBaseCell else {
                        return MultiSelectionBaseCell()
                    }
                    cell.configure(item: item)
                    return cell
                })

    //handle collectionview cell tap

    collectionView.rx.itemSelected.asObservable().map { (indexPath) -> Result in
        //This method is called to update `isSelected` property. Once `isSelected` is updated. I am expecting the collectionview to reload and update the cell.
        self.viewModel.toggleItemSelected(indexPath: indexPath)
    }
    collectionView.rx.setDelegate(self).disposed(by: disposeBag)

    viewModel.items
            .bind(to: collectionView.rx.items(dataSource: dataSource))
                  .disposed(by: disposeBag)
}

//ViewModel

struct MultiSelectionCollectionViewModel {
    var items: BehaviorRelay<[MultiSelectionQuestionModel]> = BehaviorRelay(value: [])
    var delegate:
    init(questions: BehaviorRelay<[MultiSelectionQuestionModel]>) {
        self.items = questions
    }

    //This method is called to update `isSelected` property. Once `isSelected` is updated. I am expecting the collectionview to reload and update the cell.
    func toggleItemSelected(indexPath: IndexPath) {
        let item = self.items.value[indexPath.section]
        if let options = item.items as? [MultiSelectionAnswerModel] {
            var optionItem = options[indexPath.row]
            optionItem.isSelected = true // Collectionview reload Not working. 
        } 
    }
}

I just started learning RxSwift. Any help is appreciated. Thanks

Upvotes: 1

Views: 1721

Answers (1)

Daniel T.
Daniel T.

Reputation: 33967

You have to call items.accept(_:) to push a new array out of your BehaviorRelay. In order to do that, you have to build a new array. Also, BehaviorRelays (any Relays or Subjects) should never be vars; they should always be lets.

Also, keep in mind that you can't actually modify the array in the relay. Instead you replace it with a new array.

This should work:

struct MultiSelectionCollectionViewModel {
    let items: BehaviorRelay<[MultiSelectionQuestionModel]>

    init(questions: BehaviorRelay<[MultiSelectionQuestionModel]>) {
        self.items = questions
    }

    //This method is called to update `isSelected` property. Once `isSelected` is updated. I am expecting the collectionview to reload and update the cell.
    func toggleItemSelected(indexPath: IndexPath) {
        var multiSelectionQuestionModel = items.value // makes a copy of the array contained in `items`.
        var item = multiSelectionQuestionModel[indexPath.section].items[indexPath.row] // makes a copy of the item to be modified
        item.isSelected = true // modifies the item copy
        multiSelectionQuestionModel[indexPath.section].items[indexPath.row] = item // modifies the copy of items by replacing the old item with the new one
        items.accept(multiSelectionQuestionModel) // tells BehaviorRelay to update with the new array of items (it will emit the new array to all subscribers.)
    }
}

protocol SectionModelType { }

enum CellType {
    case CustomType
}

struct MultiSelectionQuestionModel {
    var header: String
    var items: [Item]
}

extension MultiSelectionQuestionModel: SectionModelType {
    typealias Item = MultiSelectionAnswerModel

    init(original: MultiSelectionQuestionModel, items: [Item]) {
        self = original
        self.items = items
    }
}

struct MultiSelectionAnswerModel {
    var text: String
    var isSelected: Bool = false //property to be updated
    var cellType: CellType = .CustomType
}

Upvotes: 1

Related Questions