RxSwift系列
①、RxSwift基础控件的使用、RxSwif-Tableview的使用、RxSwift-SectionTableview结合RxDataSources的使用、RxSwift 网络请求封装的使用
②、RxSwift函数式响应编程思想,RxSwift-KVO、Button、UITextField、ScrollView、手势、通知、定时器、网络请求的使用
③、iOS-RxSwift核心逻辑,RxSwift实战案例,Observable的继承链,Observable订阅的流程、OnNext的流程、AnonymousObservableSink业务逻辑用
④、RxSwift的Timer、Observable的创建、RxSwiftUI控件(UIDatePicker、UIButton、UISwitch、UISlider、UIStepper)在实际开发的使用
推荐网站
RxSwift官方文档
RxSwift-Github源码
RxSwift的操作符Demo
Demo-①、RxSwift基础控件的使用、RxSwif-Tableview的使用、RxSwift-SectionTableview结合RxDataSources的使用、RxSwift 网络请求封装的使用
Demo-②、RxSwift函数式响应编程思想,RxSwift-KVO、Button、UITextField、ScrollView、手势、通知、定时器、网络请求的使用-1
Demo-②、RxSwift函数式响应编程思想,RxSwift-KVO、Button、UITextField、ScrollView、手势、通知、定时器、网络请求的使用-2-Observable
本篇文章 参照文献
1.BehaviorRelay 无法使用value 给BehaviorRelay扩展 不使用value属性 使用val属性
RxSwift初探
- ①、RxSwift 基本控件的使用
- 1.RAC和RxSwift对比
- 2.RxSwift 监听值的改变 以`UITextField`输出多少字符为例
- 3.RxSwift 双向绑定 添加操作符的扩展
- 3.1 rxSwift4 定义变量是`Variable ` RxSwift5.x 使用的是 `BehaviorRelay`或者`BehaviorSubject` .[具体参考](https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/rxswift_core/observable_and_observer/variable.html)
- 3.2 `<->`双向绑定操作符的扩展 `BehaviorRelay`不能直接使用value属性 [具体参考](https://stackoverflow.com/questions/47452582/how-to-use-behaviorrelay-as-an-alternate-to-variable-in-rxswift)`BehaviorRelay`添加扩展
- 4.RxSwift给`UIbutton`绑定点击事件
- ②、RxSwift Tableview的使用
- ③、RxSwift SectionTableview结合RxDataSources的使用
- ④、RxSwift 网络请求封装的使用
①、RxSwift 基本控件的使用
1.RAC和RxSwift对比
RAC
// 1.创建信号
@weakify(self);
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
@strongify(self);
NSLog(@"来了,网络请求");
// 3.发送信息
[subscriber sendNext:@"yuye"];
// 4.销毁信号
return [RACDisposable disposableWithBlock:^{
NSLog(@"我们销毁了");
}];
}];
// 2.订阅信号 --- 保存block
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"订阅到了 : %@",x);
}];
[signal subscribeError:^(NSError * _Nullable error) {
NSLog(@"Error is %@",error);
}];
RxSwift
func RxSwiftUse(){
// RAC --- Signal
// 1.产生序列 --- swift
let observer = Observable<Any>.create { (ob) -> Disposable in
//
print(ob)
// 3.发送
ob.onNext("tenhow")
ob.onError(MyError.errorA)
ob.onCompleted()
return Disposables.create()
}
// 订阅
observer.subscribe { (signal) in
print(signal)
} onError: { error in
print(error)
} onCompleted: {
print("完成了")
} onDisposed: {
print("销毁了")
}.disposed(by: disposB)
}
2.RxSwift 监听值的改变 以UITextField
输出多少字符为例
@IBOutlet weak var textf: UITextField!
@IBOutlet weak var label: UILabel!
func listenInput() {
// orEmpty 可能是空值
// asDriver(当司机) 生成响应的系列类型之后
// throttle 只隔几秒之后 才会响应
// map 进行函数预设
// drive(开车) 卡车 就绑定到响应的控件里面去
//
let textInput = textf.rx.text.orEmpty.asDriver().throttle(RxTimeInterval.seconds(1))
textInput.map { "输入:\($0.count)"
}.drive(label.rx.text).disposed(by: disposB)
}
2.1 RxSwift 监听值的改变绑定多个UI 进行关联
@IBOutlet weak var btn: UIButton!
textInput.map{$0.count > 5}.drive(btn.rx.isEnabled).disposed(by: disposB)
3.RxSwift 双向绑定 添加操作符的扩展
3.1 rxSwift4 定义变量是Variable
RxSwift5.x 使用的是 BehaviorRelay
或者BehaviorSubject
.具体参考
// rxSwift5 开始使用 BehaviorRelay
let textOb = BehaviorRelay(value: "🔴")
// <-> 操作符 <-> 是一个扩展 双向绑定
_ = textf.rx.textInput <-> textOb
3.2 <->
双向绑定操作符的扩展 BehaviorRelay
不能直接使用value属性 具体参考BehaviorRelay
添加扩展
Operators.swift
//
// Operators.swift
// RxExample
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import RxSwift
import RxCocoa
import UIKit
// Two way binding operator between control property and variable, that's all it takes {
infix operator <-> : DefaultPrecedence
// rxSwift5 双向绑定 给 BehaviorRelay扩展
extension BehaviorRelay {
var val: Element {
get { value }
set { accept(newValue) }
}
}
func nonMarkedText(_ textInput: UITextInput) -> String? {
let start = textInput.beginningOfDocument
let end = textInput.endOfDocument
guard let rangeAll = textInput.textRange(from: start, to: end),
let text = textInput.text(in: rangeAll) else {
return nil
}
guard let markedTextRange = textInput.markedTextRange else {
return text
}
guard let startRange = textInput.textRange(from: start, to: markedTextRange.start),
let endRange = textInput.textRange(from: markedTextRange.end, to: end) else {
return text
}
return (textInput.text(in: startRange) ?? "") + (textInput.text(in: endRange) ?? "")
}
func <-> <Base>(textInput: TextInput<Base>, variable: BehaviorRelay<String>) -> Disposable {
let bindToUIDisposable = variable.asObservable()
.bind(to: textInput.text)
let bindToVariable = textInput.text
.subscribe(onNext: { [weak base = textInput.base] n in
guard let base = base else {
return
}
let nonMarkedTextValue = nonMarkedText(base)
/**
In some cases `textInput.textRangeFromPosition(start, toPosition: end)` will return nil even though the underlying
value is not nil. This appears to be an Apple bug. If it's not, and we are doing something wrong, please let us know.
The can be reproed easily if replace bottom code with
if nonMarkedTextValue != variable.value {
variable.value = nonMarkedTextValue ?? ""
}
and you hit "Done" button on keyboard.
*/
if let nonMarkedTextValue = nonMarkedTextValue, nonMarkedTextValue != variable.value {
variable.val = nonMarkedTextValue
}
}, onCompleted: {
bindToUIDisposable.dispose()
})
return Disposables.create(bindToUIDisposable, bindToVariable)
}
func <-> <T>(property: ControlProperty<T>, variable: BehaviorRelay<T>) -> Disposable {
if T.self == String.self {
#if DEBUG
fatalError("It is ok to delete this message, but this is here to warn that you are maybe trying to bind to some `rx.text` property directly to variable.\n" +
"That will usually work ok, but for some languages that use IME, that simplistic method could cause unexpected issues because it will return intermediate results while text is being inputed.\n" +
"REMEDY: Just use `textField <-> variable` instead of `textField.rx.text <-> variable`.\n" +
"Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n"
)
#endif
}
let bindToUIDisposable = variable.asObservable()
.bind(to: property)
let bindToVariable = property
.subscribe(onNext: { n in
variable.val = n
}, onCompleted: {
bindToUIDisposable.dispose()
})
return Disposables.create(bindToUIDisposable, bindToVariable)
}
// }
4.RxSwift给UIbutton
绑定点击事件
btn.rx.tap.subscribe(onNext:{ (ob) in
print("点了");
}).disposed(by: disposB)
②、RxSwift Tableview的使用
0.pod 安装 RxDataSource
用来操作tableview的
pod 'RxDataSources'
1.RxSwift绑定tableview bind
1.Rx的just操作符 just 操作符将某一个元素转换为 Observable。
func RxBindTableview(){
tableView = UITableView(frame: self.view.bounds, style: .plain)
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: MyTableViewCell.description())
self.view.addSubview(tableView)
// just 操作符将某一个元素转换为 Observable。
// 绑定
items.bind(to: self.tableView.rx.items){(tb,row,model) -> UITableViewCell in
let cell = tb.dequeueReusableCell(withIdentifier: MyTableViewCell.description()) as? MyTableViewCell
cell?.titlteLabel?.text = model.despStr;
cell?.nameLabel?.text = model.nameStr;
return cell!
}.disposed(by: disposeBag)
}
2.RxSwift的tableview select
选择操作
func RxBindTableviewAction() {
// 点击
tableView.rx.modelSelected(DataModel.self).subscribe(onNext: { (model) in
print(model)
})
.disposed(by: disposeBag)
tableView.rx.itemSelected.subscribe(onNext: { [weak self] indexpath in
print("点击了 \(indexpath)")
// self?.navigationController?.pushViewController(SectionTableViewVC(), animated: true)
})
.disposed(by: disposeBag)
}
3.RxSwift的tableview move
移动操作
// 移动
tableView.isEditing = true
tableView.rx.itemMoved.subscribe(onNext: { (soureceIndex,desIndex) in
print("从\(soureceIndex)移动到 \(desIndex)")
})
.disposed(by: disposeBag)
4.RxSwift的tableview delete
删除操作
// 删除
tableView.rx.itemDeleted.subscribe(onNext: { (indexPath) in
print("删除+++\(indexPath)")
})
.disposed(by: disposeBag)
③、RxSwift SectionTableview结合RxDataSources的使用
1.创建section的tableview
var tableView : UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
AddSectionTableView()
}
func AddSectionTableView(){
self.title = "分组显示的Table"
self.view.backgroundColor = UIColor.orange
tableView = UITableView(frame: self.view.bounds, style: .plain)
tableView.register(SectionTableCell.self, forCellReuseIdentifier: SectionTableCell.description())
self.view.addSubview(tableView)
}
// cell
class SectionTableCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: reuseIdentifier)
// print(#function)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2.使用RxTableViewSectionedReloadDataSource
创建 数据源
2.1 先创建本地SectionTableView的数据
RxDataModel
//
// RxDataModel.swift
// 优快云-RxSwift
//
// Created by 李宇鸿 on 2022/2/21.
//
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
struct DataModel {
let despStr:String
let nameStr:String
}
struct InfoViewModel {
var arr = Array<DataModel>()
init(){
arr.append(DataModel(despStr: "first", nameStr: "宇夜"))
arr.append(DataModel(despStr: "2", nameStr: "iOS"))
arr.append(DataModel(despStr: "3", nameStr: "Android"))
arr.append(DataModel(despStr: "4", nameStr: "Java"))
arr.append(DataModel(despStr: "5", nameStr: "Python"))
}
}
// section tableview DataS
struct SectionDataModel {
let name: String
let gitHubID: String
var image: UIImage?
init(name: String, gitHubID: String) {
self.name = name
self.gitHubID = gitHubID
image = UIImage(named: gitHubID)
}
}
extension SectionDataModel:IdentifiableType{
typealias Identity = String
var identity:Identity {return gitHubID}
}
class GithubData {
let githubData = Observable.just([
SectionModel(model: "A", items: [
SectionDataModel(name: "Alex V Bush", gitHubID: "alexvbush"),
SectionDataModel(name: "Andrew Breckenridge", gitHubID: "AndrewSB"),
SectionDataModel(name: "Anton Efimenko", gitHubID: "reloni"),
SectionDataModel(name: "Ash Furrow", gitHubID: "ashfurrow"),
]),
SectionModel(model: "B", items: [
SectionDataModel(name: "Ben Emdon", gitHubID: "BenEmdon"),
SectionDataModel(name: "Bob Spryn", gitHubID: "sprynmr"),
]),
SectionModel(model: "C", items: [
SectionDataModel(name: "Carlos García", gitHubID: "carlosypunto"),
SectionDataModel(name: "Cezary Kopacz", gitHubID: "CezaryKopacz"),
SectionDataModel(name: "Chris Jimenez", gitHubID: "PiXeL16"),
SectionDataModel(name: "Christian Tietze", gitHubID: "DivineDominion"),
]),
SectionModel(model: "D", items: [
SectionDataModel(name: "だいちろ", gitHubID: "mokumoku"),
SectionDataModel(name: "David Alejandro", gitHubID: "davidlondono"),
SectionDataModel(name: "David Paschich", gitHubID: "dpassage"),
]),
SectionModel(model: "E", items: [
SectionDataModel(name: "Esteban Torres", gitHubID: "esttorhe"),
SectionDataModel(name: "Evgeny Aleksandrov", gitHubID: "ealeksandrov"),
]),
SectionModel(model: "F", items: [
SectionDataModel(name: "Florent Pillet", gitHubID: "fpillet"),
SectionDataModel(name: "Francis Chong", gitHubID: "siuying"),
]),
SectionModel(model: "G", items: [
SectionDataModel(name: "Giorgos Tsiapaliokas", gitHubID: "terietor"),
SectionDataModel(name: "Guy Kahlon", gitHubID: "GuyKahlon"),
SectionDataModel(name: "Gwendal Roué", gitHubID: "groue"),
]),
SectionModel(model: "H", items: [
SectionDataModel(name: "Hiroshi Kimura", gitHubID: "muukii"),
]),
SectionModel(model: "I", items: [
SectionDataModel(name: "Ivan Bruel", gitHubID: "ivanbruel"),
]),
SectionModel(model: "J", items: [
SectionDataModel(name: "Jeon Suyeol", gitHubID: "devxoul"),
SectionDataModel(name: "Jérôme Alves", gitHubID: "jegnux"),
SectionDataModel(name: "Jesse Farless", gitHubID: "solidcell"),
SectionDataModel(name: "Junior B.", gitHubID: "bontoJR"),
SectionDataModel(name: "Justin Swart", gitHubID: "justinswart"),
]),
SectionModel(model: "K", items: [
SectionDataModel(name: "Karlo", gitHubID: "floskel"),
SectionDataModel(name: "Krunoslav Zaher", gitHubID: "kzaher"),
]),
SectionModel(model: "L", items: [
SectionDataModel(name: "Laurin Brandner", gitHubID: "lbrndnr"),
SectionDataModel(name: "Lee Sun-Hyoup", gitHubID: "kciter"),
SectionDataModel(name: "Leo Picado", gitHubID: "leopic"),
SectionDataModel(name: "Libor Huspenina", gitHubID: "libec"),
SectionDataModel(name: "Lukas Lipka", gitHubID: "lipka"),
SectionDataModel(name: "Łukasz Mróz", gitHubID: "sunshinejr"),
]),
SectionModel(model: "M", items: [
SectionDataModel(name: "Marin Todorov", gitHubID: "icanzilb"),
SectionDataModel(name: "Maurício Hanika", gitHubID: "mAu888"),
SectionDataModel(name: "Maximilian Alexander", gitHubID: "mbalex99"),
]),
SectionModel(model: "N", items: [
SectionDataModel(name: "Nathan Kot", gitHubID: "nathankot"),
]),
SectionModel(model: "O", items: [
SectionDataModel(name: "Orakaro", gitHubID: "DTVD"),
SectionDataModel(name: "Orta", gitHubID: "orta"),
]),
SectionModel(model: "P", items: [
SectionDataModel(name: "Paweł Urbanek", gitHubID: "pawurb"),
SectionDataModel(name: "Pedro Piñera Buendía", gitHubID: "pepibumur"),
SectionDataModel(name: "PG Herveou", gitHubID: "pgherveou"),
]),
SectionModel(model: "R", items: [
SectionDataModel(name: "Rui Costa", gitHubID: "ruipfcosta"),
SectionDataModel(name: "Ryo Fukuda", gitHubID: "yuzushioh"),
]),
SectionModel(model: "S", items: [
SectionDataModel(name: "Scott Gardner", gitHubID: "scotteg"),
SectionDataModel(name: "Scott Hoyt", gitHubID: "scottrhoyt"),
SectionDataModel(name: "Sendy Halim", gitHubID: "sendyhalim"),
SectionDataModel(name: "Serg Dort", gitHubID: "sergdort"),
SectionDataModel(name: "Shai Mishali", gitHubID: "freak4pc"),
SectionDataModel(name: "Shams Ahmed", gitHubID: "shams-ahmed"),
SectionDataModel(name: "Shenghan Chen", gitHubID: "zzdjk6"),
SectionDataModel(name: "Shunki Tan", gitHubID: "milkit"),
SectionDataModel(name: "Spiros Gerokostas", gitHubID: "sger"),
SectionDataModel(name: "Stefano Mondino", gitHubID: "stefanomondino"),
]),
SectionModel(model: "T", items: [
SectionDataModel(name: "Thane Gill", gitHubID: "thanegill"),
SectionDataModel(name: "Thomas Duplomb", gitHubID: "tomahh"),
SectionDataModel(name: "Tomasz Pikć", gitHubID: "pikciu"),
SectionDataModel(name: "Tony Arnold", gitHubID: "tonyarnold"),
SectionDataModel(name: "Torsten Curdt", gitHubID: "tcurdt"),
]),
SectionModel(model: "V", items: [
SectionDataModel(name: "Vladimir Burdukov", gitHubID: "chipp"),
]),
SectionModel(model: "W", items: [
SectionDataModel(name: "Wolfgang Lutz", gitHubID: "Lutzifer"),
]),
SectionModel(model: "X", items: [
SectionDataModel(name: "xixi197 Nova", gitHubID: "xixi197"),
]),
SectionModel(model: "Y", items: [
SectionDataModel(name: "Yongha Yoo", gitHubID: "inkyfox"),
SectionDataModel(name: "Yosuke Ishikawa", gitHubID: "ishkawa"),
SectionDataModel(name: "Yury Korolev", gitHubID: "yury"),
]),
])
}
2.2 创建RxTableViewSectionedReloadDataSource
类型的数据源
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,SectionDataModel>>(configureCell: { (ds,tb, indexPath, model) -> UITableViewCell in
let cell = tb.dequeueReusableCell(withIdentifier: SectionTableCell.description(), for: indexPath) as? SectionTableCell
cell?.imageView?.image = model.image
cell?.textLabel?.text = model.name
cell?.detailTextLabel?.text = model.gitHubID
return cell!
},titleForHeaderInSection:{(ds, index) -> String? in
return ds.sectionModels[index].model //[self.dataArray[indexPath.section] title]//self.dataArray[indexPath.section][indexPath.row]
})
2.3 将本地数据关联到数据源里面去
swift
var dataS = GithubData()
dataS.githubData.asDriver(onErrorJustReturn: [])
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
RxSwift 总结 : 更多的是面向开发
cell 数值、配置、闭包、函数式
④、RxSwift 网络请求封装的使用
1.结合URSession使用RxSwift 进行请求 以 json
格式返回
let surStr = "https://www.douban.com/j/app/radio/channels" // 豆瓣广播渠道接口
let disposeB = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
RxNetwork_json()
}
func RxNetwork_json(){
let url = URL(string: surStr)
URLSession.shared.rx.json(url: url!).subscribe(onNext: { (jsonData) in
print("RxNetwork_json jsonData = \(jsonData)\n")
},onError: { (error) in
print("RxNetwork_json \(error)")
}).disposed(by: disposeB)
}
---
返回结果
2022-02-22 10:34:12.652000+0800 优快云-RxSwift[48336:5068445] [boringssl] boringssl_metrics_log_metric_block_invoke(151) Failed to log metrics
curl -X GET
"https://www.douban.com/j/app/radio/channels" -i -v
Success (485ms): Status 200
RxNetwork_json jsonData = {
channels = (
{
"abbr_en" = My;
"channel_id" = 0;
name = "\U79c1\U4eba\U5146\U8d6b";
"name_en" = "Personal Radio";
"seq_id" = 0;
},
{
"abbr_en" = "";
"channel_id" = 189;
name = "\U62c9\U4e01";
"name_en" = "";
"seq_id" = 46;
}
);
}
2.结合URSession使用RxSwift 进行请求 以 data
格式返回
let surStr = "https://www.douban.com/j/app/radio/channels" // 豆瓣广播渠道接口
let disposeB = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
RxNetwork_data()
}
func RxNetwork_data(){
let url = URL(string: surStr)
URLSession.shared.rx.data(request: URLRequest(url: url!))
.subscribe(onNext:{ (data) in
print("RxNetwork_data data = \(data)\n")
}, onError: { (error) in
print("RxNetwork_data \(error)")
})
.disposed(by: disposeB)
}
---
返回结果
2022-02-22 10:34:12.924371+0800 优快云-RxSwift[48336:5068439] [boringssl] boringssl_metrics_log_metric_block_invoke(151) Failed to log metrics
curl -X GET
"https://www.douban.com/j/app/radio/channels" -i -v
Success (674ms): Status 200
RxNetwork_data data = 3661 bytes
3.结合URSession使用RxSwift 进行请求 以 reponse
,data
格式返回
let surStr = "https://www.douban.com/j/app/radio/channels" // 豆瓣广播渠道接口
let disposeB = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
RxNetwork_data()
}
func RxNetwork_response(){
let url = URL(string: surStr)
URLSession.shared.rx.response(request: URLRequest(url: url!))
.subscribe(onNext: {(resp,data) in
print("RxNetwork_response reponse = \(resp)\n")
print("RxNetwork_response data = \(data)\n")
},onError: {(error) in
print("RxNetwork_response \(error)")
})
.disposed(by: disposeB)
}
---
返回结果
curl -X GET
"https://www.douban.com/j/app/radio/channels" -i -v
Success (699ms): Status 200
RxNetwork_response reponse = <NSHTTPURLResponse: 0x600002e644c0> { URL: https://www.douban.com/j/app/radio/channels } { Status Code: 200, Headers {
"Cache-Control" = (
"must-revalidate, no-cache, private"
);
Connection = (
"keep-alive"
);
"Content-Encoding" = (
br
);
"Content-Type" = (
"application/json; charset=utf-8"
);
Date = (
"Tue, 22 Feb 2022 02:34:13 GMT"
);
Expires = (
"Sun, 1 Jan 2006 01:00:00 GMT"
);
"Keep-Alive" = (
"timeout=30"
);
Pragma = (
"no-cache"
);
Server = (
dae
);
"Strict-Transport-Security" = (
"max-age=15552000;"
);
"Transfer-Encoding" = (
Identity
);
"X-DAE-App" = (
fm
);
"X-DAE-Instance" = (
v1api
);
"X-DAE-Mountpoint" = (
True
);
"X-Douban-Mobileapp" = (
0
);
"X-Xss-Protection" = (
"1; mode=block"
);
} }
RxNetwork_response data = 3661 bytes
2022-02-22 10:36:33.420631+0800 优快云-RxSwift[48336:5068442] [ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke
4.使用RxSwift网络请求应用到搜索模块
1.SearchViewModel
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
struct Reposity {
let name: String
let url: String
}
class SearchViewModel: NSObject {
// 搜索的文本
let searchText = BehaviorRelay(value: "")
// 搜索返回的数据
// 外部观察 searchBar.text <-> searchText -> 请求searData
var searchData : Driver<[Reposity]> {
print("search")
// 每0.5秒监听值得变化 在主线程执行。发送改变时 转换到请求网络函数里面
return self.searchText.asObservable()
.throttle(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest(SearchViewModel.ReponsFor)
.asDriver(onErrorJustReturn: [])
}
// 请求网络
static func ReponsFor(_ githubId:String) -> Observable<[Reposity]> {
// 空判断
print("请求")
guard !githubId.isEmpty,let url = URL(string: "https://api.github.com/users/\(githubId)/repos") else {
return Observable.just([])
}
print("请求操作")
// retry https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/decision_tree/retry.html
// retry 操作符将不会将 error 事件,传递给观察者,然而,它会从新订阅源 Observable,给这个 Observable 一个重试的机会,让它有机会不产生 error 事件。retry 总是对观察者发出 next 事件,即便源序列产生了一个 error 事件,所以这样可能会产生重复的元素(如上图所示)。
// 请求在子线程操作
return URLSession.shared.rx.json(url: url)
.retry()
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.map(SearchViewModel.parse)
}
// 请求的数据 格式化
static func parse(json : Any) -> [Reposity] {
// 空处理
guard let items = json as? [[String:Any]] else { return [] }
// guard let items = json as? [[String:Any]] else { return [] }
var resps = [Reposity]() // 初始化模型数组
items.forEach{ item in
//空处理
guard let name = item["name"] as? String,let url = item["html_url"] as? String else { return}
// 添加数据
resps.append(Reposity(name: name, url: url))
}
return resps
}
}
2.RxNewSearchVC
1.创建Tableview
、SearchBar
、searchResultsController
let searchController = UISearchController(searchResultsController: nil)
var searchBar: UISearchBar{return searchController.searchBar}
var myTableView:UITableView!
let reuserId = "cell"
let disposeB = DisposeBag()
var shouldShowSearchResults = false //是否显示搜索结果
var vm = SearchViewModel()
override func viewDidLoad() {
super.viewDidLoad()
createTableViewAndSearchBar()
}
func createTableViewAndSearchBar() {
self.title = "搜索界面"
self.view.backgroundColor = UIColor.orange
myTableView = UITableView(frame: self.view.bounds, style: .plain)
myTableView.register(SectionTableCell.self, forCellReuseIdentifier: SectionTableCell.description())
self.view.addSubview(myTableView)
// searchVC 、 SearchBar
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "请输入你要搜索的内容"
searchBar.showsCancelButton = true
myTableView.tableHeaderView = searchController.searchBar
}
// cell
class SectionTableCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: reuseIdentifier)
// print(#function)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2.将SearchViewModel
和Tableview进行关联绑定
// 双向绑定
self.vm.searchData.drive(myTableView.rx.items) { (tb,row,model) -> UITableViewCell in
let cell = tb.dequeueReusableCell(withIdentifier: SectionTableCell.description()) as? SectionTableCell
cell?.textLabel?.text = model.name;
cell?.detailTextLabel?.text = model.url;
return cell!
}.disposed(by: disposeB)
3.将SearchBar
输入和SearchViewModel
进行绑定
//kvo
// 将searchBar输入的值 绑定到VM里面的SearchText里面去
searchBar.rx.text.orEmpty
.bind(to: vm.searchText)
.disposed(by: disposeB)
myTableView.rx.didEndDecelerating
.subscribe(onNext: { () in
self.searchBar.endEditing(true)
})
.disposed(by: disposeB)
// viewModel 检测值发生改变 响应到 title
vm.searchData.asDriver()
.map {[weak self] (resp) -> String in
return "\(String(describing: self?.searchBar.text))有resp\(resp.count)"
}
.drive(navigationItem.rx.title)
.disposed(by: disposeB)