Swift - RxSwift的使用详解68(监听滚动条滚动到底部的行为:reachedBottom)
作者:hangge | 2018-06-18 08:10
有时我们需要监测滚动条是否滚动到底部,如果滚到底的话则自动执行相应的操作。比如:表格一开始只加载一部分数据并显示,当滚动显示到最后一行时,又会自动加载更多的数据。下面通过样例演示如何实现这个功能。
一、扩展 UIScrollView
(1)为保证通用性,我们首先对 UIScrollView 进行 Rx 扩展(UIScrollView+Rx.swift),增加一个 reachedBottom 的方法。该方法返回一个可观察序列,该序列不断监听滚动条的位置变化,每当视图滚到底或者超出时便会发出事件。
(2)由于是对 UIScrollView 进行扩展,所以该方法对 UIScrollView 的子类(如:UITableView、UICollectionView)也是有效的。
import UIKit import RxSwift import RxCocoa extension Reactive where Base: UIScrollView { //视图滚到底部检测序列 var reachedBottom: Signal<()> { return contentOffset.asDriver() .flatMap { [weak base] contentOffset -> Signal<()> in guard let scrollView = base else { return Signal.empty() } //可视区域高度 let visibleHeight = scrollView.frame.height - scrollView.contentInset.top - scrollView.contentInset.bottom //滚动条最大位置 let threshold = max(0.0, scrollView.contentSize.height - visibleHeight) //如果当前位置超出最大位置则发出一个事件 let y = contentOffset.y + scrollView.contentInset.top return y > threshold ? Signal.just(()) : Signal.empty() } } }
二、使用样例
1,效果图
(1)页面初始化完毕后,自动生成 20 条随机数据显示到表格中。
(2)当表格滑动到底部时,继续生成 20 条新数据并拼接到原数据的下方显示。
(3)当表格再次滑动到底部时,重复上面的动作。
2,样例代码
注意:这里我使用了 flatMapFirst 操作符防止在数据没有返回的时候,多次上拉造成重复请求。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { //表格数据序列 let tableData = BehaviorRelay<[String]>(value: []) var tableView:UITableView! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //创建表格视图 self.tableView = UITableView(frame: self.view.frame, style:.plain) //创建一个重用的单元格 self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") self.view.addSubview(self.tableView!) //单元格数据的绑定 self.tableData.asDriver() .drive(tableView.rx.items) { (tableView, row, element) in let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! cell.textLabel?.text = "\(row+1)、\(element)" return cell } .disposed(by: disposeBag) //上拉加载数据 self.tableView.rx.reachedBottom.asObservable() .startWith(()) //初始化完毕时会自动加载一次数据 .flatMapFirst(getRandomResult) //防止重复加载 .subscribe(onNext: { [weak self] items in if let tableData = self?.tableData { tableData.accept(tableData.value + items ) } }) .disposed(by: disposeBag) } //获取随机数据 func getRandomResult() -> Driver<[String]> { print("正在请求数据......") //随机生成20条数据 let items = Array(0..<20).map{ _ in "随机条目\(arc4random())"} let observable = Observable.just(items) return observable .delay(2, scheduler: MainScheduler.instance) .asDriver(onErrorDriveWith: Driver.empty()) } }
附:功能改进
上面的样例在加载数据的时候界面没有任何提示,用户体验不好。下面对此进行优化。
1,效果图
(1)如果表格正在加载数据,表格尾部会有一个活动指示器。
(2)当请求结束后活动指示器自动消失。
2,样例代码
主要是加了个名为 isLoading 的序列,表示当前是否正在加载数据。该序列用来控制是否需要发送请求,以及活动指示器是否显示。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { //表格数据序列 let tableData = BehaviorRelay<[String]>(value: []) //当前是否正在加载序列 var isLoading = BehaviorRelay<Bool>(value: false) var tableView:UITableView! //表格底部用来提示数据加载的视图 var loadMoreView:UIView! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //创建表格视图 self.tableView = UITableView(frame: self.view.frame, style:.plain) //创建一个重用的单元格 self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") self.view.addSubview(self.tableView!) //初始化表格尾部的上拉刷新视图 self.setupInfiniteScrollingView() //单元格数据的绑定 self.tableData.asDriver() .drive(tableView.rx.items) { (tableView, row, element) in let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! cell.textLabel?.text = "\(row+1)、\(element)" return cell } .disposed(by: disposeBag) //表格尾部的上拉加载视图显示绑定 self.isLoading.asDriver() .drive(onNext: { if $0 { self.tableView.tableFooterView = self.loadMoreView }else{ self.tableView.tableFooterView = nil } }) .disposed(by: disposeBag) //上拉加载数据 self.tableView.rx.reachedBottom.asObservable() .startWith(()) //初始化完毕时会自动加载一次数据 .flatMapFirst(getRandomResult) //防止重复加载 .subscribe(onNext: { [weak self] items in if let tableData = self?.tableData { tableData.accept(tableData.value + items ) } self?.isLoading.accept(false) }) .disposed(by: disposeBag) } //获取随机数据 func getRandomResult() -> Driver<[String]> { print("正在请求数据......") self.isLoading.accept(true) //随机生成20条数据 let items = Array(0..<20).map{ _ in "随机条目\(arc4random())"} let observable = Observable.just(items) return observable .delay(3, scheduler: MainScheduler.instance) .asDriver(onErrorDriveWith: Driver.empty()) } //上拉加载视图 private func setupInfiniteScrollingView() { self.loadMoreView = UIView(frame: CGRect(x:0, y:self.tableView.contentSize.height, width:self.tableView.bounds.size.width, height:40)) self.loadMoreView!.autoresizingMask = .flexibleWidth //添加中间的环形进度条 let activityViewIndicator = UIActivityIndicatorView(activityIndicatorStyle: .white) activityViewIndicator.color = .darkGray let indicatorX = self.loadMoreView!.frame.size.width/2 - activityViewIndicator.frame.width/2 let indicatorY = self.loadMoreView!.frame.size.height/2 - activityViewIndicator.frame.height/2 activityViewIndicator.frame = CGRect(x:indicatorX, y:indicatorY, width:activityViewIndicator.frame.width, height:activityViewIndicator.frame.height) activityViewIndicator.startAnimating() self.loadMoreView!.addSubview(activityViewIndicator) } }
全部评论(0)