9

I need to show a progress bar upon an API call and hide it after the API call completes. The following is code I have written to populate a table. Where should I make calls to show and hide progress for the API being called? Is there an RxSwift way of doing this?

items = fetchAllAnswers()
items.bindTo(self.myTableView.rx_itemsWithCellIdentifier("cellIdentifier", cellType: UITableViewCell.self)){ (row, element, cell) in
    cell.textLabel?.text = element
}
.addDisposableTo(disposeBag)

func fetchAllAnswers() -> Observable<[String]>{
    let api = Observable.create { (obsever: AnyObserver<[String]>) -> Disposable in
        //progress.show()
        let items = Api.getUsers()

        obsever.onNext(items)
        obsever.onCompleted()
        //progress.hide
        return AnonymousDisposable{
            print("api dispose called")
        }
    }
    return api
}
solidcell
  • 7,639
  • 4
  • 40
  • 59

4 Answers4

8

You may to use ActivityIndicator from RxSwift repo. I using MBProgressHUD in my project. At first you need to create extension for this library:

extension MBProgressHUD {

    /**
     Bindable sink for MBProgressHUD show/hide methods.
     */
    public var rx_mbprogresshud_animating: AnyObserver<Bool> {
        return AnyObserver { event in
            MainScheduler.ensureExecutingOnScheduler()

            switch (event) {
            case .Next(let value):
                if value {
                    let loadingNotification = MBProgressHUD.showHUDAddedTo(UIApplication.sharedApplication().keyWindow?.subviews.last, animated: true)
                    loadingNotification.mode = self.mode
                    loadingNotification.labelText = self.labelText
                    loadingNotification.dimBackground = self.dimBackground
                } else {
                    MBProgressHUD.hideHUDForView(UIApplication.sharedApplication().keyWindow?.subviews.last, animated: true)
                }
            case .Error(let error):
                let error = "Binding error to UI: \(error)"
                #if DEBUG
                    rxFatalError(error)
                #else
                    print(error)
                #endif
            case .Completed:
                break
            }
        }
    }
}
import RxSwift
import RxCocoa

extension Reactive where Base: MBProgressHUD {
    public var animation: Binder<Bool> {
        return Binder(self.base) { hud, show in
            guard let view = UIApplication.shared.keyWindow?.subviews.last()! else { return }
            if show {
                if hud.superview == nil {
                    view.addSubview(hud)
                }
                hud.show(animated: true)
            } else {
                hud.hide(animated: true)
            }
        }
    }
}

Next you need to create ActivityIndicator object in your ViewController class:

let progress = MBProgressHUD()
progress.mode = MBProgressHUDMode.Indeterminate
progress.labelText = "Loading..."
progress.dimBackground = true

let indicator = ActivityIndicator()
indicator.asObservable()
    .bindTo(progress.rx_mbprogresshud_animating)
    .addDisposableTo(bag)

Next just use trackActivity() function into your sequences:

apiMethod
.trackActivity(indicator)
.subscribeNext { stringArray in 
    items.value = stringArray
}
.addDisposableTo(bag)
Community
  • 1
  • 1
Svyatoslav
  • 1,350
  • 12
  • 19
  • by binding to to 'ProgressHUD' in 'viewDidLoad' it start showing it. Before calling 'trackActivity' – Hamdullah shah Jul 01 '16 at 03:01
  • 'MBProgressHUD' start showing only when 'trackActivity' was called. I'm using it in all of my projects. If 'MBProgressHUD' showing before calling 'trackActivity' maybe you doing something wrong. You can to check my RxSwift tutorial https://github.com/svyatoslav-reshetnikov/ReactiveApp and see how I implemented it. – Svyatoslav Jul 01 '16 at 08:27
1

You have do this work in your ViewController, something like this:

var disposeBag = DisposeBag()

...

items = fetchAllAnswers()
      .subscribeOn(backgroundWorkScheduler)
      .observeOn(mainScheduler)
      .subscribe(
                onNext: { data in
                    print("onNext")
                    //show/update progress
                },
                onCompleted: {
                    print("onCompleted")
                    //hide progress
                },
                onDisposed: {
                    print("onDisposed")
                }
       ).addDisposableTo(disposeBag)
Sergey Neskoromny
  • 1,188
  • 8
  • 15
1

Swift 4

extension MBProgressHUD {
var rx_mbprogresshud_animating: AnyObserver<Bool> {

    return AnyObserver { event in

        MainScheduler.ensureExecutingOnScheduler()

        switch (event) {
        case .next(let value):
            if value {
                let loadingNotification = MBProgressHUD.showAdded(to: (UIApplication.shared.keyWindow?.subviews.last)!, animated: true)
                loadingNotification.mode = self.mode
                loadingNotification.label.text = self.label.text
            } else {
                MBProgressHUD.hide(for: (UIApplication.shared.keyWindow?.subviews.last)!, animated: true)
            }
        case .error(let error):
            let error = "Binding error to UI: \(error)"
            print(error)
        case .completed:
            break
        }
      }
    }
  }
1

swift 4.2 && swift 5

// MBProgressHUD+Rx.swift
import RxSwift
import RxCocoa

extension Reactive where Base: MBProgressHUD {
    public var animation: Binder<Bool> {
        return Binder(self.base) { hud, show in
            guard let view = UIApplication.shared.keyWindow else { return }
            if show {
                if hud.superview == nil {
                    view.addSubview(hud)
                }
                hud.show(animated: true)
            } else {
                hud.hide(animated: true)
            }
        }
    }
}

use

// hud init
private var hud: MBProgressHUD = {
    let progress = MBProgressHUD()
    progress.mode = MBProgressHUDMode.indeterminate
    progress.label.text = ""
    progress.backgroundColor = UIColor.black.withAlphaComponent(0.5)
    return progress
}()
// track
some_action.flatMapLatest{
    return some_operation1.trackActivity(hudModeIndicator)
}.flatMapLatest{
    return some_operation2.trackActivity(hudModeIndicator)
}.drive(onNext: {}).disposed(by: rx.disposeBag)

// bind
hudModeIndicator
    .drive(self.hud.rx.animation).disposed(by: rx.disposeBag)

reference

ActivityIndicator.swift

Community
  • 1
  • 1
phil zhang
  • 31
  • 3