Search code examples
iosswiftuirefreshcontrolrx-swift

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?


Solution

  • 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]>
    }