RxSwift-MVVM
MVVM
核心在于数据与UI
的双向绑定,数据的变化会更新UI
,UI
变化会更新我们的数据。那这种绑定操作谁来做呢?当然是我们的RxSwift
。学习RxSwift
框架以来,似乎并没有真正使用过这个框架,下面就来看看,RxSwift
具体能带来哪些便利。
一、登录页面
先看看效果:
UI
页面代码省略,下面只看数据UI
是如何绑定的。
1、UISwitch
和UILabel
的绑定
switch1.rx.isOn.map{!$0}.bind(to: titleLabel.rx.isHidden).disposed(by: disposeBag)switch1.rx.isOn.map{!$0}.bind(to: inputLabel.rx.isHidden).disposed(by: disposeBag)
rx
将isOn
属性值绑定到label
的isHidden
属性上,UI
改变isOn
属性同时给label
的属性赋值,两个属性类型同为Bool
类型。
2、UITextField
和UILabel
的绑定
nameTf.rx.text.bind(to: inputLabel.rx.text).disposed(by: disposeBag)
paswdTf.rx.text.bind(to: inputLabel.rx.text).disposed(by: disposeBag)
输入值text
改变,同时改变inputLabel
的text
属性。
3、绑定提示文本
let nameVerify = nameTf.rx.text.orEmpty.map{$0.count>5}
nameVerify.bind(to: nameLabel.rx.isHidden).disposed(by: disposeBag)
let pawdVerify = paswdTf.rx.text.orEmpty.map{$0.count>5}
pawdVerify.bind(to: paswdLabel.rx.isHidden).disposed(by: disposeBag)
通常一些提示语需要跟随输入来改变,如上通过map
设置条件,将序列绑定到相应的UI
控件上,控制显隐。当输入文本字符大于5隐藏提示文本,以上序列满足条件发送的是true
,isHidden=true
即为隐藏。
4、联合绑定
Observable.combineLatest(nameVerify,pawdVerify){$0 && $1
}.bind(to: loginBtn.rx.isEnabled).disposed(by: disposeBag)
结合两个用户名和密码两个条件来控制登录按钮是否可以点击。combineLatest
合并为新序列,两个条件同时成立即使能登录按钮。
通过以上的演示,明显能够感受到
RxSwift
给我们带来的便捷。通常需要我们设置触发事件,在触发事件中来赋值展示,代码过长,业务与UI
分散不好管理,在RxSwift
中只需要一两行代码便可以完成事件的创建与监听以及赋值。
二、UITableView列表展示
先看一下RxSwift
实现的效果:
展示上没有特别之处。在常规写法中,需要遵循代理并实现代理方法,在RxSwift
中我们可以如下写法:
1、创建tableView
tableview = UITableView.init(frame: self.view.bounds,style: .plain)
tableview.tableFooterView = UIView()
tableview.register(RowViewCell.classForCoder(), forCellReuseIdentifier: resuseID)
tableview.rowHeight = 100
self.view.addSubview(tableview)
常规写法,RxSwift
再精简也不能把我们的UI
精简了,这里还是需要我们一步步创建实现。当然这里我们可以看到我们并没有遵循delegate
和dataSource
代理。
2、初始化序列并展示
let dataOB = BehaviorSubject.init(value: self.viewModel.dataArray)
dataOB.asObserver().bind(to: tableview.rx.items(cellIdentifier:resuseID, cellType: RowViewCell.self)){(row, model, cell) incell.setUIData(model as! HBModel)
}.disposed(by: disposeBag)
初始化一个BehaviorSuject
序列,并加载cell
。到这里我们就可以展示一个列表了,至于cell
样式我们就常规创建设置。到此仅仅两步我们就能看到一个完整列表,很简洁,很高效。
这里很像我们之前在
OC
里边拆分代理实现一样,RxSwift
帮我们实现了内部方法。
3、实现点击事件
tableview.rx.itemSelected.subscribe(onNext: {[weak self] (indexPath) inprint("点击\(indexPath)行")self?.navigationController!.pushViewController(SectionTableview.init(), animated: true)self?.tableview.deselectRow(at: indexPath, animated: true)
}).disposed(by: disposeBag)
这里把所有点击事件当做序列来处理像观察者发送点击消息。
4、删除一个cell
tableview.delegate = self
tableview.rx.itemDeleted.subscribe(onNext: {[weak self] (indexPath) inprint("删除\(indexPath)行")self!.viewModel.dataArray.remove(at: indexPath.row)self?.loadUI(obSubject: dataOB)
}).disposed(by: disposeBag)extension RowTableview: UITableViewDelegate{func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {return .delete}
}
这里需要我们遵循代理,并实现以上方法,设置删除类型。
5、新增一个cell
tableview.delegate = self
tableview.rx.itemInserted.subscribe(onNext: {[weak self] (indexPath) inprint("添加数据:\(indexPath)行")guard let model = self?.viewModel.dataArray.last else{print("数据相等不太好添加")return}self?.viewModel.dataArray.insert(model, at: indexPath.row)self?.loadUI(obSubject: dataOB)
}).disposed(by: disposeBag)extension RowTableview: UITableViewDelegate{func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {return .insert}
}
同上遵循代理,实现方法,设置为插入类型。
6、移动cell
位置
tableview.isEditing = true
tableview.rx.itemMoved.subscribe(onNext: {[weak self] (sourceIndex, destinationIndex) inprint("从\(sourceIndex)移动到\(destinationIndex)")self?.viewModel.dataArray.swapAt(sourceIndex.row, destinationIndex.row)self?.loadUI(obSubject: dataOB)
}).disposed(by: disposeBag)
设置为可编辑既可以出现删除图标去,和移动图标。
- 使用
tableview
响应的功能,只需通过tableview
调用相应的序列,并订阅即可- 移动、新增
cell
需要我们实现UITableViewDelegate
代理方法,设置相应的EditingStyle- 同
cell
不同行高,也需要我们实现UITableViewDelegate
的代理方法,根据不同类型返回不同行高
三、UITableView的组实现
1、先创建tableview
视图
//列表
tableview = UITableView.init(frame: self.view.bounds,style: .plain)
tableview.tableFooterView = UIView()
tableview.register(RowViewCell1.classForCoder(), forCellReuseIdentifier: resuseID)
tableview.rowHeight = 80
tableview.delegate = self//此处遵循协议-实现编辑类型 删除、增加,设置头尾视图高度
self.view.addSubview(tableview)
- 设置
delegate
可以实现cell
的编辑类型(删除、增加)设置头尾视图高度
2、创建一个Model
文件,声明一个结构体设置我们需要显示的属性
struct CustomData {let name: Stringlet gitHubID: Stringvar image: UIImage?init(name:String, gitHubID:String) {self.name = nameself.gitHubID = gitHubIDimage = UIImage(named: gitHubID)}
}
- 每一条展示的数据都是从结构体中获取
3、创建组信息结构体
struct SectionOfCustomData {var header: Identityvar items: [Item]
}
extension SectionOfCustomData: SectionModelType{typealias Item = CustomDatatypealias Identity = Stringvar identity: Identity{return header}init(original: SectionOfCustomData, items: [Item]) {self = originalself.items = items}
}
header
头部标题字符串items
数组结构,用来存放步骤1中的结构体对象- 扩展
SectionOfCustomData
结构体,定义Item
为CustomData
类型,Identity
为String
类型
4、创建一个数据源类,并设置数据
class CustomDataList {var dataArrayOb:Observable<[SectionOfCustomData]>{get{return Observable.just(dataArray)}}var dataArray = [SectionOfCustomData(header: "A", items: [CustomData(name: "Alex V Bush", gitHubID: "alexvbush"),CustomData(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),CustomData(name: "Anton Efimenko", gitHubID: "reloni"),CustomData(name: "Ash Furrow", gitHubID: "ashfurrow")]),SectionOfCustomData(header: "B", items: [CustomData(name: "Alex V Bush", gitHubID: "alexvbush"),CustomData(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),CustomData(name: "Anton Efimenko", gitHubID: "reloni"),CustomData(name: "Ash Furrow", gitHubID: "ashfurrow")]),SectionOfCustomData(header: "C", items: [CustomData(name: "Alex V Bush", gitHubID: "alexvbush"),CustomData(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),CustomData(name: "Anton Efimenko", gitHubID: "reloni"),CustomData(name: "Ash Furrow", gitHubID: "ashfurrow")]),]
}
- 创建数组,存放定义的数据结构,并设置每组信息
- 将数组插入到可观察序列中,用来想绑定对象发送元素
5、创建数据源对象,数据类型为SectionOfCustomData
let dataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData>(configureCell: {[weak self] (dataSource, tableView, indexPath, HBSectionModel) -> RowViewCell1 inlet cell = tableView.dequeueReusableCell(withIdentifier: self!.resuseID, for: indexPath) as! RowViewCell1cell.selectionStyle = .nonecell.setSectionUIData(dataSource.sectionModels[indexPath.section].items[indexPath.row])return cell
})
点击查看该类,进入内部查看,该类继承了TableViewSectionedDataSource
类,在改类中,实际上实现了外部tableview
的所有UITableViewDataSource
的代理方法,通过闭包属性,将代理方法中的处理交给外部实现。
public typealias ConfigureCell = (TableViewSectionedDataSource<Section>, UITableView, IndexPath, Item) -> UITableViewCell
public typealias TitleForHeaderInSection = (TableViewSectionedDataSource<Section>, Int) -> String?
public typealias TitleForFooterInSection = (TableViewSectionedDataSource<Section>, Int) -> String?
public typealias CanEditRowAtIndexPath = (TableViewSectionedDataSource<Section>, IndexPath) -> Bool
public typealias CanMoveRowAtIndexPath = (TableViewSectionedDataSource<Section>, IndexPath) -> Bool
外部实现如下:
//展示头视图
dataSource.titleForHeaderInSection = {(dataSource,index) -> String inreturn dataSource.sectionModels[index].header
}
//展示尾部视图
dataSource.titleForFooterInSection = {(dataSource,index) -> String inreturn "\(dataSource.sectionModels[index].header) 尾部视图"
}
//设置可编辑-根据不同组来设置是否可编辑
dataSource.canEditRowAtIndexPath = {data,indexPath inreturn true
}
//设置可移动-根据不同组来设置是否可移动
dataSource.canMoveRowAtIndexPath = {data,indexPath inreturn true
}
效果如下:
四、search搜索请求实现
有个搜索列表需求,搜索框输入文本,发出请求,在将数据加载到tableview
列表中。UI
常规操作,不做描述。通常我们需要添加输入事件,在事件方法中发送网络请求,再将数据加载到tableview
上。而在的RxSwift
中呢,我们不需复杂的操作,只需要将UI
绑定到序列上,序列在绑定至UI
上即可。
1、创建数据Model
类
class searchModel: HandyJSON {var name: String = ""var url: String = ""required init() {}init(name:String,url:String) {self.name = nameself.url = url}
}
- 存放用来展示的属性,提供初始化方法
- 继承自
HandyJSON
,能够帮助我们序列化请求过来的数据
2、创建viewModel
类
class SearchViewModel: NSObject {//1、创建一个序列let searchOB = BehaviorSubject(value: "")lazy var searchData: Driver<[searchModel]> = {return self.searchOB.asObservable().throttle(RxTimeInterval.milliseconds(300), scheduler: MainScheduler.instance)//设置300毫秒发送一次消息.distinctUntilChanged()//搜索框内容改变才发送消息.flatMapLatest(SearchViewModel.responseData).asDriver(onErrorJustReturn: [])}()//2、请求数据static func responseData(_ githubID:String) -> Observable<[searchModel]>{guard !githubID.isEmpty, let url = URL(string: "https://api.github.com/users/\(githubID)/repos")else{return Observable.just([])}return URLSession.shared.rx.json(url: url).retry()//请求失败尝试重新请求一次.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))//后台下载.map(SearchViewModel.dataParse)}//3、数据序列化static func dataParse(_ json:Any) -> [searchModel]{//字典+数组guard let items = json as? [[String:Any]] else {return []}//序列化guard let result = [searchModel].deserialize(from: items) else {return []}return result as! [searchModel]}
}
- 创建一个
BehaviorSubject
类型的序列,可做序列生产者又可做观察者 searchData
输入的入口,触发搜索获取网络数据throttle
设定消息发送时间间隔,避免频繁请求distinctUntilChanged
只有输入内容发生变化才发出消息flatMapLatest
序列的序列需要下沉请求,回调结果asDriver
使得序列为Driver
序列,保证状态共享,不重复发送请求,保证消息发送在主线程
3、双向绑定
搜索框绑定到序列:
self.searchBar.rx.text.orEmpty.bind(to: self.viewModel.searchOB).disposed(by: disposeBag)
- 绑定序列,输入时会向序列发送消息,开始请求数据并保存
绑定UI->tableview
:
self.viewModel.searchData.drive(self.tableview.rx.items) {[weak self] (tableview,indexPath,model) -> RowViewCell2 inlet cell = tableview.dequeueReusableCell(withIdentifier: self!.resuseID) as! RowViewCell2cell.selectionStyle = .nonecell.nameLabel.text = model.namecell.detailLabel.text = model.urlreturn cell
}.disposed(by: disposeBag)
- 通过
drive
发送请求到的共享数据,将数据绑定到tableview
上显示
最终实现效果如下:
通过以上的对
RxSwift
的使用体验,我们会发现,在RxSwift
中省略了所有事件的创建,点击事件,编辑事件,按钮事件等等,在哪创建UI
,就在哪使用,事件的产生由RxSwift
直接提供,UI
的展示也可以直接交给RxSwift
来赋值。我们需要做的是:数据和UI
的相互绑定。
在没有接触
RAC
和RxSwift
之前,个人也是封装了这些事件,便于调用,但是数据绑定上并没考虑到太多,道行尚浅还需继续学习。
RxSwift-MVVM相关推荐
- 开源项目分析(SwiftHub)Rxswift + MVVM + Moya 架构分析(一)第三方框架使用
文章目录 开源项目分析(SwiftHub)Rxswift + MVVM + Moya 架构分析(一)第三方框架使用 1. SwiftHub项目简介 1.1 SwiftHub项目UI 1.2 Swift ...
- 干货集中营 ReactiveCocoa+RXSwift+MVVM
原文地址: 传送门简书只做同步更新功能 学习函数响应式编程已经接近两个月的时间.说实话坚持下来实在不易.两个月的时间看过近150篇博文,算下来啃下来一本千页的技术书籍也差不多.不过随着知识面的拓广,学 ...
- 关于RxSwift MVVM flatMapLatest 点击事件网络请求失败整个序列结束
例子 先上代码吧: self.signedIn = input.loginTaps.withLatestFrom(usernameAndPassword).flatMapLatest { (usern ...
- 老司机 iOS 周报 #65 | 2019-04-29
老司机 iOS 周报,只为你呈现有价值的信息. 你也可以为这个项目出一份力,如果发现有价值的信息.文章.工具等可以到 Issues 里提给我们,我们会尽快处理.记得写上推荐的理由哦.有建议和意见也欢迎 ...
- iOS'Dev的2018年个人总结 | 掘金年度征文
"2018 's summary ,转载自我的个人博客,本文地址:www.hualong.me/2019/01/10/-" 引言 2018,是我正式踏入职场的第一年,从17年末校招 ...
- 开源项目源码分析(Kickstarter-iOS )(一)
开源项目源码分析(Kickstarter-iOS )(一) 1.Kickstarter开源项目简介 2. Kickstarter项目结构 2.1 Makefile 文件 2.2 Git submodu ...
- 使用MVVM Swift UIKit RxSwift 写一个SpaceX 发射计划APP
文章: Build a simple SpaceX Launches iOS app with MVVM and RxSwift 源码 GitHub - ykpoh/SpaceXLaunch: A i ...
- RxSwift技术路线与参考资料
RxSwift技术路线与参考资料 ## RxSwift简介 响应式编程 响应式编程(Reactive Programming)是一种通过异步和数据流来构建事务关系的编程思想.核心体现就是观察者和可被观 ...
- RxSwift处理Error事件
如何处理RxSwift的Error事件 翻译自:How to handle errors in RxSwift 在最近这些日子里,MVVM在iOS开发中变得越来约受欢迎,RxSwfit也变得越来越流行 ...
- 理解 RXSwift:单元测试(四)
理解 RxSwift:为什么要使用 RxSwift(一) 理解 RxSwift:实现原理(二) 理解 RxSwift:单元测试(四) 这篇文章是阅读 RxSwift: Reactive Program ...
最新文章
- golang reflect
- 程序员失业第一步?斯坦福研究员用AI从编译器反馈中学习改Bug
- Level/levelup-2-API
- CTFshow php特性 web131
- Java中list.forEach方法的使用示例-根据key获取对应的value
- Python-读取文件例子:一个获取指定目录下一定格式的文件名称和文件修改时间并保存为文件的python脚本 ....
- 牛客 怕npy的牛牛(双指针)
- python3 random模块操作
- 草稿-git的使用-for windows -1006
- android datepicker控件,android中控件DatePicker控件-Fun言
- linux实战清理挖矿病毒kthreaddi
- python从入门到精通-小白如何系统学习python,从入门到精通?
- 国内自主研发的游戏引擎一览
- MariaDB数据库导出导入
- UG NX 12 抽取面特征
- _id随机的 es_ES再现偷ID事件?仅与阿水ID相差1个字,玩家却释怀,原是系统作梗...
- 创新设计思维记录(part1)
- 数学、键盘符号和时间复杂度的英语术语及表述方法(编程,标识符,按键,空间复杂度,指数,对数,模运算)
- 树莓派Zero 2W python3.7 安装tensorflow2.2
- 如何使用mysql数据库做网站_php小型数据库(不用mysql做网站)
热门文章
- 如何使用文件保险箱加密 Mac 上的启动磁盘?
- 传递VB数组给DLL中的函数
- 38年来,NBA最有价值球员|数据分析
- 医用计算机是什么意思,pc是什么意思(全网最全解读pc寓意)
- 手机的红外线功能有可能淘汰
- Maven setting文件配置错误:Non-parseable settings..in comment after two dashes (--) next character must be
- 【毕业设计】单片机与NBIOT通信模块 - 单片机 物联网 stm32
- 仙人掌相关问题的处理方法(未完待续)
- python手把手教你创作趣味词云(保姆级贴心)
- x11-forwarding disabled解决办法