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

Ошибка заголовка панели навигации с помощью interactivePopGestureRecognizer

У меня возникла странная проблема с заголовком UINavigationBar в приложении, когда interactivePopGestureRecognizer входит в игру. Я продемонстрировал демонстрационное приложение, чтобы продемонстрировать эту ошибку.

Настройка:

  • rootViewController - это UINavigationController.
  • FirstViewController скрыта панель навигации, а interactivePopGestureRecognizer.enabled = NO;
  • Second и ThirdViewController отобразится панель навигации и включен по умолчанию.

Ошибка:

Ошибка возникает при возврате из второго в первый вид с использованием popgesture. Если вы выберете второй вид на полпути, а затем вернитесь ко второму виду, заголовок навигации покажет "Второй просмотр" (как и ожидалось). Но когда вы перейдете на третий вид, название не изменится на "Третий вид". А затем нажав кнопку "Назад" третьего вида, навигационная панель будет запутана.

Пожалуйста, ознакомьтесь с моим демо-приложением. Любая помощь, объясняющая, почему эта ошибка происходит, будет оценена по достоинству. Спасибо!

4b9b3361

Ответ 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];
    }
}

Ответ 2

Исправление прост, но на данный момент у меня нет объяснений, почему это происходит.

Один ваш 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.

Ответ 3

Я не знаю, как вы делаете "FirstViewController имеет скрытую навигационную панель".

У меня такая же проблема, и я исправил ее, заменив

self.navigationController.navigationBarHidden = YES / NO;

по

[self.navigationController setNavigationBarHidden:YES / NO animated:animated];

Ответ 4

Я сдался, пытаясь заставить эту работу использовать мой собственный распознаватель проводов, который выталкивает стек навигации:

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 с правильным направлением.
  • Попасть в стек навигации, анимированный при обнаружении его прокрутки

Ответ 5

Вот что зафиксировало это для меня (Swift)

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