M-Miyazako
2/6/2020 - 7:05 AM

インジケータ

インジケータ

要RxSwift

import RxSwift
import UIKit

final class Indicator {
    /// シングルトン
    static let shared = Indicator()

    /// モーダル用のView
    private let modal = UIView()

    /// インジケーター
    private let indicator = UIActivityIndicatorView()

    /// 非同期スケジューラ
    private let backgroundScheduler = ConcurrentDispatchQueueScheduler(qos: .background)

    /// 初期処理
    private init() {
        indicator.style = .whiteLarge
        indicator.color = Theme.Colors.black
        modal.addSubview(indicator)
        setModalStyle()
    }

    /// モーダルウィンドウ風のデザインにする
    private func setModalStyle() {
        indicator.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        indicator.layer.cornerRadius = 15
        indicator.backgroundColor = Theme.Colors.indicatorBackground
        modal.backgroundColor = Theme.Colors.modalBackground
    }

    /// インジケーターを表示する
    ///
    /// - Parameter target: インジケーター表示対象のView
    func show(target: UIView? = UIApplication.topViewController()?.view) {
        guard let target = target else {
            return
        }

        target.addSubview(modal)
        target.bringSubviewToFront(modal)
        modal.frame = target.frame
        indicator.center = modal.center

        fadeIn { [weak self] in
            self?.indicator.startAnimating()
        }
    }

    /// インジケーターを非表示にする
    func dismiss() {
        fadeOut { [weak self] in
            self?.indicator.stopAnimating()
            self?.modal.removeFromSuperview()
        }
    }

    /// フェードイン
    ///
    /// - Parameter before: フェードイン前の処理
    private func fadeIn(before: () -> Void) {
        before()
        modal.alpha = 0.0
        UIView.animate(withDuration: 0.3, animations: { [weak self] in
            self?.modal.alpha = 1.0
        })
    }

    /// フェードアウト
    ///
    /// - Parameter after: フェードアウト後の処理
    private func fadeOut(after: @escaping () -> Void) {
        modal.alpha = 1.0
        UIView.animate(withDuration: 0.3, animations: { [weak self] in
            self?.modal.alpha = 0.0
        }, completion: { _ in
            after()
        })
    }

    /// 非同期タスクの作成
    ///
    /// - Parameter target: インジケーター表示対象のView
    /// - Parameter task: タスク
    /// - Returns: 非同期タスク
    func createAsyncTask<T>(target: UIView? = UIApplication.topViewController()?.view,
                            task: @escaping () throws -> T) -> Observable<T> {
        let observable = Observable<T>.create { observer in
            do {
                observer.on(.next(try task()))
                observer.on(.completed)
            } catch {
                observer.on(.error(error))
            }
            return Disposables.create()
        }
        return createAsyncTask(target: target, observable: observable)
    }

    /// 非同期タスクの作成
    ///
    /// - Parameters:
    ///   - target: インジケーター表示対象のView
    ///   - observable: タスク
    /// - Returns: 非同期タスク
    func createAsyncTask<T>(target: UIView? = UIApplication.topViewController()?.view,
                            observable: Observable<T>) -> Observable<T> {
        return observable
            .subscribeOn(backgroundScheduler)
            .observeOn(MainScheduler.instance)
            .do(onSubscribe: { [weak self] in
                self?.show(target: target)
                }, onDispose: { [weak self] in
                    self?.dismiss()
            })
    }
}