Как "отменить" просмотр переходов внешнего вида для пользовательских переходов контроллера контейнера
Я создал собственный контроллер контейнера, который работает аналогично UIPageViewController
чтобы я мог реализовать некоторые пользовательские переходы и логику источника данных. Я пытался подражать тому, как новые API перехода контроллера представления клиента работают в iOS 7, и он работает хорошо, за исключением некоторых раздражающих причуд с обратными вызовами представления представления, когда переход отменяется...
А именно, при выполнении перехода, когда именно beginAppearanceTransition:animated:
а также endAppearanceTransition
называться?
Мой пользовательский контейнерный класс имеет такой код:
- (BOOL)shouldAutomaticallyForwardAppearanceMethods
{
return NO; // Since the automatic callbacks are wrong for our custom transition.
}
- (void)startTransition:(CustomTransitionContext *)context
{
// Get reference to the view controllers involved in the transition.
UIViewController *oldVC = [context viewControllerForKey:UITransitionContextFromViewController];
UIViewController *newVC = [context UITransitionContextToViewController];
// Prepare parent/child relationship changes.
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Begin view appearance transitions.
[oldVC beginAppearanceTransition:NO animated:[context isAnimated]];
[newVC beginAppearanceTransition:YES animated:[context isAnimated]];
// Register a completion handler to run when the context's completeTransition: method is called.
__weak CustomContainerController *weakSelf = self;
context.transitionCompletionHandler = ^(BOOL complete) {
// End appearance transitions here?
[oldVC endAppearanceTransition];
[newVC endAppearanceTransition];
if (complete) {
// Or only if the transition isn't cancelled; here?
[oldVC endAppearanceTransition];
[newVC endAppearanceTransition];
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:weakSelf];
} else {
[newVC removeFromParentViewController];
[oldVC didMoveToParentViewController];
}
}
if ([context isInteractive] && [self.transitionController conformsToProtocol:@protocol(UIViewControllerInteractiveTransitioning)]) {
// Start the interactive transition.
[self.transitionController startInteractiveTransition:context];
} else if ([context isAnimated] && [self.transitionController conformsToProtocol:@protocol(UIViewControllerAnimatedTransitioning)]) {
// Start the animated transition.
[self.transitionController animateTransition:context];
} else {
// Just complete the transition.
[context completeTransition:YES];
}
}
Так что если я позвоню endAppearanceTransition
независимо от того, был ли переход отменен, мои обратные вызовы вида выглядят так, когда переход отменен:
oldVC viewWillDisappear: // Fine
newVC viewWillAppear: // Fine
// ... some time later transition is cancelled ...
oldVC viewDidDisappear: // Wrong! This view controller's view is staying.
newVC viewDidAppear: // Wrong! The appearance of this view controllers view was cancelled.
Если я позвоню endAppearanceTransition
только когда переход завершился успешно, сначала все выглядит лучше:
oldVC viewWillDisappear: // Fine
newVC viewWillAppear: // Fine
// ... some time later transition is cancelled ...
// ... silence. (which is correct - neither view actually appeared or disappeared,
// and I can undo side effects in viewWill(Dis)Appear using the
// transitionCoordinator object)
Но затем, в следующий раз, когда я начинаю переход, я не получаю никаких обратных вызовов внешнего вида. Следующий набор обратных вызовов по внешнему виду появляется только после beginAppearanceTransition:animated:
за которым следует endApperanceTransition
вызов. Стоит отметить, что я не получаю часто сообщаемое предупреждение консоли "Неуравновешенные вызовы, чтобы начать / закончить переходы появления для ViewController".
На WWDC 2013 Session 218 (Пользовательские переходы с использованием контроллеров представлений) Брюс Нило шутит, что его коллеги говорят ему, что viewWillAppear:
& viewWillDisappear:
действительно должен называться viewMightAppear:
& viewMightDisappear:
(см. раздел этой сессии, начинающейся в 42:00). Учитывая, что мы сейчас находимся в сфере отменяемых интерактивных жестов, кажется, что нам нужен cancelAppearanceTransition
(или же endAppearanceTransition:(BOOL)finished
) метод для пользовательских контейнеров.
Кто-нибудь знает, что я делаю не так? Или отменяемые, настраиваемые переходы в пользовательских контейнерах просто еще не поддерживаются должным образом?
1 ответ
Как и в случае с SO, вы часами бесполезно работаете над проблемой, а затем сразу после публикации вопроса вы найдете ответ...
Я смотрел на обратные вызовы появления UINavigationController
встроенный interactivePopGestureRecognizer
чтобы увидеть, что происходит, когда отменяется переход, и оказывается, что мое предположение о том, какие методы появления следует вызывать, было неверным.
Для контроллера представления, который успешно переведен, получены следующие обратные вызовы (как и следовало ожидать):
transition
finished
viewWillAppear: ----------> viewDidAppear:
Но для перехода контроллера представления, который является неудачным (то есть переход был отменен), контроллер представления получает следующие обратные вызовы:
transition immediately
cancelled followed by
viewWillAppear: ----------> viewWillDisappear: ----------> viewDidDisappear:
Таким образом, мы получаем немного больше из этой хитрой семантики внешнего вида; Я не уверен, как вид может исчезнуть, если он еще не появился! Ну да ладно... я полагаю, что это немного лучше, чем представление о том, что оно появится, а потом ничего не происходит.
Итак, возвращаясь к моему CustomTransitionContext
"s transitionCompletionHandler
код, решение выглядит так:
...
// Register a completion handler to run when the context's completeTransition: method is called.
__weak CustomContainerController *weakSelf = self;
__weak CustomTransitionContext *weakContext = context;
context.transitionCompletionHandler = ^(BOOL complete) {
if (complete) {
// End the appearance transitions we began earlier.
[oldVC endAppearanceTransition];
[newVC endAppearanceTransition];
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:weakSelf];
} else {
// Before ending each appearance transition, begin an
// appearance transition in the opposite direction.
[newVC beginAppearanceTransition:NO animated:[weakContext isAnimated]];
[newVC endAppearanceTransition];
[oldVC beginAppearanceTransition:YES animated:[weakContext isAnimated]];
[oldVC endAppearanceTransition];
[newVC removeFromParentViewController];
[oldVC didMoveToParentViewController];
}
}
....
Так что теперь обратные вызовы внешнего вида выглядят так:
oldVC viewWillDisappear:
newVC viewWillAppear:
// ... some time later transition is cancelled ...
newVC viewWillDisappear:
newVC viewDidDisappear:
oldVC viewWillAppear:
oldVC viewDidAppear:
Кроме того, последующие переходы теперь вызывают обратные вызовы, как и ожидалось (так как мы закрываем все переходы вызовами endAppearanceTransition
).