処理中インジケータを表示して、UI操作を抑止する

エンジニアの德光です。 あまり労力をかけずに、(そこそこ)ちゃんとしたアプリを開発するための小技の第二弾です。…

エンジニアの德光です。
あまり労力をかけずに、(そこそこ)ちゃんとしたアプリを開発するための小技の第二弾です。

◆進捗くるくる表示

通信処理など、時間がかかる処理をするときなどに、簡単に処理中表示をしたいという状況はよくあります。
このような処理をするボタンを連打されてしまうのも、なるべく防いでおきたいですよね。
そんなときには、簡易進捗ダイアログを表示して対応しましょう。

▼実現方法:

とりあえず最低限の機能を考えます。
 1) 画面中央に処理中を表す「何か」を表示する
 2) 処理中には、他のUI操作を受け付けない
 3) 処理が終わったら「何か」を消す

1) 画面中央に処理中を表す「何か」を表示する

 ・表示させるためのコードが長いのは嫌だ。(いちいち生成させて管理するのは面倒くさい)
  →シングルトンで作っちゃえ!
 ・「何か」は、用意されている「UIActivityIndicatorView」を使う
  →3種類の見た目が選べるけど、大きめのものは「Large White」しかない
   →白いインジケータを目立たせるために背景を黒っぽくしておかないと……

2) 処理中には、他のUI操作を受け付けない

 ・最前面の画面全体をViewで覆って、UI操作を奪えば良い
  →「isUserInteractionEnabled」を「true」にしてUI操作を奪いつつ何もさせなければ良い
 ・背景色を透明寄りの黒色にしておけば、さきほどのインジケータも目立つし、UI操作を無視する見た目になって良い感じ!

3) 処理が終わったら「何か」を消す

 ・全面を覆っていたものをremoveFromSuperviewで除去る

▼実装:

Xcodeでカスタム部品のクラスを生成しておきます

・【BusyIndicator.swift】【BusyIndicator.xib】の2ファイルを追加します。
 Xcodeメニュー[File]->[New]->[File]でプロジェクトにファイルを追加していきます。(ProjectNavigatorにて「New File…」でも構いませんよ?)
 「Source」の中から「Cocoa Touch Class」を選び、サブクラスにUIViewを指定して〔Next〕ボタンで【BusyIndicator.swift】を追加します。
 「User Interface」の中から「View」を選び、「Save As:」に「BusyIndicator」と入力して〔Create〕ボタンで【BusyIndicator.xib】を追加します。

InterfaceBuilderで部品を作成します

InterfaceBuilderで部品を配置してみた

・全面を覆う背景としてのUIViewを置き、黒色の透明度20%にします。
 「User Interaction Enabled」をチェックしておき、UI操作を受け付けるようにしておきます。
 (これで、この部品が表示されれば、その下位に表示されているUI操作が無視されるようになります)
・インジケータ背景としてのUIViewを置き、黒色の透明度40%にします。また、中央配置にしておきます。
 「User Defined Runtime Attributes」に「cornerRadius」を追加して、「Number」で「16」を設定しておく。
 (これで、インジケータ背景が角丸表示になり、ちょっと見た目がマシになります)
・インジケータ背景の子供として UIActivityIndicatorViewを置き、中央配置にしておきます。
 スタイルとして「LargeWhite」を選択します。またBehaviorで「Animating」もチェックしておきます。
・「File’s Owner」のカスタムクラスとして、Class項目に「BusyIndicator」を設定しておきます。

BusyIndicatorクラスの定義をします

・さきほど用意しておいたUI部品との関連づけをします。

    //=== 基本的な見た目などの初期化
    private func commonInit() {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "BusyIndicator", bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
        self.addSubview(view)

        view.translatesAutoresizingMaskIntoConstraints = false
        let bindings = ["view": view]
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings))
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings))
    }

・シングルトンで生成できるるようにします。

    //=== シングルトンで生成する
    static let sharedManager: BusyIndicator = {
        let instance = BusyIndicator()
        instance.commonInit()	//基本的な見た目などの初期化
        return instance
    }()

・このBusyIndicatoの表示・非表示用のメソッド定義する

    //=== 表示・非表示の制御
    func show() {
        self.frame = UIScreen.main.bounds //画面全体を覆わせるため
        let vc = UIApplication.shared.keyWindow?.rootViewController
        vc.view.addSubview(self)    //ビューを追加して表示させる
    }
    func dismiss() {
        self.removeFromSuperview()  //ビューを除去して非表示にする
    }

使ってみる!

・適当なボタンを配置して、下記のコードでくるくるを表示させてみます。
 表示開始させた3秒後に表示終了も呼んでいます。
 意図通りに動作したならば、表示終了のボタンを用意しても押せないわけですからね。

        BusyIndicator.sharedManager.show()//くるくる表示開始
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            BusyIndicator.sharedManager.dismiss()//くるくる表示終了
        }

・とりあえず良さげに動きましたが、画面回転に追随しないなどの問題があります。
 それについては、また次回!