Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

はじめに

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を使った方が良いのかなぁと思います。

peromasamune
iOSがメインです(swift,Objective-C)。たまにAndroid、サーバーサイドなど(PHP, Ruby)
http://peromasamune.hateblo.jp/
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした