UILabelでエレベーター風なアニメーション


# はじめに

ラベルの数字がエレベーター風に昇降アニメーションする実装をしてみました。
完成イメージは下のような感じ↓

![Untitled.gif](https://qiita-image-store.s3.amazonaws.com/0/31823/d1ed7a10-29d3-981b-1b42-86d3f077190f.gif "Untitled.gif")

# 環境

* XCode7.2 (swift2.1)

# 実装

## アニメーションするラベル

`primaryLabel`と`secondaryLabel`の2つのラベルを用意して、originをアニメーションさせていきます。

```swift:PMElevateLabel.swift
import UIKit

class PMElevateLabel: UIView {

    //MARK: - Properties
    //MARK: Public
    var duration : NSTimeInterval = 1.0

    //MARK: Private
    private var currentValue : Int = 0
    private var primaryLabel : UILabel!
    private var secondaryLabel : UILabel!

    //MARK: - Initializer
    override init(frame: CGRect) {
        super.init(frame: frame)

        self.layer.masksToBounds = true

        primaryLabel = self.label(CGRectMake(0, 0, frame.width, frame.height))
        self.addSubview(primaryLabel)

        secondaryLabel = self.label(CGRectMake(0, 0, frame.width, frame.height))
        self.addSubview(secondaryLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    //MARK: - Public method
    func setValue(value : Int, animated : Bool) {
        if self.currentValue == value {
            return
        }

        self.primaryLabel.text = "\(self.currentValue)"
        self.secondaryLabel.text = "\(value)"

        let destOffset = (self.currentValue > value) ? -self.frame.height : self.frame.height

        self.primaryLabel.setOrigin(.zero)
        self.secondaryLabel.setOrigin(CGPointMake(0, destOffset))

        UIView.animateWithDuration((animated) ? self.duration : 0.0, delay: 0.0, options: .CurveLinear, animations: { () -> Void in
            self.primaryLabel.setOrigin(CGPointMake(0, -destOffset))
            self.secondaryLabel.setOrigin(.zero)
            }, completion: { _ in })

        self.currentValue = value
    }

    //MARK: - Private method
    private func label(frame : CGRect) -> UILabel {
        let label = UILabel(frame: frame)
        label.backgroundColor = UIColor.clearColor()
        label.textAlignment = .Center
        label.font = UIFont.systemFontOfSize(50)
        return label
    }
}

extension UIView {
    func setOrigin(origin : CGPoint) {
        var rect = self.frame
        rect.origin = origin
        self.frame = rect
    }
}
```

## アニメーションさせる

今回は`CADisplayLink`を使って適当にアニメーションさせます。


```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    //MARK: - Properties
    //MARK: Public

    //MARK: Private
    private var elevateLabel : PMElevateLabel!
    private var animationStartTime : CFTimeInterval?

    //MARK: - View Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()

        elevateLabel = PMElevateLabel(frame: CGRectMake(0, 0, 100, 100))
        elevateLabel.center = CGPointMake(self.view.frame.width / 2, self.view.frame.height / 2)
        elevateLabel.layer.borderColor = UIColor.blueColor().CGColor
        elevateLabel.layer.borderWidth = 1.0
        self.view.addSubview(elevateLabel)

		self.startAnimation()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    //MARK: - Private method
    private func startAnimation() {
        let displayLink = CADisplayLink(target: self, selector: "updateDisplayLink:")
        displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)

        self.animationStartTime = CACurrentMediaTime()
    }

    //MARK: - CADislayLink handler
    @objc private func updateDisplayLink(displayLink : CADisplayLink) {
        guard let animTime = self.animationStartTime else {
            displayLink.invalidate()
            return
        }

        let elapsedTime = displayLink.timestamp - animTime
        if elapsedTime >= 30 {
            displayLink.invalidate()
        }

        elevateLabel.setValue(Int(elapsedTime), animated: true)
    }
}
```

#終わりに

やっつけですが、UILabelを使ったお手軽アニメーションを実装してみました。
現状は数値を渡して、それをもとにアニメーションするだけです。

といっても`CADisplayLink`やタイマーなどで定期的に呼ばないとそれっぽくならないので、あらかじめ配列でデータを渡しておいたり、アニメーションの実装を全て内包してしまったほうが使いやすい気はしてます。

アニメーション自体もかなりシンプルなので、Transformとかその他装飾をしてあげればもうちょっとかっこよくなると思います。

ソースコードは下記githubにあります。

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