アニメーションが思った通りに動いてくれない！？そんな時の対策法1


[アプリ道場 Advent Calendar 2015](http://qiita.com/advent-calendar/2015/app-dojo)の21日目です。よろしくお願いします。

##アプリ作成に慣れてきた！その次にやりたいことと言えばコレ！
アプリを作り始めて、「ちょっと出来るようになってきたかも！」と思った時、次にやりたくなること。それはアニメーションではないでしょうか？(私だけ！？)
そんな時、一番お手軽な方法として```animateWithDuration```というメソッドを使用する方法があります。
私は以下みたいなテンプレート関数を作ってゆるーく使ってます。
ヘッダの説明にもありますが、第1引数に指定したViewを、第2引数に第1引数に指定したViewの移動先座標を指定し、アニメーションを実行する関数です。


```swift    
    /**
     指定されたViewを指定された中心座標へアニメーションする
     
     - parameter targetView:アニメーションさせるView
     - parameter targetPoint:異動先の座標
     */
    func animationLocateWithView(targetView :UIView, targetPoint :CGPoint) {
        
        print("\(__FUNCTION__) is called!!")
        
        let animationSpeed = 1.0
        
        UIView.animateWithDuration(animationSpeed, animations: { () -> Void in

            targetView.center = targetPoint
            }, completion: nil)
    }
```

##今回の課題
以下のようなものをStoryboardで作りました。

- "Move〜"ボタン: pressButtonメソッド, 押下時にアニメーションを実行
- オレンジ色正方形: orangeColorView, アニメーションさせるView
- グレー色正方形左: positionViewLeft, 開始地点の目安のView(今回は使用しない)
- グレー色正方形右: positionViewRighr, アニメーションの異動先の目標とするView
- "Before"テキスト: textLabel, 動かした時"Before"を"After"に変更する

![スクリーンショット 2015-12-20 11.24.17.png](https://qiita-image-store.s3.amazonaws.com/0/14762/1f6fb8a4-cdaf-fb18-084f-fbb8a9aabe0e.png "スクリーンショット 2015-12-20 11.24.17.png")


##まずはアニメーションさせてみよう
まずはorangeColorViewをアニメーションさせてみましょう。
ボタンを押した時、animationLocateWithViewを実行する作りになっています。

```
import UIKit

class ViewController: UIViewController {

    ...
    
    /**
     ボタンを押した時の動作。アニメーション実行のトリガ
     */
    @IBAction func pressButton(sender: AnyObject) {
        
        print("\(__FUNCTION__) is called!!")
 
        animationLocateWithView(orangeColorView, targetPoint: positionViewRight.center) //アニメーション実行
        
    }
    
    /**
     指定されたViewを指定された中心座標へアニメーションする
     
     - parameter targetView:アニメーションさせるView
     - parameter targetPoint:異動先の座標
     */
    func animationLocateWithView(targetView :UIView, targetPoint :CGPoint) {
        
        print("\(__FUNCTION__) is called!!")
        
        let animationSpeed = 1.0
        
        UIView.animateWithDuration(animationSpeed, animations: { () -> Void in

            targetView.center = targetPoint
            }, completion: nil)
    }


}
```

これを実行した様子が以下のgifになります。
  
![animation3.gif](https://qiita-image-store.s3.amazonaws.com/0/14762/a265a5e3-f995-0497-68a6-93b545f210e2.gif "animation3.gif")

よしよし。ちゃんと動いていますね。

##テキストファイルの更新。こんなのチョロいと油断するな。
あとはボタンを押す前は"Before"と書かれたLabelのTextをボタンを押したタイミングで"After"と書き換えればいいだだけです。

```swift
    /**
     ボタンを押した時の動作。アニメーション実行のトリガ
     */
    @IBAction func pressButton(sender: AnyObject) {
        
        print("\(__FUNCTION__) is called!!")
        
        self.textLabel.text = "After"   //テキストをafterへ変更
 
        animationLocateWithView(orangeColorView, targetPoint: positionViewRight.center) //アニメーション実行
        
    }
    
```

実行してみます。
実行したgifが以下ですが...

![animation1.gif](https://qiita-image-store.s3.amazonaws.com/0/14762/216b8db0-06c3-987b-4f2d-09047f98b45a.gif "animation1.gif")

ええ〜！？orangeColorViewが意図した動きしてなくない！?

実はこの動作、Auto Layoutを無効にすると発生しません。
またStoryBoardからでなく、コード上から```addSubView```したViewをアニメーションさせる場合には発生しません。
この不可解な動作理由ですが、以下のような理由により発生していると考えられます。

- Labelの更新が行われることによりloadView(レイアウト再実行)メソッドが実行される
- ラベルの内容更新とアニメーションが同時に実行される

完全には追えていませんが、 _アニメーション + AutoLayout + Viewの更新_ という条件が重なる場合に現象は発生します。
Auto Layout有効にしているとViewが更新された際に再レイアウトが実行されます。今回の場合はUILabelのテキストを更新したタイミングですね。
この時、```loadView```という内部処理が実行されます。
```loadView```とラベルの更新が同時に行われることが、意図しない動作に要因の一つと認識しています。

##解決法は？
解決方法としてはoverrideした```viewDidLayoutSubView```内にアニメーションするViewのアニメーション後の座標を書くという方法です。
この```viewDidLayoutSubView```ですが、上述の```loadView```が実行された直後に呼び出される処理です。
```viewDidLayoutSubView```をoverrideし、その内部にアニメーション後のorangeColorView座標を設定してあげると、あら不思議！
この問題が解決します。

コードは以下。
( ```viewDidLayoutSubView```は起動時にも呼ばれる点を考慮に入れることを忘れないこと。)


```
class ViewController: UIViewController {

    ...

    var animationFlag = false
    
    ...
    
    override func viewDidLayoutSubviews() {
        print("\(__FUNCTION__) is called!!")
        
        /**
        アニメーション移動後のViewの座標を設定します。
        アニメション後の座標の管理方法はアプリによって異なると思われます。
        */

        //アニメーションするか否か確認。アニメーションしない場合にはreturnする。
        if animationFlag == false {
            return
        }
        
        orangeColorView.center = positionViewRight.center   //移動後のorangeColorViewの座標を設定
        animationFlag = false                               //アニメーションフラグをfalseに
        
        
    }

    /**
     ボタンを押した時の動作。アニメーション実行のトリガ
     */
    @IBAction func pressButton(sender: AnyObject) {
        
        print("\(__FUNCTION__) is called!!")
        
        animationFlag = true            //アニメーションフラグをtrueにする
        
        self.textLabel.text = "After"   //テキストをafterへ変更 (←これがアニメーションが意図しない動きになる原因！！)
 
        animationLocateWithView(orangeColorView, targetPoint: positionViewRight.center) //アニメーション実行
        
    }
    
    /**
     指定されたViewを指定された中心座標へアニメーションする
     
     - parameter targetView:アニメーションさせるView
     - parameter targetPoint:異動先の座標
     */
    func animationLocateWithView(targetView :UIView, targetPoint :CGPoint) {
        
        print("\(__FUNCTION__) is called!!")
        
        let animationSpeed = 1.0
        
        UIView.animateWithDuration(animationSpeed, animations: { () -> Void in

            targetView.center = targetPoint
            }, completion: nil)
    }


}
```
実行時のgif画像は以下です。
想定通りの動作になっています！

![animation2.gif](https://qiita-image-store.s3.amazonaws.com/0/14762/862f53a6-365a-9096-cd40-6a702e1aa464.gif "animation2.gif")

完成したプロジェクトをGithubに上げておきます。参考にして下さい。
▶︎[anthrgrnwrld/animation](https://github.com/anthrgrnwrld/animation)

現象が発生する原因について正確には追えていませんので正式な対策ではないかもしれませんが、初心者向けのテクニックとしてご使用頂けると嬉しいです。