如何将`Observable`转换为`ControlEvent`

时间:2018-10-25 14:56:57

标签: ios swift rx-swift

我们有一个视图控制器MainCollectionView,其中包含具有多个单元格FooCell的集合视图。在每个FooCell中,都有一个收集视图和一个单元格BarCell的列表。

如何在BarCellMainCollectionView中传播按钮点击事件?

这就是我们拥有的:

class FooCell: ... {

    private let barCellButtonTappedSubject: PublishSubject<Void> = PublishSubject<Void>()
    var barCellButtonTappedObservable: Observable<Void> {
        return barCellButtonTappedSubject.asObserver()
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeue(...)

        if let cell = cell as BarCell {
            cell.button.rx.tap.bind { [weak self] in
                self?.barCellButtonTappedSubject.onNext(())
            }.disposed(by: cell.rx.reusableDisposeBag)
        }

        return cell
    }
}

class MainCollectionView: ... {

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeue(...)

        if let cell = cell as FooCell {
            cell.barCellButtonTappedObservable.subscribe { [weak self] in
                // Do other stuff when the button inside bar cell is tapped.
            }.disposed(by: cell.rx.reusableDisposeBag)
        }

        return cell
    }
}

在我读到有关ControlEvent之前,这一直有效:

  • 它永远不会失败
  • 它不会在订阅时发送任何初始值
  • 它将在取消控制权后完成序列
  • 它永远不会出错
  • 它在MainScheduler.instance上传递事件

ControlEvent中使用FooCell似乎更合适:

private let barCellButtonTappedSubject: PublishSubject<Void> = PublishSubject<Void>()
var barCellButtonTappedObservable: Observable<Void> {
    return barCellButtonTappedSubject.asObserver()
}

将此barCellButtonTappedObservable转换为ControlEvent的正确方法是什么?还是有其他更好的主意将嵌套单元格中的ControlEvent传播到外部视图控制器?

3 个答案:

答案 0 :(得分:1)

我个人更喜欢将RxAction用于此类操作,但是由于您已经在单元格中声明了PublishSubject<Void>,因此可以将主题转换为ControlEvent

controlEvent = ControlEvent<Void>(events: barCellButtonTappedSubject.asObservable())

尽可能直截了当!但是,如果您只想这样做,那么您甚至不需要barCellButtonTappedSubject

controlEvent = ControlEvent<Void>(events: cell.button.rx.tap)

实际上,您甚至不需要声明控制事件:),因为cell.button.rx.tap本身是一个控制事件:)因此,如果您在单元格中将按钮声明为公共属性,则可以直接访问它在tableView控制器中点击控制事件

但就我个人而言,我会使用RxAction而不是声明publishSubject的{​​{1}}或controlEvent可以要求您的FooCell采取行动

TableViewController

最后,您的TableViewController / CollectionViewController可以通过

传递操作
class FooCell: ... {
   var cellTapAction : CocoaAction! = nil

   func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeue(...)

        if let cell = cell as BarCell {
            cell.button.rx.action = cellTapAction
        }

        return cell
    }
}

您只需要处理是否将cellctionView嵌入class MainCollectionView: ... { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeue(...) if var cell = cell as FooCell { cell.cellTapAction = CocoaAction { _ -> Observable<Void> in debugPrint("button in cell tapped") return Observable.just(()) } } return cell } } 内,因为在FooCell嵌入collectionView传递之后传递action甚至可能在传递动作之前加载,因此您将调整逻辑,以便在将操作传递给deQueReusableCell之后重新加载嵌入式集合视图,或解决此问题的其他解决方法:)

希望它会有所帮助:)我相信使用FooCell可使代码更简洁,更易于理解。

答案 1 :(得分:1)

我从未使用过其他答案中提到的Action。我也想知道为什么您似乎是在手动设置您的委托而不是使用RxCocoa来完成。最后,感觉您可能想要某种方式来了解哪个按钮被点击。在下面的代码中,我为每个Bar单元分配了一个ID整数。

class BarCell: UICollectionViewCell {

    @IBOutlet weak var button: UIButton!

    func configure(with viewModel: BarViewModel) {
        button.rx.tap
            .map { viewModel.id }
            .bind(to: viewModel.buttonTap)
            .disposed(by: bag)
    }

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

    private var bag = DisposeBag()
}

class FooCell: UICollectionViewCell {

    @IBOutlet weak var collectionView: UICollectionView!

    func configure(with viewModel: FooViewModel) {
        viewModel.bars
            .bind(to: collectionView.rx.items(cellIdentifier: "Bar", cellType: BarCell.self)) { index, element, cell in
                cell.configure(with: element)
        }
        .disposed(by: bag)
    }

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

    private var bag = DisposeBag()
}

class MainCollectionView: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    var viewModel: MainViewModel!

    override func viewDidLoad() {
        super.viewDidLoad()

        let foos = viewModel.foos
            .share()

        let buttonTaps = foos
            .flatMap { Observable.merge($0.map { $0.bars }) }
            .flatMap { Observable.merge($0.map { $0.buttonTap.asObservable() }) }

        buttonTaps
            .subscribe(onNext: {
                print("button \($0) was tapped.")
            })
            .disposed(by: bag)

        foos
            .bind(to: collectionView.rx.items(cellIdentifier: "Foo", cellType: FooCell.self)) { index, element, cell in
                cell.configure(with: element)
            }
            .disposed(by: bag)
    }

    private let bag = DisposeBag()
}

struct FooViewModel {
    let bars: Observable<[BarViewModel]>
}

struct BarViewModel {
    let id: Int
    let buttonTap = PublishSubject<Int>()
}

struct MainViewModel {
    let foos: Observable<[FooViewModel]>
}

关于代码最有趣的一点是所有buttonTaps的合并。找出来有点冒险。 :-)

答案 2 :(得分:0)

在尝试观察按钮的状态之前,我会亲自向按钮添加一个操作,然后从那里处理您的响应。

class Cell: UICollectionViewCell {
   let button = UIButton(type: .custom)

   func setUpButton() {
      button.addTarget(self, action: #selector(Cell.buttonTapped), for: .touchUpInside)
   }

   @IBAction func buttonTapped(sender: UIButton) {
      //This runs every time the button is tapped
      //Do something here to notify the parent that your button was selected or handle it in this Cell.
      print(sender.state)
   }
}