UILabelを1文字ずつアニメーションさせる


# はじめに

UILabelを1文字ずつ動かして、ゲームのローディング画面っぽい表示をさせてみる。

完成図は下記の画像のような感じ↓

![Untitled.gif](https://qiita-image-store.s3.amazonaws.com/0/31823/59d14275-43ce-3b6d-a8a6-2abaf3c52445.gif "Untitled.gif")

# 実装

順を追って説明していきます。
今回はswiftで実装します。

## クラスの作成

UIViewのサブクラスを作成します。
(`AnimationLabel`だとUILabelのサブクラスっぽい印象があるので、`AnimationLabelView`という名前にしました)

```swift:
import UIKit

class AnimationLabelView: UIView {

}
```

## プロパティの追加

クラスのプロパティをUILabelに合わせる(似せる)ことで違和感無く使用することができます。
使用イメージとしては、UILabelを使う感覚でオブジェクトを生成したあと、最後にアニメーション用のメソッドを呼んでもらう感じにしたい。
外部から操作されたくない変数は全部privateにしまっちゃいます。

```swift:
//MARK: - Public Property
var roopCount : NSInteger = 0 //アニメーションのループ回数。0で無限ループとする
var text : NSString = ""
var font : UIFont = UIFont.boldSystemFontOfSize(17)
var textColor : UIColor = UIColor.blackColor()
var animationOffset : CGFloat = 5 // ラベルが上にバウンスする量(ピクセル)
var animationDuration : Double = 0.2 // ラベル1つあたりのアニメーション時間
var animationDelay : Double = 0.1 // ラベル間のアニメーションの時差

//MARK: - Private Property
private var contentView : UIView = UIView()
private var stopAnimationFlag : Bool = false
private var currentRoopCount : NSInteger = 0
private var animationIndex : NSInteger = 0
private var labelArray : Array<UILabel> = Array<UILabel>()

```

## 定数

ラベル間のマージンを定数で置いておきます。
(プロパティから操作できるようにしても良いかも)

```swift:
private let LABEL_MARGIN : CGFloat = 1.0
``` 

## クラスメソッドの追加

クラスメソッドは極力シンプルかつ、少なくしておきましょう。

```swift:
//MARK: - Class Method
func startAnimation(){
    self.reloadView()
    self.executeAnimation()
}

func stopAnimation(){
    self.stopAnimationFlag = false
}

```

## プライベートメソッドの追加
### Viewの実装

```swift:
//MARK: - Private Method

//パラメータ類の初期化
private func initializeView(){
    for subView : UILabel in self.labelArray {
        subView.removeFromSuperview()
    }
    labelArray = Array<UILabel>()
    currentRoopCount = 0
    stopAnimationFlag = false

    contentView = UIView(frame: CGRectMake(0, 0, 10, 10))
    contentView.backgroundColor = UIColor.clearColor()
    self.addSubview(contentView)
}

//Viewの描画
private func reloadView(){
    self.initializeView()

    var offsetX : CGFloat = 0
    var labelHeight : CGFloat = 0

	//textにセットした文字列を1文字ずつに分解し、UILabelに1文字ずつ格納していく
    for var i = 0; i < text.length; i++ {
        let unitString = text.substringWithRange(NSMakeRange(i, 1))

        var label : UILabel = UILabel(frame: CGRectMake(offsetX, 0, 10, 10))
        label.backgroundColor = UIColor.clearColor()
        label.text = unitString
        label.font = self.font
        label.textColor = self.textColor
        label.sizeToFit()

        self.contentView.addSubview(label)
        self.labelArray.append(label)

        offsetX += label.frame.size.width + LABEL_MARGIN
        if (labelHeight < CGRectGetHeight(label.frame)) {
            labelHeight = CGRectGetHeight(label.frame)
        }
    }

    self.contentView.frame = CGRectMake(0, 0, offsetX, labelHeight)
    self.contentView.center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2)
}

//アニメーションの実行
private func executeAnimation(){

    var delayInSeconds : Double = 0.0
    var duration : Double = self.animationDuration

    self.animationIndex = 1

	//dispatch_afterで徐々にアニメーション開始のタイミングをずらしていく
    for label : UILabel in self.labelArray {
        let seconds = delayInSeconds * Double(NSEC_PER_SEC)
        let popTime : dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds))
        dispatch_after(popTime, dispatch_get_main_queue(), { () -> Void in
            UIView.animateWithDuration(duration, animations: { () -> Void in

                var labelFrame : CGRect = label.frame
                labelFrame.origin.y -= self.animationOffset
                label.frame = labelFrame

                }, completion: { (finished : Bool) -> Void in

                    UIView.animateWithDuration(duration, animations: { () -> Void in

                        var labelFrame : CGRect = label.frame
                        labelFrame.origin.y += self.animationOffset
                        label.frame = labelFrame

                        }, completion: { (finished : Bool) -> Void in

                            if (self.animationIndex == self.labelArray.count) {
                                self.didFinishAnimation()
                            }else{
                                self.animationIndex++
                            }
                    })
            })
        })
        delayInSeconds += self.animationDelay
    }
}

//アニメーション完了後のハンドラ
private func didFinishAnimation(){

    if (self.stopAnimationFlag == true) {
        return
    }

    if (self.roopCount <= 0){
        self.executeAnimation()
    }else if (self.currentRoopCount < self.roopCount){
        self.executeAnimation()
        self.currentRoopCount++;
    }
}

```

## Viewの追加(使用例)

```swift:
var animationLabel : AnimationLabelView = AnimationLabelView(frame: CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 100))
animationLabel.text = "Loading..."
animationLabel.font = UIFont.boldSystemFontOfSize(40)
animationLabel.center = CGPointMake(CGRectGetWidth(self.view.frame) / 2, CGRectGetHeight(self.view.frame) / 2)

self.view.addSubview(animationLabel)

animationLabel.startAnimation()
```


ソースコードは下記githubにて公開しております。

[PMAnimationLabelView_swift - GitHub](https://github.com/peromasamune/PMAnimationLabelView_swift)

# 終わりに

途中でラベルにテキストをセットし直したとしても、再度`startAnimation()`を呼ぶ事でviewが再描画され、表示を更新することができます。

`executeAnimation()`のアニメーション部分をいじるればいろいろなアニメーションを実装することができます。

この実装だと`stopAnimation()`ですぐにアニメーションを停止できないので、させたい場合はCoreAnimationを使った方が良いのかなぁと思います。