RxSwift modify tableview cell on select
Asked Answered
C

3

18

I have a table view in my app. I generated the datasource for this table using following code

struct ContactNameNumberBlockStatus {
    var contactThumbnail: Data?
    var contactName : String
    var contactNumber: String
    var blockStatus : Bool
}

class BlockListTableViewCell: UITableViewCell {
    @IBOutlet weak var contactImage: UIImageView!
    @IBOutlet weak var contactName: UILabel!
    @IBOutlet weak var contactNumber: UILabel!
    @IBOutlet weak var blockButton: UIButton!
    var eachCell : ContactNameNumberBlockStatus! {
        didSet {
            // setting ui
        }
    }
}

private func showTableContent(data :   Observable<[ContactNameNumberBlockStatus]>) {
        data.bindTo(tableView.rx.items(
            cellIdentifier: "BlockListTableViewCell")) {
            row, contributor, cell in
            if let cell2 = cell as? BlockListTableViewCell {
                cell2.eachCell = contributor
            }
            }.addDisposableTo(disposeBag)
}

Now when I tap on cell I want to update ui by showing/hiding blockButton mentioned in top

how to do this ??

before using rx i used the didSelectRowAt of table view as following

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        contacts[indexPath.row].blockStatus = false
        self?.tableView.reloadData()
}

I found that tableView.rx.itemSelected is same as above didSelectRowAt but i cant find how i can update the table view using following code

tableView.rx.itemSelected
  .subscribe(onNext: { [weak self]indexPath in

  }).addDisposableTo(disposeBag)

So how to update the cell?

Contractor answered 5/2, 2017 at 8:39 Comment(2)
Did you try to change your model and reload table view in .subscribe(onNext: {}) method?Pecan
i cant change the model because its let typeContractor
U
76

You can gain access to the cell like this

tableView.rx.itemSelected
  .subscribe(onNext: { [weak self] indexPath in
    let cell = self?.tableview.cellForRow(at: indexPath) as? SomeCellClass
    cell.button.isEnabled = false
  }).addDisposableTo(disposeBag)
Unlike answered 6/2, 2017 at 13:32 Comment(2)
Welcome to stack overflow :-) Please look at How to Answer. You should provide some information why your code solves the problem. Code-only answers aren't useful for the community.Laurice
Upvoted the answer only because I hate haters (even if they use kind words).Gwenn
R
3

//to get access to model

            tableView.rx.modelSelected(Item.self)
            .subscribe(onNext: { [weak self] model in
                guard let self = self else { return }
                self.selectedItem = model
            }).disposed(by: disposeBag)
            

//to get access to indexPath

         eg: var names = ["A", "B", "C"]
                tableView.rx.itemSelected
                    .subscribe(onNext: { [weak self] indexPath in
                        guard let self = self else { return }
                        self.selectedName = self.names[indexPath.row]
                        self.performSegue(withIdentifier: "ItemDetail", sender: self)
                    }).disposed(by: disposeBag)

You can have both in the file if you want to get a handle of the model as well as the index path

Ruffina answered 18/10, 2020 at 23:27 Comment(0)
S
3

I'm not a big fan of the accepted answer because in that answer, there is no model that keeps track of the block state. Instead it just disables the button. It's important to have the state of your Views follow the Models and the accepted answer ignores that.

The following is obviously more complex, but it sets up the model and makes the state of the view reflect the underlying model instead of avoiding the model.

enum Input {
    case reset([ContactNameNumberBlockStatus])
    case blockTapped(UUID)
}

struct State {
    var order: [UUID] = []
    var values: [UUID: ContactNameNumberBlockStatus] = [:]
}

let taps = PublishSubject<UUID>()

let state = Observable.merge(
    contacts.map { Input.reset($0) },
    taps.map { Input.blockTapped($0) }
)
.scan(into: State()) { (state, input) in
    switch input {
    case .reset(let contacts):
        state.order = contacts.map { _ in UUID() }
        state.values = Dictionary.init(zip(state.order, contacts), uniquingKeysWith: { lhs, _ in lhs })
    case .blockTapped(let uuid):
        state.values[uuid]!.blockStatus = true
    }
}

state
    .map { $0.order }
    .bind(to: tableView.rx.items(cellIdentifier: "BlockListTableViewCell", cellType: BlockListTableViewCell.self)) { row, uuid, cell in
        let cellState = state.compactMap { $0.values[uuid] }
        cell.disposeBag.insert(
            cellState.map { $0.contactThumbnail.flatMap { UIImage(data: $0) } }.bind(to: cell.contactImage.rx.image),
            cellState.map { $0.contactName }.bind(to: cell.contactName.rx.text),
            cellState.map { $0.contactNumber }.bind(to: cell.contactNumber.rx.text),
            cellState.map { $0.blockStatus }.bind(to: cell.blockButton.rx.isHidden),
            cell.blockButton.rx.tap.map { uuid }.bind(to: taps)
        )

    }
    .disposed(by: disposeBag)

The above code sets up a state machine that keeps track of users and updates the user model as necessary, which in turn causes the view to update. It's much more extensible; when a new user input is discovered, just add a case to the Input type. Also, when new state is discovered, that can just be added to the State type.

The code also has an added benefit; updating the state of an item in the model does not cause the entire table view to reload. Only that cell will be changed.

Samuele answered 19/10, 2020 at 11:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.