【Swift】UIActivityIndicatorが表示されない場合の対処法

画像を保存したり削除したりするときに待ち時間が発生してしまうのでIndicatorを導入

スクリーンショット-2016-01-09-18.10.00

よくみるクルクルするやつです。

 

UIActivityIndicatorの設置方法は超簡単。

var myIndicator = UIActivityIndicatorView()
myIndicator.frame = CGRectMake(0,0,150,150)
myIndicator.center = self.view.center
//アニメーションが止まった時にindecatorを隠すかどうか
myIndicator.hidesWhenStopped = false
myIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.White
myIndicator.startAnimating()
self.view.addSubview(myIndicator)

 

これだけで設置できちゃいます。

しかしIndicatorは常に表示しておくものでもありません。OnOffが必要なものです。

私はこれをメソッド化して必要なときに呼び出すようにしました。

Indicatorと「削除中」ラベルの表示を1つのメソッドに

    var myIndicator:UIActivityIndicatorView!
    func Indicator(){
        myIndicator = UIActivityIndicatorView()
        myIndicator.frame = CGRectMake(0,0,150,150)
        myIndicator.backgroundColor = UIColor(red:0,green:0,blue:0,alpha:0.5)
        myIndicator.layer.cornerRadius = 20.0
        myIndicator.center = self.view.center
        myIndicator.hidesWhenStopped = false
        myIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.White
        myIndicator.startAnimating()
        self.view.addSubview(myIndicator)
        let label = UILabel()
        label.frame = CGRectMake(0,0,150,30)
        label.layer.position = CGPoint(x:myIndicator.frame.width / 2.0 , y:myIndicator.frame.height / 2.0 + 50.0)
        label.text = "削除中"
        label.textAlignment = NSTextAlignment.Center
        label.font = UIFont.systemFontOfSize(12)
        label.textColor = UIColor.whiteColor()
        myIndicator.addSubview(label)
    }

 

使用するときは

Indicatorの表示 → 削除処理 → Indicatorを非表示(removeSuperviewで取り除く)

の順で使用しました。

 

Indicatorが表示されない・・・?

ここで適当にボタンを用意しておきます

let btn = UIButton()
btn.frame = CGRectMake(100,100,50,50)
btn.addTarget(self,action:"btnTapped:",forControlEvents:.TouchUpInside)
self.view.addSubview(btn)

それでこのボタンを押すとIndicatorを表示して画像の削除をする、という処理を書いていきます。

func btnTapped(sender:UIButton){
      Indicator()
      trash()
}

trashメソッドに削除処理を書いていますがそこは割愛

 

これだと何故かIndicatorが表示されません。。。。trashに待ち時間が発生するから〜なのだと思いますが。

別のところで同様に使用してみたら動いたり動かなかったり・・・ムムム

 

対処法

待ち時間のある処理を別スレッドで行うことで解決(こちらは非推奨。下記参照)

func btnTapped(sender:UIButton){
      Indicator()
      self.performSelector("TrashProcessDuringIndicator", withObject: nil, afterDelay: 0.1)
}
func TrashProcessDuringIndicator(){
     //待ち時間のある処理をここで行う
     trash()
}

 

performSelector:withObject:afterDelayを使用することで解決〜

 

trash(){
     //削除処理を書いています
        ~~~~

  myIndicator.removeFromSuperview()
}

削除処理が完了したらremoveFromSuperviewでindicatorを取り除いています。

 

performSelectorよりdispatch_afterを使った方が良い?!

さてここまで書いておいてperformSelector系はメモリ管理で危うい所があるらしいです。

そしてperformSelectorの代わりにdispatch_afterを使う処理の書き方も紹介しておきます。(間違っているかもしれませんが・・・多分大丈夫なハズ・・・)

func btnTapped(sender:UIButton){
      Indicator()
      //performSelectorは消しておきまよう
      //self.performSelector("TrashProcessDuringIndicator", withObject: nil, afterDelay: 0.1)
      let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
      dispatch_after(delayTime, dispatch_get_main_queue()) {
             self.TrashProcessDuringIndicator()
      }
}
func TrashProcessDuringIndicator(){
     //待ち時間のある処理をここで行う
     trash()
}

 

こんな感じになりました。

イマイチ使い方が分かっていない所もありますが・・・

何故遅延処理をしたら良いのか〜とかもちゃんと勉強しておきます(;;)

 

 

2018/04/14追記 とりあえずSwift4で使ってみた
ウーーーーン。趣旨と変わるけれど一応ちょっとだけ使ってみた。

import UIKit

class SecondViewController: UIViewController {

    var indicator:UIActivityIndicatorView!
    var count = 5
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let width = self.view.frame.width
        let height = self.view.frame.height
        
        indicator = UIActivityIndicatorView(frame: CGRect(x:0,y:0,width:50,height:50))
        indicator.center = CGPoint(x:width / 2,y:height / 2)
        indicator.hidesWhenStopped = true
        indicator.backgroundColor = UIColor.blue
        self.view.addSubview(indicator)
        indicator.startAnimating()
        //重い処理
        let timer = Timer.scheduledTimer(timeInterval: 1,
                                         target: self,
                                         selector: #selector(self.timerAction(sender:)),
                                         userInfo: nil,
                                         repeats: true)
        timer.fire()
    }

    @objc func timerAction(sender:Timer) {
        count -= 1
        if count == 0 {
            //indicatorを隠す
            self.indicator.stopAnimating()
            sender.invalidate()
        }
    }
}
タイマー動かして5秒間表示して、0になったらインジケータを隠す感じ。 前までhidesWhenStoppedをアニメーションが終わったら隠すじゃなくて、隠れたらアニメーションが終わるみたいなおかしなことが書かれていたので修正しました。 最近でもindicatorはあんまり得意ではないです。 非同期処理と組み合わせて使ってると表示されないってのは最近でもあった気がするけれど、とりあえずまた再現したら追記することにします。 今回はこれで失礼します。
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS
more