Подтвердить что ты не робот

Отмена интерактивного жестов по умолчанию UINavigationController не вызывает методы UINavigationControllerDelegate

Если вы перетащите край UIViewController, чтобы начать интерактивный поп-переход внутри UINavigationController, UIViewController под током будет вызван viewWillAppear:, за которым следует метод UINavigationControllerDelegate navigationController:willShowViewController:animated:.

Если вы отменяете переход (т.е. перетаскиваемый контроллер помещается обратно туда, где он был и не извлекается), viewWillAppear: и viewDidAppear: вызываются на контроллере вида сверху, как и ожидалось, но методы делегата navigationController:willShowViewController:animated: и navigationController:didShowViewController:animated: не отображаются. "т. Кажется, что по крайней мере один или оба из них должны быть вызваны, учитывая, что методы жизненного цикла представления UIViewController вызваны. Мне интересно, является ли это преднамеренным или ошибка в UINavigationController.

Что мне действительно нужно, так это уметь видеть, когда интерактивная популярность отменяется, либо в моем подклассе UINavigationController, либо в его UINavigationControllerDelegate. Есть ли очевидный способ сделать это?

редактировать

Я все еще ищу решение этой проблемы, но хотел бы отметить, что я сообщил об этой проблеме как об ошибке в Apple. Глядя на документацию, нет причин, по которым эти методы делегата не должны вызываться, особенно если учесть, что методы жизненного цикла эквивалентного представления ДОЛЖНЫ вызываться.

edit2

Мой радарный билет (16823313) был закрыт сегодня (21 мая 2015 года) и помечен как задумано. :(

Инжиниринг установил, что эта проблема ведет себя как задумано на следующую информацию:

Это на самом деле правильное поведение. Навигационный переход что происходит из B → A, если вы отмените его в середине перехода, вы не получит didShowViewController: метод. Отмена этого переход не должен рассматриваться как переход от A → B, потому что Вы никогда не достигли А.

view [Will/Did] Появление должно все еще называться как ожидалось.

Это довольно обидно, так как это нелогично, но обходной путь в моем ответе ниже должен хорошо работать в обозримом будущем, по крайней мере, для моего варианта использования.

4b9b3361

Ответ 1

Для всех, кого это интересует, я нашел два способа обойти это на уровне UINavigationControllerDelegate.

  • Используйте KVO для наблюдения за свойством state interactivePopGestureRecognizer. К сожалению, отмена перехода не изменяет состояние на UIGestureRecognizerStateFailed, а вместо этого просто UIGestureRecognizerStateEnded, поэтому вам нужно будет написать немного дополнительного кода, чтобы отслеживать, что произошло, если вам нужно различать отмененный или завершенный поп.

  • После тестирования это, вероятно, лучшее решение: используйте метод navigationController:willShowViewController:animated:, чтобы добавить блок уведомлений к координатору перехода. Это выглядит примерно так:

    - (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];
                    });
                }
    
    
            }
        }];
    }
    

Я не решался сначала использовать это решение, потому что в документации было неясно, можно ли установить более одного из них (так как в этом случае, если контроллер представления непознанного представления также установил свой собственный блок уведомлений, он мог бы потенциально либо замените это, либо замените его на этот). Однако после тестирования, похоже, что это не соотношение 1:1, и вы можете безопасно добавить несколько блоков уведомлений.

изменить

Я отредактировал код выше, чтобы задержать вызов navigationController:didShowViewController:animated: только для вызова, когда предполагается, что анимация будет завершена, чтобы более точно соответствовать ожидаемому поведению по умолчанию.

Ответ 2

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)
        }
    }
}

Ответ 3

Я перевел @Dima ответ в Swift для моего проекта:

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 */

}

Обратите внимание, что я не проверяю существование реализации navigationController(_:didShowViewController:animated:), хотя я считаю, что это проверено во время компиляции в Swift, и что вы получите ошибку компилятора, если попытаетесь вызвать это, когда он не реализован.