一、基本用法
① 单个分区的集合视图
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 70)
self.collectionView = UICollectionView(frame: self.view.frame,
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.view.addSubview(self.collectionView!)
let items = Observable.just([
"Swift",
"PHP",
"Ruby",
"Java",
"C++",
])
items
.bind(to: collectionView.rx.items) { (collectionView, row, element) in
let indexPath = IndexPath(row: row, section: 0)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(row):\(element)"
return cell
}
.disposed(by: disposeBag)
② 单元格选中事件响应
- 如下所示,当点击某个单元格时将其索引位置,以及对应的标题打印出来:
选中项的indexPath为:[0, 0]
选中项的标题为:Swift
collectionView.rx.itemSelected.subscribe(onNext: { indexPath in
print("选中项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
collectionView.rx.modelSelected(String.self).subscribe(onNext: { item in
print("选中项的标题为:\(item)")
}).disposed(by: disposeBag)
collectionView.rx.itemSelected.subscribe(onNext: { [weak self] indexPath in
print("选中项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
collectionView.rx.modelSelected(String.self).subscribe(onNext: {[weak self] item in
print("选中项的标题为:\(item)")
}).disposed(by: disposeBag)
- 如果想要同时获取选中项的索引以及内容,可以如下实现:
Observable.zip(collectionView.rx.itemSelected, collectionView.rx.modelSelected(String.self))
.bind { [weak self] indexPath, item in
print("选中项的indexPath为:\(indexPath)")
print("选中项的标题为:\(item)")
}
.disposed(by: disposeBag)
③ 单元格取消选中事件响应
被取消选中项的indexPath为:[0, 4]
被取消选中项的的标题为:C++
collectionView.rx.itemDeselected.subscribe(onNext: { [weak self] indexPath in
print("被取消选中项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
collectionView.rx.modelDeselected(String.self).subscribe(onNext: {[weak self] item in
print("被取消选中项的的标题为:\(item)")
}).disposed(by: disposeBag)
Observable
.zip(collectionView.rx.itemDeselected, collectionView.rx.modelDeselected(String.self))
.bind { [weak self] indexPath, item in
print("被取消选中项的indexPath为:\(indexPath)")
print("被取消选中项的的标题为:\(item)")
}
.disposed(by: disposeBag)
④ 单元格高亮完成后的事件响应
高亮单元格的indexPath为:[0, 3]
collectionView.rx.itemHighlighted.subscribe(onNext: { indexPath in
print("高亮单元格的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
⑤ 高亮转成非高亮完成的事件响应
失去高亮的单元格的indexPath为:[0, 3]
collectionView.rx.itemUnhighlighted.subscribe(onNext: { indexPath in
print("失去高亮的单元格的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
⑥ 单元格将要显示出来的事件响应
将要显示单元格indexPath为:[0, 0]
将要显示单元格cell为:<RxSwift.MyCollectionViewCell: 0x7fe0f1112490; baseClass = UICollectionViewCell; frame = (5 0; 126 70); layer = <CALayer: 0x6000006f1a40>>
将要显示单元格indexPath为:[0, 1]
将要显示单元格cell为:<RxSwift.MyCollectionViewCell: 0x7fe0f0706be0; baseClass = UICollectionViewCell; frame = (144 0; 126 70); layer = <CALayer: 0x6000006f6360>>
将要显示单元格indexPath为:[0, 2]
将要显示单元格cell为:<RxSwift.MyCollectionViewCell: 0x7fe0f1304420; baseClass = UICollectionViewCell; frame = (283 0; 126 70); layer = <CALayer: 0x6000006bd900>>
将要显示单元格indexPath为:[0, 3]
将要显示单元格cell为:<RxSwift.MyCollectionViewCell: 0x7fe0f1304e40; baseClass = UICollectionViewCell; frame = (5 80; 126 70); layer = <CALayer: 0x6000006bd960>>
将要显示单元格indexPath为:[0, 4]
将要显示单元格cell为:<RxSwift.MyCollectionViewCell: 0x7fe0f1113c50; baseClass = UICollectionViewCell; frame = (144 80; 126 70); layer = <CALayer: 0x6000006f18a0>>
collectionView.rx.willDisplayCell.subscribe(onNext: { cell, indexPath in
print("将要显示单元格indexPath为:\(indexPath)")
print("将要显示单元格cell为:\(cell)\n")
}).disposed(by: disposeBag)
⑦ 分区头部或尾部将要显示出来的事件响应
collectionView.rx.willDisplaySupplementaryView.subscribe(onNext: { view, kind, indexPath in
print("将要显示分区indexPath为:\(indexPath)")
print("将要显示的是头部还是尾部:\(kind)")
print("将要显示头部或尾部视图:\(view)\n")
}).disposed(by: disposeBag)
二、RxDataSources
① 单分区的 CollectionView
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 70)
self.collectionView = UICollectionView(frame: self.view.frame,
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.view.addSubview(self.collectionView!)
let items = Observable.just([
SectionModel(model: "", items: [
"Swift",
"PHP",
"Python",
"Java",
"javascript",
"C#"
])
])
let dataSource = RxCollectionViewSectionedReloadDataSource
<SectionModel<String, String>>(
configureCell: { (dataSource, collectionView, indexPath, element) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(element)"
return cell}
)
items
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 70)
self.collectionView = UICollectionView(frame: self.view.frame,
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.view.addSubview(self.collectionView!)
let sections = Observable.just([
MySection(header: "", items: [
"Swift",
"PHP",
"Python",
"Java",
"javascript",
"C#"
])
])
let dataSource = RxCollectionViewSectionedReloadDataSource<MySection>(
configureCell: { (dataSource, collectionView, indexPath, element) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(element)"
return cell}
)
sections
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
struct MySection {
var header: String
var items: [Item]
}
extension MySection : AnimatableSectionModelType {
typealias Item = String
var identity: String {
return header
}
init(original: MySection, items: [Item]) {
self = original
self.items = items
}
}
- 注意:RxDataSources 是以 section 来做为数据结构的,因此不管 collectionView 是单分区还是多分区,在使用 RxDataSources 的过程中,都需要返回一个 section 的数组。
② 多分区的 CollectionView
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 70)
flowLayout.headerReferenceSize = CGSize(width: self.view.frame.width, height: 40)
self.collectionView = UICollectionView(frame: self.view.frame,
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.collectionView.register(MySectionHeader.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "Section")
self.view.addSubview(self.collectionView!)
let items = Observable.just([
SectionModel(model: "脚本语言", items: [
"Python",
"javascript",
"PHP",
]),
SectionModel(model: "高级语言", items: [
"Swift",
"C++",
"Java",
"C#"
])
])
let dataSource = RxCollectionViewSectionedReloadDataSource
<SectionModel<String, String>>(
configureCell: { (dataSource, collectionView, indexPath, element) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(element)"
return cell},
configureSupplementaryView: {
(ds ,cv, kind, ip) in
let section = cv.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "Section", for: ip) as! MySectionHeader
section.label.text = "\(ds[ip.section].model)"
return section
})
items
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 70)
flowLayout.headerReferenceSize = CGSize(width: self.view.frame.width, height: 40)
self.collectionView = UICollectionView(frame: self.view.frame,
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.collectionView.register(MySectionHeader.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "Section")
self.view.addSubview(self.collectionView!)
let sections = Observable.just([
MySection(header: "脚本语言", items: [
"Python",
"javascript",
"PHP",
]),
MySection(header: "高级语言", items: [
"Swift",
"C++",
"Java",
"C#"
])
])
let dataSource = RxCollectionViewSectionedReloadDataSource<MySection>(
configureCell: { (dataSource, collectionView, indexPath, element) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(element)"
return cell},
configureSupplementaryView: {
(ds ,cv, kind, ip) in
let section = cv.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "Section", for: ip) as! MySectionHeader
section.label.text = "\(ds[ip.section].header)"
return section
})
sections
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
struct MySection {
var header: String
var items: [Item]
}
extension MySection : AnimatableSectionModelType {
typealias Item = String
var identity: String {
return header
}
init(original: MySection, items: [Item]) {
self = original
self.items = items
}
}
三、刷新集合数据
① 刷新数据
- 如下所示:
-
- 界面初始化完毕后,collectionView 默认会加载一些随机数据;
-
- 点击右上角的刷新按钮,collectionView 会重新加载并显示一批新数据;
-
- 为方便演示,每次获取数据不是真的去发起网络请求,而是在本地生成后延迟 2 秒返回,模拟这种异步请求的情况。
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 70)
flowLayout.headerReferenceSize = CGSize(width: self.view.frame.width, height: 40)
self.collectionView = UICollectionView(frame: CGRect.init(x: 0, y: 88, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 88),
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.view.addSubview(self.collectionView!)
let randomResult = self.refreshButton.rx.tap.asObservable()
.startWith(())
.flatMapLatest(getRandomResult)
.share(replay: 1)
let dataSource = RxCollectionViewSectionedReloadDataSource
<SectionModel<String, Int>>(
configureCell: { (dataSource, collectionView, indexPath, element) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(element)"
return cell}
)
randomResult
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
func getRandomResult() -> Observable<[SectionModel<String, Int>]> {
print("正在请求数据......")
let items = (0 ..< 5).map {_ in
Int(arc4random_uniform(100000))
}
let observable = Observable.just([SectionModel(model: "S", items: items)])
return observable.delay(2, scheduler: MainScheduler.instance)
}
② 防止集合视图多次刷新
- flatMapLatest 的作用是:当在短时间内(上一个请求还没回来)连续点击多次“刷新”按钮,虽然仍会发起多次请求,但 collectionView 只会接收并显示最后一次请求,避免集合视图出现连续刷新的现象:
let randomResult = refreshButton.rx.tap.asObservable()
.startWith(())
.flatMapLatest(getRandomResult)
.share(replay: 1)
- 也可以对源头进行限制下,即通过 throttle 设置个阀值(比如 1 秒),如果在 1 秒内有多次点击则只取最后一次,那么自然也就只发送一次数据请求:
let randomResult = refreshButton.rx.tap.asObservable()
.throttle(1, scheduler: MainScheduler.instance)
.startWith(())
.flatMapLatest(getRandomResult)
.share(replay: 1)
③ 停止数据请求
- 在实际项目中我们可能会需要对一个未完成的网络请求进行中断操作,比如切换页面或者分类时,如果上一次的请求还未完成就要将其取消掉。
- 该功能简单说就是通过 takeUntil 操作符实现,当 takeUntil 中的 Observable 发送一个值时,便会结束对应的 Observable:
let randomResult = refreshButton.rx.tap.asObservable()
.startWith(())
.flatMapLatest{
self.getRandomResult().takeUntil(self.cancelButton.rx.tap)
}
.share(replay: 1)
四、样式修改
- 不管屏幕尺寸如何,collectionView 每行总是固定显示 4 个单元格,即单元格的宽度随屏幕尺寸的变化而变化,而单元格的高度为宽度的 1.5 倍:
let flowLayout = UICollectionViewFlowLayout()
self.collectionView = UICollectionView(frame: self.view.frame,
collectionViewLayout: flowLayout)
self.collectionView.backgroundColor = UIColor.white
self.collectionView.register(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "Cell")
self.view.addSubview(self.collectionView!)
let items = Observable.just([
SectionModel(model: "", items: [
"Swift",
"PHP",
"Python",
"Java",
"C++",
"C#"
])
])
let dataSource = RxCollectionViewSectionedReloadDataSource
<SectionModel<String, String>>(
configureCell: { (dataSource, collectionView, indexPath, element) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
for: indexPath) as! MyCollectionViewCell
cell.label.text = "\(element)"
return cell}
)
items
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
collectionView.rx.setDelegate(self)
.disposed(by: disposeBag)
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width
let cellWidth = (width - 30) / 4
return CGSize(width: cellWidth, height: cellWidth * 1.5)
}