UIPopoverPresentationControllerで吹き出しのアニメーションをカスタマイズ


担当アプリで以下のようなチュートリアル表示をしたい（ボタンに吹き出しして、機能を説明するような）という要望があったので、実装してみた。

![TutorialPopover2.gif](https://qiita-image-store.s3.amazonaws.com/0/37373/3041d6f3-8432-a212-97e6-cc01e3734e78.gif "TutorialPopover2.gif")

https://github.com/HIkaruSato/PopOverViewForTutorial

<br/>
以下のようなライブラリを利用しても簡単にできると思ったが、この程度でライブラリを入れてアプリサイズが大きくなるのも微妙だと思い、自分で実装してみた。
https://github.com/andreamazz/AMPopTip.git


実装はiOS8から利用できるUIPopoverPresentationController（iPhoneでも吹き出し表示ができる標準SDK）で簡単にできると思って、やってみたがアニメーションのカスタマイズに関する情報が少なく、ちょっと苦労したところもあったのでQiitaに投稿しようと思った。

わたしが調べた感じではこの記事のような感じなのだがもっと簡単に吹き出しアニメーションをカスタマイズできるとかあれば是非コメントをお願いします🙇


#表示アニメーションのやり方
上記のような吹き出しの拡大縮小アニメーションは、viewWillAppearとviewWillDisappearで以下のコードにて実装できる。
基本的にはUIViewController.popoverPresentationController.popoverBackgroundViewClassプロパティ（吹き出し部分のViewをカスタマイズするクラス）を設定しないとうまくいかないので、
その辺りは以下の[注意点](#注意点)を参考にしてほしい。

```swift:viewWillAppear
/** viewWillAppearのところで実装。吹き出し表示時のアニメーション。 **/

        //これで吹き出し部分のView(_UIPopoverViewクラスというRunTimeのクラスの模様)を取得
        let view = self.popoverPresentationController!.presentedView()!
        let backgroundView = view.performSelector(Selector("backgroundView")).takeUnretainedValue() as! UIPopoverBackgroundView
        let arrowOffset = backgroundView.arrowOffset
        switch self.popoverPresentationController!.arrowDirection {
        case UIPopoverArrowDirection.Up:
            let offset = arrowOffset/view.frame.width
            view.layer.anchorPoint = CGPoint(x: view.layer.anchorPoint.x + offset, y: 0)
            break;
        case UIPopoverArrowDirection.Down:
            let offset = arrowOffset/view.frame.width
            view.layer.anchorPoint = CGPoint(x: view.layer.anchorPoint.x + offset, y: 1)
            break;
        case UIPopoverArrowDirection.Left:
            let offset = arrowOffset/view.frame.height
            view.layer.anchorPoint = CGPoint(x: 0, y: view.layer.anchorPoint.y + offset)
            break;
        case UIPopoverArrowDirection.Right:
            let offset = arrowOffset/view.frame.height
            view.layer.anchorPoint = CGPoint(x: 1, y: view.layer.anchorPoint.y + offset)
            break;
        default:
            break;
        }
        print("view.layer.anchorPoint\(view.layer.anchorPoint)")
        view.transform = CGAffineTransformMakeScale(0.1, 0.1)
        UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
            view.transform = CGAffineTransformIdentity
        }) { (_) in
        	 view.hidden = true
        }
```

<br/>

```swift:viewWillDisappear
/** viewWillDisappearのところで実装。吹き出しクローズ時のアニメーション。 **/

        let view = self.popoverPresentationController!.presentedView()!
        UIView.animateWithDuration(0.3, animations: {
            view.transform = CGAffineTransformMakeScale(0.1, 0.1)
        }) { (_) in
            view.hidden = true
        }
```


#注意点

###デフォルトの吹き出しだと、表示アニメーションしたときに端の方で吹き出し表示するとarrow部分が透ける
上記の表示アニメーションをやると、端の方で吹き出し表示したときにarrow部分が透けることがわかった。
Reveal.appで表示アニメーションあり/なしを比較してみると、、、

* 表示アニメーションあり
![popOver_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/37373/182bf7ad-811b-232a-7799-a16d47c02ba7.jpeg "popOver_1.jpg")


* 表示アニメーションなし
![popOver_2.png](https://qiita-image-store.s3.amazonaws.com/0/37373/7f23ff95-2ed5-6896-3b14-ea4f26f4f605.png "popOver_2.png")


<br/>
表示アニメーションなしのときの方に存在している_UIPopverStandardChromeView(吹き出し部分のView。RuntimeのClass)->_UIBackdropView内のUIViewが、表示アニメーションありのときに無いのがわかった。
こうなってしまったのは、吹き出し表示アニメーション時に以下のようにview(_UIPopoverViewクラス).layer.anchorPoint値を変更していることが原因となっているようだった。

```_UIPopoverViewクラス.layer.anchorPoint値を変更
・
・
・
let view = self.popoverPresentationController!.presentedView()!
        let backgroundView = view.performSelector(Selector("backgroundView")).takeUnretainedValue() as! UIPopoverBackgroundView
        let arrowOffset = backgroundView.arrowOffset
        switch self.popoverPresentationController!.arrowDirection {
        case UIPopoverArrowDirection.Up:
            let offset = arrowOffset/view.frame.width
            view.layer.anchorPoint = CGPoint(x: view.layer.anchorPoint.x + offset, y: 0)
            break;
        case UIPopoverArrowDirection.Down:
            let offset = arrowOffset/view.frame.width
            view.layer.anchorPoint = CGPoint(x: view.layer.anchorPoint.x + offset, y: 1)
            break;
        case UIPopoverArrowDirection.Left:
            let offset = arrowOffset/view.frame.height
            view.layer.anchorPoint = CGPoint(x: 0, y: view.layer.anchorPoint.y + offset)
            break;
        case UIPopoverArrowDirection.Right:
            let offset = arrowOffset/view.frame.height
            view.layer.anchorPoint = CGPoint(x: 1, y: view.layer.anchorPoint.y + offset)
            break;
        default:
            break;
        }

・
・
・
```

なので、ここはUIViewController.popoverPresentationController.popoverBackgroundViewClassプロパティを設定すること（吹き出し部分のUIViewをカスタマイズ）で対応した。
ただ、これで完璧というわけではなく、すごく端っこで吹き出し表示するとarrowがずれるという問題が発生したがそんな端っこで吹き出しすることも多分ないので、不問にすることにした。
![IMG_3795.jpg](https://qiita-image-store.s3.amazonaws.com/0/37373/70e468c6-2aa5-c40c-beeb-b181a3e7328f.jpeg "IMG_3795.jpg")



##吹き出し閉じるときのアニメーションがデフォルトの吹き出しだとカスタマイズできない。
具体的には、吹き出しを閉じるときに吹き出し元の部分に向かって縮小させるようにアニメーションさせようとしたが（viewWillDisappearなどのタイミングでCGAffineTransformMakeScaleなど）、何故かうまくいかなかった。
CGAffineTransformMakeScale(0.1,　0.1)と書いてるのに何故か拡大された。

ここはデフォルトでCross Dissolve(徐々に消える)で我慢しようと思ったが、
**popoverPresentationController.popoverBackgroundViewClassプロパティを設定すると、以下のコードで吹き出し元の部分に向かって縮小させるアニメーションができた。**


#まとめ
* 上記のアニメーションはよくあるので、UIPopoverPresentationControllerでデフォルトでできるくらいだと思ったら、思いのほか苦労してしまった。
* UIPopoverPresentationControllerでアニメーションをカスタマイズする時はUIViewController.popoverPresentationController.popoverBackgroundViewClassプロパティ（吹き出し部分のUIViewをカスタマイズ）を設定したほうがよさそう。