Убрать красные сельди
Во-первых, ваш пример можно сильно упростить. Вам следует удалить весь 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
слоя постоянно обновляется, так что появляется соответствующий «кадр» анимации. Таким образом, выглядит так, как будто вы перетаскиваете представление, но это не так; вы просто делаете жест, а анимация выполняется с той же скоростью и в той же степени. Я объяснил этот механизм в этом ответе: https://stackoverflow.com/a/22677298/341994
Наиболее важно (обратите внимание на эту часть), если жест отброшен в середине (что почти наверняка будет), принимается решение относительно того, завершен ли жест более чем наполовину, и на основании этого либо анимация быстро воспроизводится до конца (т.е. speed
устанавливается на что-то вроде 3
) или анимация запускается в обратном направлении до начала (т.е. speed
устанавливается на что-то вроде -3
).
Решения и почему они работают
Теперь поговорим об ошибке. Здесь есть два осложнения, с которыми вы случайно столкнулись:
Когда начинается всплывающая анимация и всплывающий жест, viewWillAppear:
вызывается для предыдущего контроллера представления, даже если представление в конечном итоге может не появиться (потому что это интерактивный жест, и этот жест может быть отменен). Это может быть серьезной проблемой, если вы привыкли к предположению, что за viewWillAppear:
всегда следует представление, фактически захватывающее экран (и вызываемое viewDidAppear:
), потому что это ситуация, в которой эти вещи могут не произойдет. (Как Apple говорит в видеороликах WWDC 2013, «представление появится» на самом деле означает «представление может появиться».)
Есть вторичный набор анимаций, а именно все, что связано с панелью навигации - смена заголовка (он должен исчезнуть из поля зрения) и, в данном случае, смена между не скрытым и скрытым. Среда выполнения пытается согласовать вторичный набор анимаций с анимацией скользящего просмотра. Но вы усложнили это, вызвав нет анимации, когда полоса скрыта или отображается.
Таким образом, как вам уже было сказано, одно решение - изменить animated:NO
на animated:YES
во всем коде. Таким образом, отображение и скрытие панели навигации упорядочивается как часть анимации. Следовательно, когда жест отменяется и анимация выполняется в обратном направлении к началу, отображение / скрытие навигации также выполняется в обратном направлении к началу - теперь эти две вещи остаются согласованными.
Но что, если вы действительно не хотите вносить это изменение? Что ж, другое решение - полностью изменить viewWillAppear:
на viewDidAppear:
. Как я уже сказал, viewWillAppear:
вызывается в начале анимации, даже если жест не будет завершен, что приводит к выходу из строя. Но viewDidAppear:
вызывается только в том случае, если жест завершен (не отменен) и когда анимация уже закончилась.
Какое из этих двух решений я предпочитаю? Никто из них! Они оба заставляют вас вносить изменения, которых вы не хотите. Мне кажется, что реальным решением является использование координатора перехода.
Координатор перехода
Координатор перехода - это объект, предоставляемый системой для этой самой цели, то есть для обнаружения того, что мы участвуем в интерактивном переходе, и для того, чтобы вести себя по-разному в зависимости от того, отменен он или нет.
Сосредоточьтесь только на реализации viewWillAppear:
в OneViewController. Здесь все запутывается. Когда вы находитесь в TwoViewController и запускаете жест панорамирования слева, вызывается viewWillAppear:
OneViewController. Но затем вы отменяете жест, не завершая его. Только в этом одном случае вы не хотите, чтобы не выполняли то, что делали в viewWillAppear:
OneViewController. И это именно то, что позволяет вам делать координатор перехода.
Итак, это переработанная версия viewWillAppear:
в OneViewController. Это устраняет проблему без необходимости вносить какие-либо другие изменения:
-(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];
}
}
person
matt
schedule
26.04.2014
git checkout
, чтобы проверить каждое из трех решений и попробовать их по очереди, чтобы вы могли изучить код и посмотреть, как они работают. - person matt   schedule 26.04.2014