🤖

UIView.animateWithDurationのcompletionが呼ばれないことがある?

2021/12/29に公開
  • 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
}

Discussion