iOS开发列表选中问题排查解决

在iOS开发中,列表View结合Rxswfit使用时,需要注意管理cell中rx变量的订阅者的生命周期,避免订阅混乱情况发生。

问题

iOS中,使用UICollectionView实现下面的颜色选择面板,当网格布局的cell数量超出一屏数量后,会复用cell,选中某个cell,会出现多个cell也被选中的情况。

背景

使用UICollectionView加载一个网格列表,并支持选中某个item,选中状态由item.isSelected控制

swift 复制代码
// UIController

        let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)

        collectionView.backgroundColor = .white

        collectionView.showsVerticalScrollIndicator = false

        collectionView.bounces = false

        collectionView.contentInset = UIEdgeInsets(top: 12, left: 16, bottom: 0, right: 16)

        collectionView.register(MyCell.self, forCellWithReuseIdentifier: "cell")

  


        items.bind(to: collectionView.rx.items(cellIdentifier: "cell", cellType: MyCell.self)) { row, item, cell in

            cell.item = item

        }

        .disposed(by: self.bag)

  


  


// 这里绑定了每个cell的点击事件

        collectionView.rx.modelSelected(MyCell.self)

            .subscribe(onNext: { [weak self] item in

                guard let `self` = self else { return }

                if item.isSelected.value == true { return }

                self.foreColorItems.forEach { _item in

                    _item.isSelected.accept(false)

                }

                item.isSelected.accept(true)

                if let chooseClosure = self.chooseClosure {

                    chooseClosure(item.type, true)

                }

            })

            .disposed(by: self.bag)

  


// Item:

class Item {

    

    var type: String = ""

    var isSelected = BehaviorRelay(value: true)

    

    convenience init(type: String, isSelected: Bool) {

        self.init()

        self.type = type

        self.isSelected.accept(isSelected)

    }

}

  


  


  

Item的isSelected由BehaviorRelay管理监听,而Cell中设置item绑定到视图逻辑中,item.isSelected被声明为rxSwfit的被观察者,当isSelected值改变,会调用观察者,也就是执行 map { !$0 }.bind(),改变sImageView的isHidden。

swift 复制代码
//MyCell

  


    private var bag = DisposeBag()

    var item = CJQEditorFormatItem() {

        didSet {

            centerView.backgroundColor = UIColor(hexString: item.type.backgroundColor)

            

//            bag = DisposeBag()

            

            item.isSelected.asObservable()

                .map { !$0 }

                .bind(to: self.sImageView.rx.isHidden)

                .disposed(by: bag)

            

            sImageView.image = UIImage(named: (item.type == .foreColor_gray7 || item.type == .foreColor_gray8) ? "icon_keyboard_input_color_s_b" : "icon_keyboard_input_color_s_w")

        }

    }

分析

swift 复制代码
items.bind(to: collectionView.rx.items(cellIdentifier: "cell", cellType: MyCell.self))

{

     row, item, cell in

     cell.item = item

}

这里当复用的cell重新设置cell 的 item时,didSet中,item.isSelected又会添加一个新的观察着就是当前的cell,但是,但是item.isSelected没变,之前的cell观察者也没变,就导致了item.isSelected的观察者越来越多,改变item的isSelected,就会联动通知多个观察着cell,这里就必须要注销之前的观察者。

那么iOS 中的rxswfit 如何注销观察者。在其他语言中,可以通过显式的函数调用,来注销,但这里需要通过dispose对象的释放来注销,也就是将原来的dispose置为null,初始化一个新的。

这样就实现注销了以前的观察者,解决了点击一个cell会有多个cell响应的问题。

swift 复制代码
//MyCell

    private var bag = DisposeBag()

    var item = CJQEditorFormatItem() {

        didSet {

            centerView.backgroundColor = UIColor(hexString: item.type.backgroundColor)

  


            // 注销之前的Observer

             bag = DisposeBag()

            

            item.isSelected.asObservable()

                .map { !$0 }

                .bind(to: self.sImageView.rx.isHidden)

                .disposed(by: bag)

            

            sImageView.image = UIImage(named: (item.type == .foreColor_gray7 || item.type == .foreColor_gray8) ? "icon_keyboard_input_color_s_b" : "icon_keyboard_input_color_s_w")

        }

    }

知识点

1、UICollectionViewCell、UICollectionReusableView等视图的类或者nib文件,使用register方式可快速实现注册复用cell逻辑。

2、items.bind使用了RxSwift来管理数据源绑定。这种写法通过RxSwift的Observable和bind方法来简化数据和UI的绑定。

RxSwift的写法可以减少大量的样板代码,并且使得代码更具声明性和响应式。

3、在RxSwift中,DisposeBag 是一个用于管理和释放资源的工具。它的设计目的是为了方便地管理订阅(subscriptions)的生命周期。在DisposeBag对象引用计数为0(销毁)时,会自动的取消所有持有的订阅。

4、在cell的数据源赋值时,需要注意rx类的被订阅者的订阅管理,需要在适时通过释放disposebag来销毁所有的订阅者,重新订阅绑定到当前的item,否则会出现多个item响应的情况。

相关推荐
sweet丶2 小时前
SDWebImage深度解析:高效图片加载背后的架构设计与卓越实践
ios·图片资源
雪碧聊技术2 小时前
前端项目代码发生改变,如何重新部署到linux服务器?
前端·vue3·centos7·代码更新,重新部署
liulilittle3 小时前
C++ 浮点数封装。
linux·服务器·开发语言·前端·网络·数据库·c++
wordbaby3 小时前
Expo 进阶指南:赋予 TanStack Query “原生感知力” —— 深度解析 AppState 与 NetInfo
前端·react native
Moment3 小时前
从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然 🤔🤔🤔
前端·后端·面试
天问一3 小时前
使用 Vue Router 进行路由定制和调用的示例
前端·javascript·vue.js
韩立学长5 小时前
【开题答辩实录分享】以《基于Vue的非遗文化知识分享平台的设计与实现》为例进行选题答辩实录分享
前端·javascript·vue.js
优弧5 小时前
离开舒适区100天,我后悔了吗?
前端·后端·面试
胡gh5 小时前
css的臂膀,前端动效的利器,还是布局的“隐形陷阱”?
前端·css·html
灵感菇_5 小时前
Flutter Riverpod 完整教程:从入门到实战
前端·flutter·ui·状态管理