iOS 11以降で、UIViewControllerTransitionCoordinatorによるアニメーションが初回だけ効かない問題


概要

画面遷移に合わせたアニメーションとして、UIViewControllerTransitionCoordinatorを使う方法があります。

カスタムトランジションを書かなくても部分的なアニメーションがいい感じに実装できて便利なのですが、iOS11以降では一部挙動に問題がありました。

解決するためにはデフォルトの遷移を諦めて、結局カスタムトランジションと組み合わせなければなりません。

微妙に気が付きにくく、かつあまり情報がなかったためメモしておきます。


現象

ここではわかりやすさのために、以下の画面構成で話を進めたいと思います。


  • 最初の画面(ViewController)でButtonを押すと次画面(SecondViewController)へナビゲーションの遷移を行う

  • 次画面では中央に背景色青色の丸いViewが中央表示されている

  • ナビゲーションの遷移に合わせて次画面内のViewがアニメーションする。最初に赤い四角だったものが青い丸へと徐々に変化していくアニメーションとなっている

first_480.gif

最初の画面(ViewController)にUINavigationControllerDelegateを実装して、willShowで次画面(SecondViewController)のViewがアニメーションするよう処理を差し込んでいます。

extension ViewController: UINavigationControllerDelegate {

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if let vc = transitionCoordinator?.viewController(forKey: .from) as? SecondViewController {
transitionCoordinator?.animateAlongsideTransition(in: navigationController.view, animation: { (_) in
vc.contentView.layer.cornerRadius = 0
vc.contentView.backgroundColor = .red
}, completion: { context in

})
} else if let vc = transitionCoordinator?.viewController(forKey: .to) as? SecondViewController {
transitionCoordinator?.animateAlongsideTransition(in: navigationController.view, animation: { (_) in
vc.contentView.layer.cornerRadius = 150
vc.contentView.backgroundColor = .blue
}, completion: nil)
}
}
}

UINavigationControllerDelegateのメソッド内で、UIViewControllerTransitionCoordinatorを使ったアニメーション処理を書いています。その結果ナビゲーションのPush・Pop・エッジスワイプでそれらに沿ってアニメーションをしてくれます。

先程の動きが通常時の遷移になるのですが、なぜか起動後の初回の遷移ではうまく動いてくれません。次画面でのViewがはじめから青い丸で表示されてしまいます。一度前の画面に戻ってもう一度遷移をすると、きちんとアニメーションをしてくれるようになります。

first_480_gif.gif


原因

iOSのバグのようです。OpenRadarに以下の報告が掲載されていました。iOS11.2で同様の現象が再現されたとバグ報告されています。

ここに書いてあるとおり、上記現象はiOS10では再現しません。iOS 11では最新バージョン(2018/09/17時点)の11.4.1でもまだ再現しています。(このまま直らない?)


対応方法

バグであれば正攻法ではどうしようもなさそうです。かといってそのままにしたくはないでしょう。なにかしらパッチワークをするしかなさそうですが、良い方法が思いつきません。発想を変えてUIViewControllerTransitionCoordinatorの処理部分はそのままにして、画面遷移自体に注目してみます。すると、カスタムトランジションのときには問題なく初回遷移でアニメーションするのがわかりました。

extension ViewController: UINavigationControllerDelegate {

//...
//...
// 以下の2つを追加。カスタムトランジションの実装自体ここでは省略しています。
// 全体の実装は -> https://github.com/kazuhiro4949/UIViewControllerTransitionCoordinatorBug

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return operation == .push ? CustomPushAnimator() : CustomPopAnimator()
}

func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return percentDrivenInteractiveTransition
}
}

デフォルトの遷移と同じような動きをするカスタムトランジションを実装して、実行してみると起動直後の遷移でも無事アニメーションするようになります。

correct_480_gif.gif


結論

現状だと、最新のiOSでUIViewControllerTransitionCoordinatorによる遷移アニメーションをするには、カスタムトランジションと組み合わせるのが良さそうです。じゃあカスタムトランジションだけでアニメーションを実装すればいいじゃないかという感じではありますが、複雑な画面や、特定画面用に特殊な動作を行いたい場合は選択肢に入れておいてもよいでしょう。とてもつらい。

デモ用として作った全体の実装は以下のリポジトリに載せています。

https://github.com/kazuhiro4949/UIViewControllerTransitionCoordinatorBug/tree/1.0


参考資料