【iOS】Tinder的アニメーション

最近iOSのAnimation/Transition周りを触ったので、勉強の意味も込めてQiitaに書きたいと思います。

# iOSのAnimation/Transitionの実装
今回はTinder的な画面遷移を実装してみたいと思います。

## 実装後の状態
![アニメーション](http://i.giphy.com/l0JMgk0uh5rDt6inC.gif)

ソースコードは[こちら](https://github.com/TakuSemba/Tindest)にあげています。

まず、大きく分けて、画像部分のTransitionの実装と、下の三つのボタンのAnimationがあります。

# プロフィール画像部分のTransitionの実装
最初に上のgifでいうメガネのおじさん(ImageView)の遷移時の動きを実装していきます。

## 必要なプロトコル
・ UIViewControllerAnimatedTransitioning (遷移時の動きを定義する)
・ UIViewControllerTransitioningDelegate (遷移先のcontrollerで適応するdelegate)

## 概要

まず、UIViewControllerAnimatedTransitioningを実装します。
UIViewControllerAnimatedTransitioning内に遷移時の動きを定義していきます。
transitionContextで遷移先/遷移元のViewControllerが取得できます。

```swift:MyTransition.swift

//遷移時のアニメーションを定義
class PresentedAnimater : NSObject, UIViewControllerAnimatedTransitioning {
    
    let duration = 0.3
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //animationのdurationを返す
        return duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
        guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { return }
        
        let container = transitionContext.containerView
        
        let imageView = (fromVC as! FirstViewController).copyImageView() // <-このcopyImageView()は遷移元のImageViewと同じ大きさ、位置のImageViewを返す
        let destImageViewRect = (toVC as! SecondViewController).copyImageView().frame // <-このcopyImageView()は遷移先のImageViewと同じ大きさ、位置のImageViewを返す
        container.addSubview(toVC.view)
        container.addSubview(imageView)
        toVC.view.alpha = 0.0
        UIView.animate(withDuration: duration, delay: 0.01, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: [], animations: {
            toVC.view.alpha = 1.0
            imageView.frame = destImageViewRect
        }, completion: {_ in
            imageView.isHidden = true
            transitionContext.completeTransition(true)
        })
    }
}

//Dismiss時のアニメーションを定義
class DismissedAnimater : NSObject, UIViewControllerAnimatedTransitioning {
    
    let duration = 0.3
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //animationのdurationを返す
        return duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
        guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { return }
        
        let container = transitionContext.containerView
        
        let imageView = (fromVC as! SecondViewController).copyImageView() 
        let destImageViewRect = (toVC as! FirstViewController).copyImageView().frame
        container.addSubview(toVC.view)
        container.addSubview(imageView)
        toVC.view.alpha = 0.0
        UIView.animate(withDuration: duration, delay: 0.01, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: [], animations: {
            toVC.view.alpha = 1.0
            imageView.frame = destImageViewRect
        }, completion: {_ in
            transitionContext.completeTransition(true)
        })
    }
}
```

次に、UIViewControllerTransitioningDelegate部分を書いていきます。

```swift:SecondViewController

extension SecondViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 画面遷移時に呼ばれるメソッド 
        return PresentedAnimater()
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // Dismiss時に呼ばれるメソッド 
        return DismissedAnimater()
    }
}

```

あとは、SecondViewControllerのviewDidLoadあたりで、

```swift
self.transitioningDelegate = self
```

としてあげれば完成です。

# 下の三つのボタン部分のAnimationの実装
最後に上のgifでいう三つのボタンが外側からすっと出てくるアニメーションを実装します。
こちらは先ほどのTransitionとは違って遷移後にボタンをアニメーションさせます。

## 概要

まずはUIButtonを継承したものを実装します。

```swift:MovableUIButton

class MovableUIButton: UIButton {
    
    let repeatCount: Float = 1
    let dumping: CGFloat = 15
    let duration: Double = 1
    let initialVelocity: CGFloat = 15
    
    let fromBottomKey = "appearFromBottom"
    let fromRightKey = "appearFromBottom"
    let fromLeftKey = "appearFromBottom"
    
    func appearFromBottom(){
        //今回はボタンを動かすだけなので、"position"
        let animation = CASpringAnimation(keyPath: "position")
        //アニメーションのdurationを定義
        animation.duration = duration
        //アニメーションのrepeatの回数を定義
        animation.repeatCount = repeatCount
        //ハネ具合を定義　（この値が小さいほど伸縮します）
        animation.damping = dumping
        //最初の速度を定義
        animation.initialVelocity = initialVelocity
        //アニメーション開始点を定義
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x, y: self.center.y + 100))
        //アニメーション終了点を定義
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x, y: self.center.y))
        
        self.layer.add(animation, forKey: fromBottomKey)
    }
    
    func appearFromRight(){
        let animation = CASpringAnimation(keyPath: "position")
        animation.duration = duration
        animation.repeatCount = repeatCount
        animation.damping = dumping
        animation.initialVelocity = initialVelocity
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x + 100, y: self.center.y))
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x, y: self.center.y))
        
        self.layer.add(animation, forKey: fromRightKey)
    }
    
    func appearFromLeft(){
        let animation = CASpringAnimation(keyPath: "position")
        animation.duration = duration
        animation.repeatCount = repeatCount
        animation.damping = dumping
        animation.initialVelocity = initialVelocity
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 100, y: self.center.y))
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x, y: self.center.y))
        
        self.layer.add(animation, forKey: fromLeftKey)
    }
}


```

次にStoryBoard内でUIButtonのこの部分をMovableUIButtonにセットします。
![image](https://i.imgsafe.org/631387f63c.png)


あとは、ControllerのviewDidLayoutSubviewsあたりで、

```swift
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        superlikeButton.appearFromBottom()
        nopeButton.appearFromLeft()
        likeButton.appearFromRight()
    }
```
とすれば、Buttonを動かすことができます。


# 最後に
こちらに[ソースコード](https://github.com/TakuSemba/Tindest)をあげています。