使用 RxSwift 从 UITableViewCell 发送回调到 UIViewController

Posted

技术标签:

【中文标题】使用 RxSwift 从 UITableViewCell 发送回调到 UIViewController【英文标题】:Sending callback using RxSwift from UITableViewCell to UIViewController 【发布时间】:2019-09-13 04:14:00 【问题描述】:

我有一个 UITableViewCell,其中有 customContentView,我想向 viewController 发送回调。为此,我正在使用 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()
        
    


在上面的代码中,我在 awakeFromNib 方法中创建了 Observable,并在那里初始化了观察者。调用 didSelectCell 方法后,我将在观察者中传递单元格和 bool 的索引。

如果我不想使用闭包和委托,我只是想知道这是否是实现此目标的正确方法。

【问题讨论】:

【参考方案1】:

如果您有兴趣,这里有一个更完整的答案。我看到@Erumaru 警告过您多次打印单元格。如果您有足够多的表单项导致某些单元格被重复使用并且您没有正确处理,那么请记住这一点。

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()
        
    

【讨论】:

感谢这个看起来很干净,然后我做了什么。 你能回答这个问题吗? ***.com/questions/58050998/… 在上面的答案中,tapGesture & selectionImageView 不会造成内存泄漏,因为它们之间有很强的引用关系? 为什么你认为tapGestureselectionImageView 有很强的引用?代码中没有任何内容表明它。【参考方案2】:

在您的单元格中创建disposeBag。然后,在prepareForReuse 中重置它。

cell.cellSelected?.subscribe  (event) in
    let index = event.element!.0
    let isSelected = event.element!.1
.disposed(by: cell.disposeBag)

在您的情况下,经过一些滚动后,每个单元格将被多次订阅。这应该会带来一些魔力:)


一般来说,您只会订阅可见的单元格,并且不会发生内存泄漏或过度计算。对我来说似乎没问题。

【讨论】:

感谢@Erumaru 的到来,所以在 UIViewController 中保留 DisposeBag 会造成内存泄漏吗? @AnirudhaMahale 不仅,您在滚动后检查过您的打印件吗?它应该为一个单元格打印多次。 不,我不行,我会检查的。 是的,我检查了它没有打印多次。 @AnirudhaMahale 嗯,我有同样的情况和这样的问题。但是,不要介意轮到你时它是否可以正常工作。我会在这里留下我的答案,希望将来对知道的人有所帮助:)

以上是关于使用 RxSwift 从 UITableViewCell 发送回调到 UIViewController的主要内容,如果未能解决你的问题,请参考以下文章

如何从使用 RxSwift 返回 Observable 的服务中获取值

使用 RxSwift 从 ViewController 向 ViewModel 传递值

使用 rxSwift 中的 tableView 单元将数据从视图模型传递到视图控制器

可从多个 BehaviorRelay RxSwift 观察到

如何使用 RxSwift 处理应用程序状态

使用 RxSwift 定期更新