LoginSignup
69
62

More than 5 years have passed since last update.

UIViewControllerの遷移をカスタムアニメーションにする上で注意すべき点

Last updated at Posted at 2015-10-26

UIViewControllerの遷移は、UIViewControllerAnimatedTransitioningを使ってカスタマイズするのが、近頃では当たり前になってきています。

その中でもUIViewControllerpresentViewController()dismissViewController()とする場合と、UINavigationControllerpushViewController()popViewController()をする場合で、使い方が多少異なります。その異なる部分には内部的にも大きな違いがあり、注意しなければいけない点がいくつかあります。

ここでは違いと注意点、普段見落としがちなプチ情報を書いていこうと思います。遷移周りのアニメーションがカクついたり、メモリが増え続けたりする場合の原因だったりすることも多々あるのでご覧頂けると幸いです。

UIViewControllerAnimatedTransitioningの違い

UIViewControllerの場合

UIViewControllerTransitioningDelegateの下記のデリゲートメソッドを使ってUIViewControllerAnimatedTransitioningが実装されたクラスを返します。

optional func animationControllerForPresentedController(_ presented: UIViewController,
                                   presentingController presenting: UIViewController,
                                       sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?

optional func animationControllerForDismissedController(_ dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

UIViewControllerContextTransitioningのインスタンスからtransitionContext.containerView()でcontainerViewを取得した際に、UIViewではなくUITransitionViewが返されます。UIViewControllerから別なUIViewControllerprensetViewController()する度に、新しいUITransitionViewが生成されます。popViewController()した際には、それ以前の階層に紐づくUITransitionViewの同じインスタンスが返されます。

例えば3回presentViewController()して、3回dismissViewController()をすると...

presentViewController() [1] <UITransitionView: 0x7f941ae38e90; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941ae33c70>>
presentViewController() [2] <UITransitionView: 0x7f941ca93c80; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941ca8c9e0>>
presentViewController() [3] <UITransitionView: 0x7f941ad17490; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941ad23eb0>>
dismissViewController() [1] <UITransitionView: 0x7f941ad17490; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941ad23eb0>>
dismissViewController() [2] <UITransitionView: 0x7f941ca93c80; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941ca8c9e0>>
dismissViewController() [3] <UITransitionView: 0x7f941ae38e90; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941ae33c70>>

1回目のpresentViewController()と3回目のdismissViewController()UITransitionView、2回目のpresentViewController()と2回目のdismissViewController()UITransitionView、3回目のpresentViewController()と1回目のdismissViewController()UITransitionViewが同じアドレスなのがわかります。

UINavigationControllerの場合

UINavigationControllerDelegateのデリゲートメソッドを使ってUIViewControllerAnimatedTransitioningが実装されたクラスを返します。

optional func navigationController(_ navigationController: UINavigationController,
   animationControllerForOperation operation: UINavigationControllerOperation,
                fromViewController fromVC: UIViewController,
                  toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?

UIViewControllerContextTransitioningのインスタンスからtransitionContext.containerView()でcontainerViewを取得した際に、UIViewではなくUIViewControllerWrapperViewが返されます。UIViewControllerから別なUIViewControllerpushViewController()する際に、すべて同一のUIViewControllerWrapperViewが返されます。

例えば3回pushViewController()して、3回popViewController()をすると...

pushViewController() [1] <UIViewControllerWrapperView: 0x7f941ca8d7d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941cab8a60>>
pushViewController() [2] <UIViewControllerWrapperView: 0x7f941ca8d7d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941cab8a60>>
pushViewController() [3] <UIViewControllerWrapperView: 0x7f941ca8d7d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941cab8a60>>
popViewController()  [1] <UIViewControllerWrapperView: 0x7f941ca8d7d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941cab8a60>>
popViewController()  [2] <UIViewControllerWrapperView: 0x7f941ca8d7d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941cab8a60>>
popViewController()  [3] <UIViewControllerWrapperView: 0x7f941ca8d7d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f941cab8a60>>

すべてのUIViewControllerWrapperViewが同じアドレスなのがわかります。

UINavigationControllerの遷移をカスタムする際に、toViewController.viewfromViewController.viewの間に透過の黒のViewを挟むとします。アニメーションが完了した時点でその透過のViewをremoveFromSuperview()しなければ、pushやpopをする度に透過のViewがaddSubview()され続けてしまう状態になります。

遷移を繰り返していくと徐々にアニメーションは重くなっていったりしますが、ただの透過のViewだったりするとメモリも大きく増えたりなどはしないので非常に気づきにくいです。後から生成して追加したUIViewはアニメーションが完了した際にremoveFromSuperview()を忘れないようにしましょう。

プチ情報

どちらの場合でも、containerViewfromViewController.viewaddSubview()された状態で
返されるので、それぞれ

// `presentViewController()`や`pushViewController()`をする際は
containerView.addSubview(toViewContoller.view)

// `dismissViewController()`や`popViewController()`をする際は
containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)

と書くだけで、階層に合った場所にViewを配置することができます。

まとめ

  • UIViewControllerTransitioningDelegateで返されるUIViewControllerAnimatedTransitioningのcontainerViewはUITransitionView
  • UINavigationControllerDelegateで返されるUIViewControllerAnimatedTransitioningのcontainerViewはUIViewControllerWrapperView
  • containerViewに後から生成したUIViewaddSubview()した際は、アニメーション完了時に必ずremoveFromSuperview()するようにする。
  • containerView取得した時点でfromViewController.viewaddSubview()されているので、再度addSubview()する必要はない。
  • Viewが画面に表示されるときは、containerView.addSubview(toViewController.view)
  • Viewが画面から消えるときは、containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)
69
62
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
62