热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RxSwift+MJRefresh自动管理刷新状态

参照RxSwiftMJRefresh打造自动处理刷新控件状态这位大佬的文章,有点抄袭的味道。枚举首先定义一个有关刷新状态的枚举类型:可按照自己的需求添加

  参照 RxSwift + MJRefresh 打造自动处理刷新控件状态 这位大佬的文章,有点抄袭的味道。

枚举

  首先定义一个有关刷新状态的枚举类型:

/// 可按照自己的需求添加,由于我没有用到 mj_footer.beginRefreshing(),
/// 所以没有定义相关的枚举。
enum RefreshStatus {case nonecase beingHeaderRefreshcase endHeaderRefreshcase endFooterRefresh// 这个枚举由于在我项目中经常用到,所以我定一个关联值的枚举。// 项目中需要:// - 数据为空的时候隐藏 `mj_footer`,否则显示;// - 然后判断没有更多数据就调用 `endRefreshingWithNoMoreData()`// 否则 `endRefreshing()`case footerStatus(isHidden: Bool, isNoMoreData: Bool)
}

  无需过多纠结,后面会演示枚举如何使用。

BehaviorSubject

  接下来要介绍一个跟 RxSwift 有关的一个类型 BehaviorSubject,我们会在文章用到它。

  BehaviorSubject 向所有订阅者发布事件,并向新的订阅者提供最近(或最初)的值。

  怎么理解?来看看代码:

func addObserver(_ id: String) -> Disposable {return subscribe { print("Subscription:", id, "Event:", $0) }
}let disposeBag = DisposeBag()
let subject = BehaviorSubject(value: "?")subject.addObserver("1").disposed(by: disposeBag)
subject.onNext("?")
subject.onNext("?")subject.addObserver("2").disposed(by: disposeBag)
subject.onNext("?️")
subject.onNext("?️")/**
Subscription: 1 Event: next(?)
Subscription: 1 Event: next(?)
Subscription: 1 Event: next(?)
Subscription: 2 Event: next(?)
Subscription: 1 Event: next(?️)
Subscription: 2 Event: next(?️)
Subscription: 1 Event: next(?️)
Subscription: 2 Event: next(?️)
Subscription: 1 Event: next(?)
Subscription: 2 Event: next(?)
Subscription: 1 Event: next(?)
Subscription: 2 Event: next(?)
*/

  总结一下:BehaviorSubject 从上至下接收它发出的最新(原始值也属于发出的事件元素)值。并向新的订阅者提供最新值,所以这里我们 订阅2号 会接收到前面发出的最新元素,稍后才是 订阅2号 自己发出的元素事件。

  这里请允许我搬布官方的图例来说明一下:

  如你所见,紫灯的时候订阅,会接收到紫灯以及之后的所有元素。绿灯(可以理解为我们的 订阅2号,也就是传说中的新订阅者)的时候订阅,接收到绿灯以及之后的所有元素。

  图片来源:reactivex.io/documentati…,对 RxSwift 感兴趣的同学也可以看下我最近发布的 RxSwift 系列文章。

协议

  接下来我们要用到协议,用来封装和刷新状态有关的东西。对 Swift 协议还不是太明白的可以继续看上面那位大佬写的文章:

iOS - Swift 面向协议编程(一)

iOS - Swift 面向协议编程(二)

开始

  好了,下面我们就用上面学到的所有知识来写一个自动管理刷新状态案例。

  假设有这样一个需求:

// ViewController.swift// 两个闭包的参数默认为 nil,根据参数自动创建 mj_header 或 mj_footer,
// 不传参数则不创建。自动管理刷新状态。
viewModel.refreshStatusBind(to: tableView, {// 处理头部刷新。
}) {// 处理尾部刷新。
}.disposed(by: bag)

  如何做到这一点?看到方法是从 viewModel 里面调出来的,那我们就去 viewModel 里面看一看究竟。

class ViewModel: Refreshable {lazy var list &#61; Variable<[MnlDakaCommentModel]>([])let refreshStatus &#61; BehaviorSubject(value: RefreshStatus.none)let reload &#61; PublishSubject<Bool>()let bag &#61; DisposeBag()init() {reload.subscribe(onNext: { [weak self] (isDown) inguard let &#96;self&#96; &#61; self else {return}// 发送请求MnlAssetLoader.load(.dakaComment(params: self.params)) { (result) in let list &#61; result.value?["list"].arrayObjectlet models &#61; decode([MnlDakaCommentModel].self, from: list) ?? []}}}
}

  好了&#xff0c;为了简洁删除了部分代码&#xff0c;但该有的还是有&#xff0c;而且 ViewModel 里面我确实没有创建 refreshStatusBind(to:) 方法。

  这就奇怪了&#xff0c;究竟方法从何而来&#xff1f;答案在于协议。注意我们开始签了一个 Refreshable 的协议&#xff0c;refreshStatusBind(to:) 是在里面定义的。那我们就去看看&#xff0c;这个方法究竟是什么&#xff1f;为什么传几个参数进去就能自动创建刷新控件并管理其状态了呢&#xff1f;

Refreshable

  首先&#xff0c;我们定义了一个 Refreshable 协议&#xff1a;

protocol Refreshable {var refreshStatus: BehaviorSubject<RefreshStatus> { get }
}

  这里你就知道了吧&#xff1f;任何实现 Refreshable 必须实现 refreshStatus 属性&#xff0c;如果你足够眼尖应该看到&#xff0c;上面的 ViewModel 同样定义一个类型一样 refreshStatus 属性&#xff0c;为的就是实现协议中规定的属性。

  好了&#xff0c;有个这个属性之后&#xff0c;我们就可以愉快的管理刷新状态了&#xff0c;比如想让它结束刷新&#xff0c;我们可以拿到 refreshStatus 属性&#xff0c;比如在 ViewModel 里&#xff0c;我们可以这样&#xff1a;

// 发送请求
MnlAssetLoader.load(.dakaComment(params: self.params)) { (result) in let list &#61; result.value?["list"].arrayObjectlet models &#61; decode([MnlDakaCommentModel].self, from: list) ?? []// 请求完成需要结束刷新&#xff1a;// refreshStatus.onNext(.endFooterRefresh)// 或者判断没有更多数据时&#xff1a;// refreshStatus.onNext(isHidden: false, isNoMoreData: true)
}

  这时你肯定问了&#xff0c;凭什么我这样发送消息就可以管理刷新状态了&#xff1f;你逗我呢&#xff1f;之前讲 BehaviorSubject 的时候不是有讲到订阅 (subscribe) 吗&#xff1f;既然这里发送消息了&#xff0c;肯定会在接收到发出的元素的时候做了什么处理吧&#xff1f;

  问得好&#xff01;问得非常好&#xff01;问得太————好了。好吧&#xff0c;我老实交代&#xff0c;就来说下接收到元素时我都做了什么&#xff1f;

extension Refreshable {func refreshStatusBind(to scrollView: UIScrollView, _ header: (() -> Void)? &#61; nil, _ footer: (() -> Void)? &#61; nil) -> Disposable {if header !&#61; nil {scrollView.mj_header &#61; MJRefreshNormalHeader {// 处理头部方法时结束尾部刷新。scrollView.mj_footer?.endRefreshing()header?()}}if footer !&#61; nil {scrollView.mj_footer &#61; MJRefreshAutoNormalFooter {// 处理尾部方法时结束头部刷新。scrollView.mj_header?.endRefreshing()footer?()}}return refreshStatus.subscribe(onNext: { (status) inswitch status {case .none:// 未发生任何状态事件时隐藏尾部。scrollView.mj_footer?.isHidden &#61; truecase .beginHeaderRefresh:scrollView.mj_header?.beginRefreshing()case .endHeaderRefresh:scrollView.mj_header?.endRefreshing()case .endFooterRefresh:scrollView.mj_footer?.endRefreshing()case .endAllRefresh:// 结束全部拉刷新scrollView.mj_header?.endRefreshing()scrollView.mj_footer?.endRefreshing()case .footerStatus(let isHidden, let isNone):// 根据关联值确定 footer 的状态。scrollView.mj_footer?.isHidden &#61; isHidden// 处理尾部状态时&#xff0c;如果之前正在刷新头部&#xff0c;则结束刷新&#xff0c;// 至此&#xff0c;我们无需写判断结束头部刷新的代码&#xff0c;在这里自动处理。scrollView.mj_header?.endRefreshing()if isNone {scrollView.mj_footer?.endRefreshingWithNoMoreData()}else {scrollView.mj_footer?.endRefreshing()}}})}
}

  给 Refreshable 加一个扩展&#xff0c;我们先来看方法的第一部分&#xff1a;创建头部和尾部刷新控件。这段代码很容易看懂&#xff0c;结合前面放在 ViewController.swift 里的代码&#xff1a;

// 两个闭包的参数默认为 nil&#xff0c;根据参数自动创建 mj_header 或 mj_footer&#xff0c;
// 不传参数则不创建。自动管理刷新状态。
viewModel.refreshStatusBind(to: tableView, {// 处理头部刷新。
}) {// 处理尾部刷新。
}.disposed(by: bag)

  无非就是传闭包参数的时候创建对应的刷新控件&#xff0c;并把传进去的闭包作为控件的刷新事件。因为我已经把 UIScrollView 作为参数传进来了&#xff0c;所以可以直接拿到它创建刷新控件。

  在到第二部分&#xff0c;这就是一个真正监听状态改变的部分了&#xff0c;是根据发出的消息做出改变的反应。这样&#xff0c;我们在 ViewModel 里发送消息&#xff0c;这里就能接收到并作出对应的改变。

Demo

  贴下完整代码。

ViewController.swift

import UIKit
import RxSwiftclass ViewController: UITableViewController {lazy var viewModel &#61; ViewModel()let bag &#61; DisposeBag()override func viewDidLoad() {super.viewDidLoad()/// 创建刷新控件。viewModel.refreshStatusBind(to: tableView, { [weak self] in// 处理头部刷新。self?.viewModel.reload.onNext(false)}) { [weak self] in// 处理尾部刷新。self?.viewModel.reload.onNext(true)}.disposed(by: bag)// 给 viewModel 中的 reload 发送消息&#xff0c;让其请求数据。// 参数 Bool 表示是否上拉。viewModel.reload.onNext(false)}
}

ViewModel.swift

import UIKit
import RxSwiftclass ViewModel: Refreshable {lazy var list &#61; Variable<[Model]>([])let refreshStatus &#61; BehaviorSubject(value: RefreshStatus.none)let reload &#61; PublishSubject<Bool>()let bag &#61; DisposeBag()init() {reload.subscribe(onNext: { [weak self] (isReload) inguard let &#96;self&#96; &#61; self else {return}MnlAssetLoader.load(.dakaComment(params: self.params)) { (result) inlet list &#61; result.value?["list"].arrayObjectlet models &#61; decode([MnlDakaCommentModel].self, from: list) ?? []self.list.value &#61; isReload ? models : self.list.value &#43; modelslet count &#61; result.value?["count"].int ?? 0// 发送刷新状态给订阅者&#xff0c;让其作出改变。// 如果列表个数和总数相等&#xff0c;则判断它为没有更多数据。self.refreshStatus.onNext(.footerStatus(isHidden: self.list.value.isEmpty,isNoMoreData: self.list.value.count &#61;&#61; count))}}).disposed(by: bag)}
}

  关于 DisposeBag&#xff0c;其实就是一个资源回收包&#xff0c; 使用 Rx 代码会占用一些资源&#xff0c;我们把这些资源都添加到 bag 里面&#xff0c;这样在其对应的引用被 deinit 后&#xff0c;资源会被回收。

  详情可以查阅官方文档&#xff1a; Disposing section。


转:https://juejin.im/post/5a4d7f1b51882512ae130db2



推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 绿联五合一扩展坞:轻薄笔记本的刚需产品
    本文介绍了作者对于轻薄笔记本扩展坞的需求以及使用过程中遇到的问题。随后详细介绍了绿联五合一扩展坞的外观和接口情况,包括Type-C、USB 3.0、HDMI等接口的功能和特点。通过使用该扩展坞,作者解决了视频输出问题,充分满足了轻薄笔记本的扩展需求。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 【重识云原生】第四章云网络4.8.3.2节——Open vSwitch工作原理详解
    2OpenvSwitch架构2.1OVS整体架构ovs-vswitchd:守护程序,实现交换功能,和Linux内核兼容模块一起,实现基于流的交换flow-basedswitchin ... [详细]
  • 本文详细介绍了在Linux虚拟化部署中进行VLAN配置的方法。首先要确认Linux系统内核是否已经支持VLAN功能,然后配置物理网卡、子网卡和虚拟VLAN网卡的关系。接着介绍了在Linux配置VLAN Trunk的步骤,包括将物理网卡添加到VLAN、检查添加的VLAN虚拟网卡信息以及重启网络服务等。最后,通过验证连通性来确认配置是否成功。 ... [详细]
author-avatar
-彼岸花开-hui
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有