Canceling interactive UINavigationController pop gesture does not call UINavigationControllerDelegate methods

前端 未结 3 1363
忘掉有多难
忘掉有多难 2021-02-01 05:04

If you drag the edge of a UIViewController to begin an interactive pop transition within a UINavigationController, the UIViewController un

相关标签:
3条回答
  • 2021-02-01 05:13

    I translated @Dima's answer to Swift for my project:

    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    
        transitionCoordinator()?.notifyWhenInteractionEndsUsingBlock { context in
            guard context.isCancelled(), let fromViewController = context.viewControllerForKey(UITransitionContextFromViewControllerKey) else { return }
    
            self.navigationController(self, willShowViewController: fromViewController, animated: animated)
    
            let animationCompletion: NSTimeInterval = context.transitionDuration() * Double(context.percentComplete())
    
            let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
            dispatch_after(delayTime, dispatch_get_main_queue()) {
                self.navigationController(self, didShowViewController: fromViewController, animated: animated)
            }            
        }
    
        /* Your normal behavior goes here */
    
    }
    

    Note that I don't check for the existence of an implementation of navigationController(_:didShowViewController:animated:), although I believe this is checked at compile-time in Swift, and that you'll get a compiler error if you attempt to call this when it's unimplemented.

    0 讨论(0)
  • 2021-02-01 05:25

    For anyone interested, I have found 2 ways to work around this at the UINavigationControllerDelegate level.

    1. Use KVO to observe the state property of the interactivePopGestureRecognizer. Unfortunately, canceling the transition does not change the state to UIGestureRecognizerStateFailed but instead just UIGestureRecognizerStateEnded, so you would need to write a bit of additional code to keep track of what happened if you needed to discern between a cancelled or completed pop.

    2. After testing it, this is probably the better solution: Use the navigationController:willShowViewController:animated: method to add a notification block to the transition coordinator. It looks something like this:

      - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
      {
          [[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context)
          {
              if([context isCancelled])
              {
                  UIViewController *fromViewController = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
                  [self navigationController:navigationController willShowViewController:fromViewController animated:animated];
      
                  if([self respondsToSelector:@selector(navigationController:didShowViewController:animated:)])
                  {
                      NSTimeInterval animationCompletion = [context transitionDuration] * (double)[context percentComplete];
      
                      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((uint64_t)animationCompletion * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                          [self navigationController:navigationController didShowViewController:fromViewController animated:animated];
                      });
                  }
      
      
              }
          }];
      }
      

    I was hesitant to use this solution at first because the documentation was unclear about whether or not you could set more than one of these (since in that case, if an unknowing view controller also set its own notification block, it could potentially either replace this one or get replaced by this one). After testing it though, it appears that it is not a 1:1 relationship and you can add multiple notification blocks safely.

    edit

    I edited the code above to delay the navigationController:didShowViewController:animated: call to only be called when the animation is supposed to be completed to more closely match the expected default behavior.

    0 讨论(0)
  • 2021-02-01 05:31

    Swift 3:

    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        transitionCoordinator?.notifyWhenInteractionEnds { context in
            guard context.isCancelled, let fromViewController = context.viewController(forKey: UITransitionContextViewControllerKey.from) else { return }
            self.navigationController(self, willShow: fromViewController, animated: animated)
            let animationCompletion: TimeInterval = context.transitionDuration * Double(context.percentComplete)
            DispatchQueue.main.asyncAfter(deadline: .now() + animationCompletion) {
                self.navigationController(self, didShow: fromViewController, animated: animated)
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题