LoginSignup
46
43

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

Untitled.gif

実装

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

クラスの作成

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

import UIKit

class AnimationLabelView: UIView {

}

プロパティの追加

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

//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>()

定数

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

private let LABEL_MARGIN : CGFloat = 1.0

クラスメソッドの追加

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

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

func stopAnimation(){
    self.stopAnimationFlag = false
}

プライベートメソッドの追加

Viewの実装

//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の追加(使用例)

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

終わりに

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

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

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

46
43
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
46
43