You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 22:54:01