Ответ 1
Удалить красные сапоги
Прежде всего, ваш пример может быть значительно упрощен. Вы должны удалить все элементы viewDidLoad
, так как это полная красная селедка и просто усложняет проблему. Вы не должны играть с делегатом распознавателя позы для каждого изменения контроллера вида; и включение и выключение распознавателя поцелуя не имеет отношения к примеру (оно включено по умолчанию и должно быть просто оставлено для этого примера). Поэтому удалите этот вид во всех трех контроллерах представления:
- (void)viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
(Не удаляйте код, который устанавливает self.title
, хотя вы могли бы сделать вещи еще проще, выполнив это в файле xib
для каждого контроллера представления.)
Вы также можете избавиться от других неиспользуемых методов, таких как методы init...
и методы оповещения о памяти.
Еще одна проблема, кстати, заключается в том, что вы забыли называть super
в своих реализациях viewWillAppear:
. Требуется, чтобы вы это сделали. Я не думаю, что это влияет на ошибку, но хорошо соблюдать все правила, прежде чем вы начнете пытаться отследить эти вещи.
Теперь ошибка все же происходит, но у нас гораздо более простой код, поэтому мы можем начать изолировать проблему.
Как работает поп-стиль
Так в чем причина проблемы? Я думаю, что самый очевидный способ понять это - понять, как работает поп-жест. Это интерактивная анимация перехода контроллера. Это право - это анимация. Способ его работы заключается в том, что поп-анимация (слайд слева) прикреплена к уровню супервизора, но с speed
0, так что она фактически не запускается. По мере того, как идет жест, уровень timeOffset
слоя постоянно обновляется, так что появляется соответствующий "кадр" анимации. Таким образом, похоже, что вы перетаскиваете представление, но это не так; вы просто делаете жест, и анимация идет с одинаковой скоростью и в той же степени. Я объяснил этот механизм в этом ответе: fooobar.com/questions/263168/...
Самое главное (обратите внимание на эту часть), если жест оставлен посередине (что почти наверняка будет), будет принято решение о том, является ли жест более чем на полпути завершенным и основан на это либо анимация быстро воспроизводится до конца (т.е. для параметра speed
установлено что-то вроде 3
), или анимация запускается в обратном направлении к началу (т.е. для параметра speed
установлено что-то вроде -3
)).
Решения и почему они работают
Теперь поговорим об ошибке. Здесь есть два осложнения: вы случайно ударили:
-
Когда начнется поп-анимация и поп-жест,
viewWillAppear:
вызывается для предыдущего контроллера представлений, хотя представление может не отображаться в конечном счете (потому что это интерактивный жест, а жест может быть отменен). Это может быть серьезной проблемой, если вы привыкли к предположению, что заviewWillAppear:
всегда следует представление, фактически принимающее экран (иviewDidAppear:
), потому что это ситуация, в которой эти вещи могут не произойти. (Как говорит Apple в видеороликах WWDC 2013, "просмотр будет отображаться" на самом деле означает, что "может появиться представление".) -
Существует вторичный набор анимаций, а именно все, что связано с навигационной панелью - изменение названия (оно должно исчезать в поле зрения) и, в данном случае, изменение между не скрытым и скрытым. Среда выполнения пытается скоординировать вторичный набор анимаций с анимацией скользящего просмотра. Но вы сделали это трудным, не требуя анимации, когда панель скрыта или показана.
Таким образом, как вам уже сказали, одно решение должно изменить animated:NO
на animated:YES
на весь ваш код. Таким образом, отображение и скрытие навигационной панели упорядочивается как часть анимации. Поэтому, когда жест отменяется, и анимация запускается в обратном направлении до начала, отображение/скрытие навигации также выполняется назад к началу - обе вещи теперь остаются скоординированными.
Но что, если вы действительно не хотите делать это изменение? Ну, другое решение должно изменить viewWillAppear:
на viewDidAppear:
повсюду. Как я уже сказал, viewWillAppear:
вызывается в начале анимации, даже если жест не будет завершен, что приводит к тому, что вещи выходят из-под удара. Но viewDidAppear:
вызывается только в том случае, если жест завершен (не отменен) и когда анимация уже завершена.
Какое из этих двух вариантов я предпочитаю? Никто из них! Они заставляют вас вносить изменения, которые вы не хотите делать. Реальное решение, как мне кажется, заключается в использовании координатора перехода.
Координатор перехода
Координатор перехода - это объект, предоставленный системой для этой цели, то есть для обнаружения того, что мы участвуем в интерактивном переходе и ведем себя по-разному в зависимости от того, отменено или нет.
Концентрируйтесь только на реализации OneViewController viewWillAppear:
. Это то, о чем все перепуталось. Когда вы находитесь в TwoViewController, и вы начинаете жест панорамы слева, вызывается OneViewController viewWillAppear:
. Но тогда вы отменяете, отпустив жест без его завершения. В одном случае вы не хотите делать то, что вы делали в OneViewController viewWillAppear:
. Именно это позволяет координатор перехода.
Вот, вот, переписывается OneViewController viewWillAppear:
. Это устраняет проблему без внесения каких-либо изменений:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
if (tc && [tc initiallyInteractive]) {
[tc notifyWhenInteractionEndsUsingBlock:
^(id<UIViewControllerTransitionCoordinatorContext> context) {
if ([context isCancelled]) {
// do nothing!
} else { // not cancelled, do it
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
}];
} else { // not interactive, do it
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
}