iPhone
Xcode
iOS
Swift
ios10

iOS10でのNavigationControllerのカスタム遷移アニメーションの不具合と解決法

割とマイナーな気がしますが、少しハマったのでメモです。

条件と再現方法

  • navigationTranslationAnimatorをセットしてNavigationControllerの遷移アニメーションをカスタムしており、遷移アニメーションとして UIView.animate のアニメーションブロック内で frame を操作している。(例えば一覧画面から選択されたセルにセットされている画像が、そのまま次の画面に移動するような遷移アニメーションなど)
  • UIViewControllerInteractiveTransitioning のサブクラスを使ってインタラクティブジェスチャを実装している。

上記の条件のときに、ジェスチャで途中までアニメーションを進めて途中で指を離してキャンセルすると、元の状態に戻るアニメーションがiOS10の場合でのみおかしくなります。(サイズが縮んでいくようなアニメーションになる)

解決法

iOS10の場合だけ UIViewPropertyAnimator を使うことで解決しました。
iOS10未満は対応していないため、バージョンで分岐させる必要があります。

let animation: () -> Void = {
    // 一例です
    fromView.alpha      = 0
    toView.alpha        = 1.0
}
let completion: () -> Void = {
    // 一例です
    let isCancelled = transitionContext.transitionWasCancelled
    transitionContext.completeTransition(!isCancelled)
}

if UIDevice.current.systemVersion.compare("11", options: .numeric) == .orderedAscending
    && UIDevice.current.systemVersion.compare("10", options: .numeric) != .orderedAscending {
    // Only for iOS10 animation bug fix.
    let animator = UIViewPropertyAnimator(duration: duration, curve: .easeIn, animations: {
        animation()
    })
    animator.addCompletion({ (position) in
        completion()
    })
    animator.startAnimation()
} else {
    UIView.animate(withDuration: duration, delay: 0, options: [.beginFromCurrentState, .curveEaseIn], animations: {
        animation()
    }) { (finished) in
        completion()
    }
}

別の方法として、 frame を使う代わりに centertransform.scale を組み合わせるという方法もありますが、 UIImageView の場合だと contentMode に関係なく引き伸ばされて画質が粗くなったり、比率が崩れたりといった問題があります。

10/30/2017 追記

iOS11ではPropertyAnimatorを使うとInteractiveTransitionがうまく実行されず、UIView.animateであればiOS10のときのような不具合が発生することなく動きました。
このことから、iOS10のときにのみPropertyAnimatorを使う方法に変えないといけないようです。