5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RxSwift bind(to: ) を使ってIndicatorViewを回す。

Last updated at Posted at 2018-10-10

RxSwift Binder を使ってみた

overview

RxSwift のBinderでSVProgressHUDを使ってみる。

Rxswift のgit hubによしなに書かれてる、サンプルがあり、slackでもそれを参考にしてくれと記載されている。

その部分を抜粋し、MVVMで使ってみた。

これまで、show と dismissを使ってやっていた箇所を簡単に記述することができる。

Sample code

ViewController が下記である。


//
//  ViewController.swift
//  RxDemo
//
//  Created by Shichimitoucarashi on 2018/08/11.
//  Copyright © 2018年 karadanote. All rights reserved.
//

import UIKit
import RxSwift
import RxCocoa
import SHColor
import SVProgressHUD

final class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var label:UILabel!
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var indicator: UIActivityIndicatorView!
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        let viewModel = ViewModel(tap: button.rx.tap.asObservable())
        
        viewModel.indicator
            .bind(to: SVProgressHUD().shared.rx.isShow)
            .disposed(by: disposeBag)
        
        viewModel.action
            .subscribe()
            .disposed(by: disposeBag)
    }
}

extension Reactive where Base: UIActivityIndicatorView {
    
    var isAnimating :Binder<Bool>{
        return Binder(self.base) { indicator, flg in
            if flg {
                indicator.isHidden = false
                indicator.startAnimating()
            }else{
                indicator.isHidden = true
                indicator.stopAnimating()
            }
        }
    }
}

extension Reactive where Base: SVProgressHUD {
    
    var isShow: Binder<Bool> {
        return Binder(self.base) { sv, flg in
            if flg {
                SVProgressHUD.show()
            }else{
                SVProgressHUD.dismiss()
            }
        }
    }
}

private struct ActivityToken<E> : ObservableConvertibleType, Disposable {
    private let _source: Observable<E>
    private let _dispose: Cancelable
    
    init(source: Observable<E>, disposeAction: @escaping () -> ()) {
        _source = source
        _dispose = Disposables.create(with: disposeAction)
    }
    
    func dispose() {
        _dispose.dispose()
    }
    
    func asObservable() -> Observable<E> {
        return _source
    }
}

/**
 Enables monitoring of sequence computation.
 
 If there is at least one sequence computation in progress, `true` will be sent.
 When all activities complete `false` will be sent.
 */
public class ActivityIndicator : SharedSequenceConvertibleType {
    public typealias E = Bool
    public typealias SharingStrategy = DriverSharingStrategy
    
    private let _lock = NSRecursiveLock()
    private let _relay = BehaviorRelay(value: 0)
    private let _loading: SharedSequence<SharingStrategy, Bool>
    
    public init() {
        _loading = _relay.asDriver()
            .map { $0 > 0 }
            .distinctUntilChanged()
    }
    
    fileprivate func trackActivityOfObservable<O: ObservableConvertibleType>(_ source: O) -> Observable<O.E> {
        return Observable.using({ () -> ActivityToken<O.E> in
            self.increment()
            return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
        }) { t in
            return t.asObservable()
        }
    }
    
    private func increment() {
        _lock.lock()
        _relay.accept(_relay.value + 1)
        _lock.unlock()
    }
    
    private func decrement() {
        _lock.lock()
        _relay.accept(_relay.value - 1)
        _lock.unlock()
    }
    
    public func asSharedSequence() -> SharedSequence<SharingStrategy, E> {
        return _loading
    }
}

extension ObservableConvertibleType {
    public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<E> {
        return activityIndicator.trackActivityOfObservable(self)
    }
}

ViewModelを下記に記載する。


//
//  ViewModel.swift
//  RxDemo
//
//  Created by Shichimitoucarashi on 2018/10/10.
//  Copyright © 2018年 karadanote. All rights reserved.
//

import Foundation
import RxSwift
import RxCocoa

class ViewModel {
    
    let action: Observable<Bool>
    let indicator: Observable<Bool>
    
    init(tap: Observable<Void>) {
        
        let indicator = ActivityIndicator()
        indicator = indicator.asObservable()
        
        action = tap.flatMapLatest { _ in
            return ViewModel.variable.observeOn(MainScheduler.instance)
                .catchErrorJustReturn(false)
                .trackActivity(indicator)
        }
    }
    static var variable: Observable<Bool> {
        return Observable.just(false).delay(1.0, scheduler: MainScheduler.instance)
    }
    
}

今回の処理でもっとも重要なのは、trackActivityである、これがtracking してくれる便利な関数である。

下記に記したクラスが有効に働くことで、現在処理が走ってるか、走っていないかの判断を行うことができる。

ActivityIndicator <- このクラスが排他的制御を行なっている、

overview

処理が走ったら、インクリメントを行い、そうでない場合はデクリメントを行い、処理がひとつも走ってなければ、

falseを返し、一つでも走ってれば、trueを返すという仕組みであるので、処理が走ってれば、インジケーターを回し、そうでなければ、止めるという処理をワンライナーでかける、

最初はインジケーターをまわすのに、Bool値のプロパティーを作成するのはおかしいと感じましたが、

こういう使い方をするのでれば、これはこれで結構優れたものだと思える。


private struct ActivityToken<E> : ObservableConvertibleType, Disposable {
    private let _source: Observable<E>
    private let _dispose: Cancelable
    
    init(source: Observable<E>, disposeAction: @escaping () -> ()) {
        _source = source
        _dispose = Disposables.create(with: disposeAction)
    }
    
    func dispose() {
        _dispose.dispose()
    }
    
    func asObservable() -> Observable<E> {
        return _source
    }
}

/**
 Enables monitoring of sequence computation.
 
 If there is at least one sequence computation in progress, `true` will be sent.
 When all activities complete `false` will be sent.
 */
public class ActivityIndicator : SharedSequenceConvertibleType {
    public typealias E = Bool
    public typealias SharingStrategy = DriverSharingStrategy
    
    private let _lock = NSRecursiveLock()
    private let _relay = BehaviorRelay(value: 0)
    private let _loading: SharedSequence<SharingStrategy, Bool>
    
    public init() {
        _loading = _relay.asDriver()
            .map { $0 > 0 }
            .distinctUntilChanged()
    }
    
    fileprivate func trackActivityOfObservable<O: ObservableConvertibleType>(_ source: O) -> Observable<O.E> {
        return Observable.using({ () -> ActivityToken<O.E> in
            self.increment()
            return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
        }) { t in
            return t.asObservable()
        }
    }
    
    private func increment() {
        _lock.lock()
        _relay.accept(_relay.value + 1)
        _lock.unlock()
    }
    
    private func decrement() {
        _lock.lock()
        _relay.accept(_relay.value - 1)
        _lock.unlock()
    }
    
    public func asSharedSequence() -> SharedSequence<SharingStrategy, E> {
        return _loading
    }
}

extension ObservableConvertibleType {
    public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<E> {
        return activityIndicator.trackActivityOfObservable(self)
    }
}

上記実装方法に意見があるものはいつでもコメント欄に投稿していただけると幸いである。

ご連絡お待ちしております。

引用元

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?