gasparuff
gasparuff

Reputation: 2295

UITableViewCell containing a UICollectionView using RxSwift

I started using RxSwift in my iOS project and I have a UITableView with a custom UITableViewCell subclass. Within that subclass, I have a UICollectionView.

Populating the tableview using RxSwift works pretty flawlessly, I'm using another extension of RxSwift for that (RxDataSources)

Here's how I'm doing it:

    self.dataSource = RxTableViewSectionedReloadDataSource<Section>(configureCell: {(section, tableView, indexPath, data)  in
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCellWithCollectionView

        switch indexPath.section {
        case 0:
            cell.collectionViewCellNibName = "ContactDataItemCollectionViewCell"
            cell.collectionViewCellReuseIdentifier = "contactDataItemCellIdentifier"
        case 1, 2:
            cell.collectionViewCellNibName = "DebtCollectionViewCell"
            cell.collectionViewCellReuseIdentifier = "debtCellIdentifier"
        default:
            break
        }
        cell.registerNibs(indexPath.section, nativePaging: false, layoutDirection: indexPath.section != 0 ? .horizontal : .vertical)

        let cellCollectionView = cell.collectionView!

        data.debts.asObservable().bind(to: cellCollectionView.rx.items(cellIdentifier: "debtCellIdentifier", cellType: DebtCollectionViewCell.self)) { row, data, cell in
            cell.setup(debt: data)
        }


        return cell

    })

This actually works. But the problem arises when a tableview cell gets scrolled off the screen and reappears. This triggers the code block from above and lets the app crash when

    data.debts.asObservable().bind(to: cellCollectionView.rx.items(cellIdentifier: "debtCellIdentifier", cellType: DebtCollectionViewCell.self)) { row, data, cell in
        cell.setup(debt: data)
    }

is invoked twice on the same tableview cell (The funny thing is, even Xcode crashes without any trace).

What can I do to avoid this?

EDIT:

I have found a solution, but I must admit that I'm not really happy with it... Here's the idea (tested and works)

I define another Dictionary in my class:

var boundIndizes = [Int: Bool]()

and then I make an if around the binding like this:

if let bound = self.boundIndizes[indexPath.section], bound == true {
    //Do nothing, content is already bound
} else {
    data.debts.asObservable().bind(to: cellCollectionView.rx.items(cellIdentifier: "debtCellIdentifier", cellType: DebtCollectionViewCell.self)) { row, data, cell in
        cell.setup(debt: data)
        }.disposed(by: self.disposeBag)
    self.boundIndizes[indexPath.section] = true
}

But I cannot believe there is no "cleaner" solution

Upvotes: 2

Views: 2399

Answers (1)

Timofey Solonin
Timofey Solonin

Reputation: 1403

The issue is you are binding your data.debts to the collectionView within the cell every time the cell is dequeued.

I would recommend you move the logic that is related to your data.debts to the cell itself and declare a var disposeBag: DisposeBag which you reinstantiate on prepareForReuse. See this answer for reference.

Upvotes: 1

Related Questions