Ошибка заголовка навигационной панели с interactivePopGestureRecognizer
У меня странная проблема с UINavigationBar
название в приложении, когда interactivePopGestureRecognizer
вступает в игру. Я сделал демонстрационное приложение, чтобы продемонстрировать эту ошибку.
Настроить:
- RootViewController является
UINavigationController
, FirstViewController
скрыта ли панель навигации, иinteractivePopGestureRecognizer.enabled = NO;
Second
а такжеThirdViewController
s имеет видимую панель навигации и включенную всплывающую панель.
ошибка:
Ошибка возникает при переходе со второго на первый вид с использованием всплывающей подсказки. Если вы потяните второй вид наполовину, а затем вернетесь ко второму виду, заголовок навигации покажет "Второй вид" (как и ожидалось). Но при переходе к третьему виду заголовок не изменится на "Третий вид". И затем при нажатии кнопки "Назад" в третьем представлении навигационная панель будет испорчена.
Пожалуйста, проверьте мое демо-приложение. Любая помощь, объясняющая, почему эта ошибка происходит, будет оценена. Спасибо!
5 ответов
Удалить красную сельдь
Прежде всего, ваш пример может быть значительно упрощен. Вы должны удалить все 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:
, Это необходимо сделать это. Я не думаю, что это влияет на ошибку, но хорошо соблюдать все правила, прежде чем вы начнете пытаться выследить эти вещи.
Теперь ошибка все еще возникает, но у нас гораздо более простой код, поэтому мы можем начать выделять проблему.
Как работает поп-жест
Так в чем же причина проблемы? Я думаю, что самый очевидный способ понять это - понять, как работает поп-жест. Это интерактивная анимация перехода контроллера представления. Это верно - это анимация. Это работает так, что поп-анимация (слайд слева) прикреплена к слою superview, но с speed
0, так что он на самом деле не работает. Поскольку жест продолжается, timeOffset
слоя постоянно обновляется, так что появляется соответствующий "кадр" анимации. Таким образом, похоже, что вы перетаскиваете представление, но это не так; вы просто делаете жест, и анимация идет с той же скоростью и в той же степени. Я объяснил этот механизм в этом ответе: /questions/22868644/ios-kak-sdelat-animatsiyu-trekov-kasanij/22868650#22868650
Наиболее важно (обратите внимание на эту часть), если в середине отказался от жеста (который он почти наверняка будет), принимается решение о том, завершен ли жест более чем наполовину, и на основании этого либо анимация быстро воспроизводится до конца (т.е. 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];
}
}
Исправление простое, но у меня нет никакого объяснения в данный момент, почему это происходит.
Один твой OneViewController поменяй свой viewWillAppear
чтобы,
-(void)viewWillAppear:(BOOL)animated{
// [self.navigationController setNavigationBarHidden:YES animated:NO];
self.navigationController.navigationBar.hidden = YES;
}
а на втором и третьем виде контроллеры меняют его на
-(void)viewWillAppear:(BOOL)animated{
//[self.navigationController setNavigationBarHidden:NO animated:NO];
self.navigationController.navigationBar.hidden = NO;
}
Странно, но это решит проблему, когда мы будем напрямую использовать скрытое свойство UINavigationBar.
Я не знаю, как вы делаете "FirstViewController скрыл панель навигации".
У меня такая же проблема, и я исправил ее, заменив
self.navigationController.navigationBarHidden = YES / NO;
от
[self.navigationController setNavigationBarHidden:YES / NO animated:animated];
Я прекратил попытки заставить эту работу использовать свой собственный распознаватель пролистывания, который выдает стек навигации:
override func viewDidLoad() {
super.viewDidLoad()
// disable system swipe back gesture and add our own
navigationController?.interactivePopGestureRecognizer?.enabled = false
let swipeBackGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeBackAction:")
swipeBackGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
tableView.addGestureRecognizer(swipeBackGestureRecognizer)
}
func swipeBackAction(sender: UISwipeGestureRecognizer) {
navigationController?.popViewControllerAnimated(true)
}
- Отключить систему interactivePopGestureRecognizer
- Создайте свой собственный UISwipeGestureRecognizer с правильным направлением
- Выдвиньте стек навигации, анимированный при обнаружении удара
Вот что исправило это для меня (Свифт)
1-й вид контроллера:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
2-й и 3-й вид контроллеров:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}