RxSwift如何在主VC/主ViewModel中处理CollectionView Cell的点击交互事件
实现方案
你可以通过给Cell定义统一的交互事件流,把所有内部操作抛到外层处理,后续新增交互只需要加事件枚举项和Cell内的绑定逻辑即可,不需要改动外层架构。
步骤1:定义Cell交互事件枚举
统一管理Cell所有可能的交互操作,方便后续扩展:
enum PostsPagingCellEvent { case avatarTapped(Post) // 头像点击,携带当前对应Post数据 case likeButtonTapped(Post) // 后续新增事件直接加在此处 case commentButtonTapped(Post) case shareButtonTapped(Post) }
步骤2:修改Cell代码,添加事件发射逻辑
在Cell内部新增事件发射的Subject,绑定控件操作把对应事件抛出:
class PostsPagingCollectionViewCell: UICollectionViewCell { // 对外暴露的事件流,外层只需订阅这个属性即可拿到所有Cell交互 let cellEvent = PublishSubject<PostsPagingCellEvent>() private let disposeBag = DisposeBag() var post: Post! { didSet { bindViewModel() } } override func bindViewModel() { guard let viewModel = cellViewModel as? PostsPagingCellViewModel, let cellView = cellView as? PostsPagingCellView else { return } let _post = BehaviorRelay<Post>(value: post) let outputs = viewModel.transform(input: .init(post: _post.asObservable())) outputs.fullName.drive(cellView.fullNameLabel.rx.text).disposed(by: disposeBag) outputs.nickname.drive(cellView.nickNameLabel.rx.text).disposed(by: disposeBag) // 绑定头像点击事件,抛出对应事件 cellView.avatarButton.rx.tap .map { PostsPagingCellEvent.avatarTapped(self.post) } .bind(to: cellEvent) .disposed(by: disposeBag) // 后续新增其他按钮交互,直接补充绑定即可,示例: // cellView.likeButton.rx.tap // .map { PostsPagingCellEvent.likeButtonTapped(self.post) } // .bind(to: cellEvent) // .disposed(by: disposeBag) } }
步骤3:主VC层订阅事件,统一处理
根据你的需求可以选择直接在主VC处理跳转,或者把事件转发到主ViewModel统一处理业务逻辑:
方案A:主VC直接处理(适合轻量跳转场景)
let outputs = viewModel.transform(input: inputs) outputs.posts.drive(view.collectionView.rx.items(cellIdentifier: "postsPagingCollectionViewCell", cellType: PostsPagingCollectionViewCell.self)) { [weak self] row, post, cell in cell.post = post // 订阅当前Cell的交互事件 cell.cellEvent .subscribe(onNext: { event in guard let self = self else { return } switch event { case .avatarTapped(let post): // 执行跳转用户主页逻辑 let userProfileVC = UserProfileViewController(userId: post.userId) self.navigationController?.pushViewController(userProfileVC, animated: true) case .likeButtonTapped(let post): // 点赞操作,也可以直接调用主VM的方法处理 self.viewModel.likePost(postId: post.id) case .commentButtonTapped(let post): // 跳转评论页 let commentVC = CommentViewController(postId: post.id) self.navigationController?.pushViewController(commentVC, animated: true) } }) .disposed(by: cell.disposeBag) // 必须用Cell自身的disposeBag,复用的时候自动销毁旧订阅 }.disposed(by: disposeBag)
方案B:事件转发到主ViewModel处理(适合有复杂业务逻辑的场景)
首先修改主ViewModel的Input结构,接收统一的Cell事件流:
struct MainViewModelInput { // 原有其他输入参数 let cellEvent: Observable<PostsPagingCellEvent> }
主VC绑定的时候把所有Cell的事件合并传入主VM:
// 统一接收所有Cell事件的Subject let allCellEvents = PublishSubject<PostsPagingCellEvent>() // 构造主VM输入 let inputs = MainViewModelInput( // 原有其他输入参数 cellEvent: allCellEvents.asObservable() ) let outputs = viewModel.transform(input: inputs) outputs.posts.drive(view.collectionView.rx.items(cellIdentifier: "postsPagingCollectionViewCell", cellType: PostsPagingCollectionViewCell.self)) { row, post, cell in cell.post = post // 把每个Cell的事件转发到统一的Subject cell.cellEvent .bind(to: allCellEvents) .disposed(by: cell.disposeBag) }.disposed(by: disposeBag)
主VM内部统一处理所有Cell交互:
func transform(input: MainViewModelInput) -> Output { // 处理Cell事件 input.cellEvent .subscribe(onNext: { event in switch event { case .avatarTapped(let post): // 统一处理埋点、权限校验等逻辑 Analytics.track(event: "avatar_click", params: ["post_id": post.id]) // 把需要跳转的指令通过Output抛给主VC执行 self.output.navigateToUserProfile.accept(post.userId) case .likeButtonTapped(let post): // 调用点赞接口、更新本地数据等业务逻辑 self.requestLikePost(postId: post.id) } }) .disposed(by: disposeBag) // 原有其他transform逻辑 return Output(/* 原有输出参数 */) }
注意:订阅Cell事件时必须使用Cell自身的disposeBag,不能用主VC的全局disposeBag。否则Cell复用时旧订阅不会自动销毁,会出现重复触发事件、数据错乱的问题。
内容的提问来源于stack exchange,提问作者Bobby Redjeans
相关产品推荐
相关产品推荐

