jacks205 jacks205 - 5 months ago 18
iOS Question

Binding to a UIRefreshControl after network call

I am new to RxSwift and I was wondering how I would be able to "reactively" use a UIRefreshControl with a UITableView instead of the normal way of creating a target, and manually calling

beginRefreshing()
and
endRefreshing()
.

For instance, say I am loading some strings from an API:

class TableViewController: UITableViewController {

var data : [String] = []

let db = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()

refreshControl = UIRefreshControl()

//I don't want to use
//refreshControl?.addTarget(self, action: #selector(getData), forControlEvents: .ValueChanged)

//Do something to refreshControl.rx_refreshing?
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let str = data[indexPath.row]
cell.textLabel?.text = str
return cell
}

//MARK: - Requests

private func getData() {
let myData = MyAPI.getData() //Returns Observable<[String]>
myData
.subscribe({ [weak self] (event) in
switch event {
case .Next(let strings):
self?.data = strings
self?.tableView.reloadData()
break
case .Error(let err):
print(err)
break
case .Completed:
break
}
// self?.refreshControl?.endRefreshing()
})
.addDisposableTo(db)
}
}


MyAPI
sends a request for some string values, how can I bind the
refreshControl
to call
getData()
and also stop refreshing when it's finished (or error'd) the network request? Do I need to bind to refreshControl.rx_refreshing?

Answer

RxSwift's example app provides an interesting class to handle this kind of logic: ActivityIndicator.

Once you have ActivityIndicator in, code for binding rx_refreshing to the request becomes really easy.

let activityIndicator = ActivityIndicator()

override func viewDidLoad() {
  super.viewDidLoad()

  refreshControl = UIRefreshControl()

  // When refresh control emits .ValueChanged, start fetching data
  refreshControl.rx_controlEvent(.ValueChanged)
    .flatMapLatest { [unowned self] _ in
      return self.getData()
      .trackActivity(activityIndicator)
    }
    .subscribeNext { [unowned self] strings in
      self.data = strings
      self.tableView.reloadData()
    }
    .addDisposableTo(db)

  // Bind activity indicator true/false to rx_refreshing
  activityIndicator.asObservable()
    .bindTo(refreshControl.rx_refreshing)
    .addDisposableTo(db)
}

// getData only needs to return an observable, subscription is handled in viewDidLoad
private func getData() -> Observable<[String]> {
  return myData = MyAPI.getData() //Returns Observable<[String]>
}