iOS Custom Presentation & Transition (3) 〜Custom Transition〜
はじめに
iOS Custom Presentation & Transition (1) 〜コード一行もない編〜 - your3i’s blog
iOS Custom Presentation & Transition (2) 〜UIPresentationControllerでカスタムモーダルを作る〜 - your3i’s blog
1は作るものの紹介と大体の概念を説明し、2はpresentation controllerを利用してSemiModalの実装を説明した。今回は引き続き、Custom transitionの実装方法を簡単に説明。
なぜCustom transitionを実装する必要がある
1で作るものを紹介したと思うが、モーダルを出すときは下からslide inして、閉じるときは下にslide outするように。そして、モーダルをdragしても閉じれるように。
前回(2)はここまで作った:
modalTransitionStyleをstoryboardでcover verticalに設定してあるから、slide in slide outの要件はもうすでに満たしてる。なんかcustom transition やらなくてもいいじゃない?
でも実は、dragして閉じれることを作るには、UIPercentDrivenInteractiveTransitionというクラスを利用する必要がある。これのオブジェクトが使われる前提としてcustom transition animatorを作る必要がある。
A percent-driven interactive transition object relies on a transition animator delegate—a custom object that adopts the UIViewControllerAnimatedTransitioning protocol—to set up and perform the animations.
そのため、drag dismiss を実現するために、すでにslide outできるようにしてるけど、それぽい挙動になるcustom transitionを作らなきゃいけない。
下からslide outするcustom transitionの実装
CustomDismissAnimatorを作る
UIViewControllerAnimatedTransitioningをadoptしたクラスCustomDismissAnimatorを作成。今回はdismissのときだけ、custom transitionにしたいから、dismiss専用animatorを作った。Animatorという名前は、別の名前にしてもいいけど、transitionのanimationを定義するクラスだから、animatorにした。
final class CustomDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning { }
UIViewControllerAnimatedTransitioningのメソッドをimplementしてないから、エラーが出ると思う。
まず、transition アニメーションの時間を返さなきゃいけない。
final class CustomDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.2 } }
そして、transitionはどんなアニメーションで行われるのかを教えなきゃいけない。
final class CustomDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.2 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // とりあえず、何もせずtransition終わったようを教えてあげよう transitionContext.completeTransition(true) } }
CustomDismissAnimatorを使われるようにする
DetailsViewControllerにUIViewControllerTransitioningDelegateの以下の方法をadoptする。dismissするときこのクラスが定義したアニメーションを使ってくれという意味だ。
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return CustomDismissAnimator() }
動きをみてみると、DetailsViewControllerを閉じるときしゅっと消えるようになった。これはアニメーションちゃんと作ってなかったからだ、でもこれで、CustomDismissAnimatorが使われていることがわかる。
transitionのアニメーション
正直いうとここはまだそんなに理解してないんだ。
これってなに?になるものがいくつかあって、一応自分現状の理解で説明すると。
- UIViewControllerContextTransitioningのtransitionContext
- transitionアニメーションが行われる場所としての箱みたいなもの。紙芝居のあの箱みたいな。
- transitionContext.viewController(forKey: .from)
- fromとtoのそれぞれのview controllerがある。今回はDetailsViewControllerオブジェクトからMainViewControllerオブジェクトに遷移するため、fromのview controllerはDetailsViewControllerオブジェクト。
- fromViewの位置の計算
- 前のステップのgifのように、detailViewControllerのviewであるfromViewはシュッと消えるのように、transitionContextの中で、今fromViewのtransition始まるときの位置と終わるときの位置が一緒。目標はtransitionが終わるとき、fromViewがy軸で下から消えるように下から、改めてfromViewのfinalFrameを計算しておく。
- transitionContext.completeTransition
- こっちからcompleteTransitionを指示しなきゃ、transitionContext自分からtransitionを終えることができないみたい。そのため、animationが完了したあと、ちゃんと教える必要がある。そうしないと、transitionContextにはcontainerViewがあって、そのviewが画面上にぞっと残ってて、下のMainViewControllerオブジェクトを触ることができなくなる。
って、コードはこうなる:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // DetailsViewControllerオブジェクトを取る guard let fromViewController = transitionContext.viewController(forKey: .from), let fromView = transitionContext.view(forKey: .from) else { transitionContext.completeTransition(true) return } // DetailsViewControllerオブジェクトの最終位置の計算 var fromFinalFrame = transitionContext.finalFrame(for: fromViewController) let newFinalOrigin = CGPoint(x: fromFinalFrame.origin.x, y: UIScreen.main.bounds.height) fromFinalFrame.origin = newFinalOrigin // アニメーション let duration = transitionDuration(using: transitionContext) UIView.animate( withDuration: duration, animations: { fromView.frame = fromFinalFrame }, completion: { _ in // transitionキャンセルされたかどうか次第で、transitionを完了する let success = !transitionContext.transitionWasCancelled transitionContext.completeTransition(success) } ) }
実装完了
なにも変わってないじゃん〜 はい、そうですw
でもこれで!interactive transitionが作れるようになる!
次回で話す。
おわりに
transitionContextについてちゃんと説明することができなかった🤦♀️
でもanimateTransitionの中は、色々おしゃれなアニメーション作れるところだから、よく理解した方が良さそう。今度機会があったら、おしゃれなアニメーションを作ってみてそして書く。
記事のまとめ
iOS Custom Presentation & Transition (1) 〜コード一行もない編〜 - your3i’s blog
iOS Custom Presentation & Transition (2) 〜UIPresentationControllerでカスタムモーダルを作る〜 - your3i’s blog
iOS Custom Presentation & Transition (3) 〜Custom Transition〜 - your3i’s blog
iOS Custom Presentation & Transition (4) 〜Interactive Transition〜 - your3i’s blog