当有人问:详情页的数据变了,列表页面的对应UI如何同步修改?

4,614 阅读5分钟

废话开篇:最近写了一段 ApiCloud 混合开发 APP,说是混合开发,但是大部分的功能还是以 JS 为主,其实,ApiCloud 跨平台的开发只能算是工具,而不能算是一门语言。那么,JS 本身是运行在 JS 环境中的,普通页面的 JS 运行在 WKWebView 对应的环境里。当然,也可以初始化一个 JS 运行环境,让一些自定义代码运行在这个脱离 webViewJS 运行环境里。但是,不管是以什么方式运行 JS,如果不在同一个环境下,那么,该环境下的 JS 数据在不借助桥接的情况下是不能共享的,所以,就会有人好奇,原生开发下,详情页的数据变了,列表页面的对应UI如何同步修改?

一、JS模式下,详情页的数据变了,列表页面的对应UI如何同步修改?

JS 的运行及访问界限被 WKWebView 限制住了,如果不通过原生的多 WebView 之间的通信辅助是无法做到数据共享的。

二、iOS模式下,为什么详情页的数据变了,列表页面的对应UI可以同步修改?

原生的所有的值、对象、方法,都在同一个运行环境里,如果指针传的足够远,那么,程序将会在任何地方访问或调用某些对象或方法。

三、iOS模式下,如何做到详情页的数据变了,列表页面的对应UI可以同步修改?

实现方法其实有很多种:

1、代理回执;

2、闭包回执;

3、通知回执;

4、对象观察者回执;

上面所有方法的思路,其实都是通过“一根线”链接“两个点”,只是实现的方式不一样而已。

上面最好的方式还是通过对象观察者进行回执修改,这个就好比大家在过斑马线,每个人都盯着信号灯,一旦绿灯亮起,那么,大家就动起来;反之,其他的方式就类似于,张三看见绿灯亮了,告诉李四,李四告诉王五...

四、iOS模式下,简单实现

3.1 实现效果

详情页的修改“点赞”数,上一级的列表“点赞”展示数也随之变化

屏幕录制2022-06-30 上午9.33.46.gif

3.2 实现思路

利用 RxSwift 观察者进行绑定

将对象的“点赞”属性分别绑定到 Cell 上面的 UILable 和详情页里的 UILable 上。那么,不管在个代码下修改了“点赞”属性值,那么,绑定的UI都会作出更改。

3.3 代码展示

这里说一下 RxSwift 使用过程中的注意点:

新闻对象类

class NewsClass : NSObject, HandyJSON{
    @objc dynamic var thumb : Int = 0
    @objc dynamic var name : String?
    @objc dynamic var url : String?
    @objc dynamic var mineThumbStatus : Bool = false

    required override init(){

    }

    //绑定数据
    func rxBindContent(_ cell : WSLNewsListTableViewCell){
        self.rx.observeWeakly(Int.self, "thumb").asObservable().bind(to: cell.rx.thumbLab).disposed(by: cell.disposeBag)
        self.rx.observeWeakly(String.self, "name").asObservable().bind(to: cell.rx.nameLab).disposed(by: cell.disposeBag)
        self.rx.observeWeakly(String.self, "url").asObservable().bind(to: cell.rx.coverImg).disposed(by: cell.disposeBag)
    }
}

注意点1:对象需继承 NSObject,以沿用 rx 系列的扩展方法。

注意点2:对于用 RxSwiftrx.observeWeakly 观察的对象属性需要用 @objc dynamic 修饰,赋予属性动态特性。

注意点3:在绑定数据方法里,利用 bind 方法将观察序列绑定在了自定义 cellrx 相关扩展下。

把赋值及其他相关的操作放在这里,外部进进行绑定

//MARK: -扩展cell的rx属性
extension Reactive where Base : WSLNewsListTableViewCell {

    var thumbLab : Binder<Int?> {
        return Binder(self.base){ cell ,content  **in**
            cell.thumbLab.text = "点赞数:" + String(describing: content!)
            cell.thumbLab.sizeToFit()
        }
    }

    var nameLab : Binder<String?> {
        return Binder(self.base){ cell ,content in
            cell.nameLab.text = content!
            cell.nameLab.sizeToFit()
        }
    }

    var coverImg : Binder<String?> {
        return Binder(self.base) { cell , url in
            cell.coverImgView.kf.setImage(with: URL(string: url ?? ""))
        }
    }
}

注意点4:这里的绑定方法是在 UITableViewDataSource 方法里,那么,重用机制自然会导致多次绑定。

cell 的绑定赋值操作

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : WSLNewsListTableViewCell = tableView.dequeueReusableCell(withIdentifier: "CELL", for: indexPath) as! WSLNewsListTableViewCell
        //进行数据绑定
        models?[indexPath.row].rxBindContent(cell)
        //点击事件
        cell.enterDetailBtn.rx.controlEvent(.touchUpInside).subscribe {[weak self] btn in
            self?.pushDetailNewsVCSubject.onNext(self?.models?[indexPath.row])
        }.disposed(by: cell.disposeBag)
        return cell
    }

model 的绑定事件

func rxBindContent(_ cell : WSLNewsListTableViewCell){
    self.rx.observeWeakly(Int.self, "thumb").asObservable().bind(to: cell.rx.thumbLab).disposed(by: cell.disposeBag)
    self.rx.observeWeakly(String.self, "name").asObservable().bind(to: cell.rx.nameLab).disposed(by: cell.disposeBag)
    self.rx.observeWeakly(String.self, "url").asObservable().bind(to: cell.rx.coverImg).disposed(by: cell.disposeBag)
}

解决复用机制重复绑定数据的办法就是绑定数据的序列销毁关联 cell.disposeBag,在 cellprepareForReuse 方法里进行 cell.disposeBag 的重置。

这里说一下 disposed 意义:意为销毁操作。在日常的开发中,会创建很多序列,但是,它们的生命周期交给谁处理呢?为了处理这个问题就出现了 disposed(by: disposeBag) 来辅助,把所有的序列关联到 disposeBag 上,那么,当 disposeBag 对象在某个时机被销毁的时候,同时销毁它关联的所有序列。

cell 复用前进行 disposeBag 重置

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

这样就保证了,屏幕上可见的 cell 才具有绑定效果。

注意点5:在详情页修改传入参数的属性值。

btn.rx.tap.subscribe {[weak self] _ in
    self?.newsClass?.thumb += 1
}.disposed(by: disposeBag)

这样就完成了详情页修改属性,列表页面UI同步修改。

五、model 对象用 struct 类型的如何处理?

很遗憾,结构体属于值类型,在方法传入的时候会有一个副本。在逃逸闭包里,结构体更是会被复制出来,那么,如果不能操作原数据的话,扩展还有什么意义呢?

聪明的掘友会想到:是不是可以用 ReplaySubject 进行数据传递呢?因为 ReplaySubject 属于对象,即使结构体采用副本复制的方式传值,但是,对于对象类型的“属性”,复制的也是对象地址,那么,是不是可以进行直接 UI 绑定操作呢?答案是可以的! 但是这样的操作没有办法改变列表页的结构体数据,所以,不能进行数据同步,只能做到 UI 显示同步。

总结与思考:掘金大神很多,但是这篇小总结,显然不是给大神看的[抱拳][抱拳][抱拳]。但掘金里喜欢分享跟学习的小伙伴也很多,希望这篇小总结,对大家能起到帮助[抱拳][抱拳][抱拳]。