Posted at

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

More than 3 years have passed since last update.


はじめに

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

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


実装

順を追って説明していきます。

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