Edited at

カスタムのインタラクティブなトランジションでUIView.animateWithDurationのcompletionが呼ばれないことがある?

More than 3 years have passed since last update.


  • dismissのトランジション

  • スワイプに連動したトランジション

  • iOS 8.3

  • もの凄い勢いでスワイプする(笑)

で起きたのでメモ。


8.3以前は動いていたコード

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

let from: UIViewController! = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let to: UIViewController! = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)

UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
() -> Void in

// ここで色々やる

}) {
(finished) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}

という感じで、アニメーションを定義しておいて


  • UIScreenEdgePanGestureRecognizer

  • UIPanGestureRecognizer


  • UIPercentDrivenInteractiveTransition

を組み合わせて、スワイプするとdismissするように作る

switch gestureRecognizer.state {

case .Began:
if location.x < 0 { return }

interactiveTransition = UIPercentDrivenInteractiveTransition()
interactionInProgress = true

dismissViewControllerAnimated(true, completion: nil)

case .Changed:
interactiveTransition?.updateInteractiveTransition(progress)

case .Ended, .Cancelled:
interactionInProgress = false
if progress > 0.35 {
interactiveTransition?.finishInteractiveTransition()
} else {
interactiveTransition?.cancelInteractiveTransition()
}

default:
return
}

こんな感じでやっていた。


起きた問題

この状態でiOS 8.3でもの凄い勢いでスワイプすると


  • finishInteractiveTransition

  • cancelInteractiveTransition

は呼ばれるけど、animateTransition内のanimateWithDurationcompletionが呼ばれないという現象が起きた。

そのせいで、トランジションが完了したと認識できずに画面がフリーズしたような状態になってしまった。


解決策

場当たり的だけど、finishInteractiveTransitioncancelInteractiveTransitionを呼び出すルートに入るのはわかっていたので、ちょっとだけディレイさせてトランジションを強制的に終わらせるようにしたところ改善した。

もっと良い方法がないのだろうか…。


コード


  • アニメーションする所

private var currentTransitionContext: UIViewControllerContextTransitioning!

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
currentTransitionContext = transitionContext
let from: UIViewController! = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let to: UIViewController! = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)

UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
() -> Void in

// ここで色々やる

}) {
(finished) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
self.currentTransitionContext = nil
}
}

func forceFinish() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
if let transitionContext = self.currentTransitionContext {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
self.currentTransitionContext = nil
}
}
}


  • ジェスチャーの認識

switch gestureRecognizer.state {

case .Began:
if location.x < 0 { return }

interactiveTransition = UIPercentDrivenInteractiveTransition()
interactionInProgress = true

dismissViewControllerAnimated(true, completion: nil)

case .Changed:
interactiveTransition?.updateInteractiveTransition(progress)

case .Ended, .Cancelled:
interactionInProgress = false
if progress > 0.35 {
interactiveTransition?.finishInteractiveTransition()
} else {
interactiveTransition?.cancelInteractiveTransition()
}

forceFinish()

default:
return
}