Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending callback using RxSwift from UITableViewCell to UIViewController

I have a UITableViewCell in which I have customContentView on tap of that I want to send a callback to the viewController. For this I am using RxSwift.

class GISThemeFormTableCell: UITableViewCell {

    @IBOutlet weak var customContentView: UIView!

    var index = -1

    var cellSelected: Observable<(Int, Bool)>?
    private var observer: AnyObserver<(Int, Bool)>?

    override func awakeFromNib() {
        super.awakeFromNib()

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.didSelectCell))
        customContentView.addGestureRecognizer(tapGesture)

        cellSelected = Observable<(Int, Bool)>.create { (observer) -> Disposable in
            self.observer = observer
            return Disposables.create()
        }
    }

    @objc private func didSelectCell() {
        let newImage = selectionImageView.image! == Images.uncheckedRound ? Images.checkedRound : Images.uncheckedRound
        selectionImageView.image = newImage
        observer?.onNext((index, selectionImageView.image! == Images.checkedRound))
    }
}

class GISFormListView: UIView {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch indexPath.section {
        case 0:
            let cell = tableView.dequeueReusableCell(withIdentifier: "GISThemeFormTableCell", for: indexPath) as! GISThemeFormTableCell
            cell.index = indexPath.row
            cell.formTitle.text = presenter.getFormName(indexPath.row)

            cell.cellSelected?.subscribe { (event) in
                let index = event.element!.0
                let isSelected = event.element!.1

                print(index, isSelected)

            }.disposed(by: disposeBag)

            return cell

        case 1:
            let cell = tableView.dequeueReusableCell(withIdentifier: "LoadingTableViewCell", for: indexPath) as! LoadingTableViewCell
            return cell

        default:
            return UITableViewCell()
        }
    }
}

In above code I have created Observable in awakeFromNib method and I have initialised observer over there. Once the didSelectCell method is called I am passing the index of the cell and bool in the observer.

I just wanted to know if this is a correct to way to achive this if I don't want to use closures and delegates.

like image 482
Anirudha Mahale Avatar asked Sep 13 '19 04:09

Anirudha Mahale


1 Answers

Here's a more complete answer in case you're interested. I see that @Erumaru warned you about cells getting printed multiple times. That will happen if you have enough form items that some cells get reused and you aren't disposing properly so keep it in mind.

class GISThemeFormTableCell: UITableViewCell {

    @IBOutlet weak var formTitle: UILabel!
    @IBOutlet weak var selectionImageView: UIImageView!
    @IBOutlet weak var customContentView: UIView!

    var disposeBag = DisposeBag()

    private var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)

    override func awakeFromNib() {
        super.awakeFromNib()
        customContentView.addGestureRecognizer(tapGesture)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        disposeBag = DisposeBag()
    }

    func configure(title: String, initial: Bool = false) -> Observable<Bool> {
        // you shouldn't be inspecting your views to figure out the state of your model. The code below avoids that by making the state its own thing.
        let state = tapGesture.rx.event
            .filter { $0.state == .ended }
            .scan(false) { current, _ in !current }
            .startWith(initial)

        state
            .map { $0 ? Images.checkedRound : Images.uncheckedRound }
            .bind(to: selectionImageView.rx.image)
            .disposed(by: disposeBag)

        return state
    }
}

class GISFormListView: UIView {

    var presenter: Presenter!

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch indexPath.section {
        case 0:
            let cell = tableView.dequeueReusableCell(withIdentifier: "GISThemeFormTableCell", for: indexPath) as! GISThemeFormTableCell
            // the cell doesn't need to know its row, because that information is needed here, and it's already here.
            cell.configure(title: presenter.getFormName(indexPath.row))
                .map { (indexPath.row, $0) }
                .subscribe(onNext: { index, isSelected in
                    print(index, isSelected)
                })
                .disposed(by: cell.disposeBag)

            return cell

        case 1:
            let cell = tableView.dequeueReusableCell(withIdentifier: "LoadingTableViewCell", for: indexPath) as! LoadingTableViewCell
            return cell

        default:
            return UITableViewCell()
        }
    }
}
like image 73
Daniel T. Avatar answered Oct 19 '22 15:10

Daniel T.