Обнаружение, когда кнопка "назад" нажата на навигационной панели

Мне нужно выполнить некоторые действия, когда кнопка "Назад" (возврат к предыдущему экрану, возврат к родительскому виду) нажата на навигационной панели.

Есть ли какой-нибудь метод, который я могу реализовать, чтобы поймать событие и отключить некоторые действия для приостановки и сохранения данных до исчезновения экрана?

Ответ 1

ОБНОВЛЕНИЕ: Согласно некоторым комментариям, решение в первоначальном ответе, похоже, не работает при определенных сценариях в iOS 8+. Я не могу подтвердить, что это действительно так, без дальнейших подробностей.

Для тех из вас, однако, в этой ситуации есть альтернатива. Обнаружение willMove(toParentViewController:) контроллера представления возможно путем переопределения willMove(toParentViewController:). Основная идея заключается в том, что контроллер представления выталкивается, когда parent равен nil.

Проверьте "Реализация Контроллера Контейнера Представления" для получения дополнительной информации.


Начиная с iOS 5, я обнаружил, что самый простой способ справиться с этой ситуацией - использовать новый метод - (BOOL)isMovingFromParentViewController:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController) {
    // Do your stuff here
  }
}

- (BOOL)isMovingFromParentViewController имеет смысл, когда вы - (BOOL)isMovingFromParentViewController контроллеры в стеке навигации.

Однако, если вы представляете контроллеры модального представления, вы должны использовать вместо этого - (BOOL)isBeingDismissed:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isBeingDismissed) {
    // Do your stuff here
  }
}

Как отмечено в этом вопросе, вы можете объединить оба свойства:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController || self.isBeingDismissed) {
    // Do your stuff here
  }
}

Другие решения основаны на существовании UINavigationBar. Вместо этого мне больше нравится мой подход, потому что он отделяет требуемые задачи от действия, которое вызвало событие, то есть нажатие кнопки возврата.

Ответ 2

Пока кнопки viewWillAppear() и viewDidDisappear() вызываются при нажатии кнопки "Назад", их также вызывают в другое время. Подробнее см. В конце ответа.

Использование UIViewController.parent

Обнаружение кнопки "Назад" лучше сделать, когда VC будет удален из нее родительским (NavigationController) с помощью willMoveToParentViewController(_:) ИЛИ didMoveToParentViewController()

Если родительский элемент равен нулю, контроллер просмотра выгружается из навигационного стека и отклоняется. Если parent не равен нулю, он добавляется в стек и отображается.

// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
     [super willMoveToParentViewController:parent];
    if (!parent){
       // The back button was pressed or interactive gesture used
    }
}


// Swift
override func willMove(toParentViewController parent: UIViewController?) {
    super.willMove(toParentViewController:parent)
    if parent == nil {
        // The back button was pressed or interactive gesture used
    }
}

Поменяйте willMove на didMove и убедитесь, что self.parent выполняет работу после отклонения контроллера вида.

Остановка увольнения

Обратите внимание, что проверка родителя не позволяет вам "приостановить" переход, если вам нужно выполнить какое-то асинхронное сохранение. Для этого вы можете реализовать следующее. Единственным недостатком здесь является то, что вы теряете причудливую кнопку стилизованной/анимированной кнопки iOS. Также будьте осторожны с интерактивным жестом салфетки. Для обработки этого случая используйте следующее.

var backButton : UIBarButtonItem!

override func viewDidLoad() {
    super.viewDidLoad()

     // Disable the swipe to make sure you get your chance to save
     self.navigationController?.interactivePopGestureRecognizer.enabled = false

     // Replace the default back button
    self.navigationItem.setHidesBackButton(true, animated: false)
    self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
    self.navigationItem.leftBarButtonItem = backButton
}

// Then handle the button selection
func goBack() {
    // Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
    self.navigationItem.leftBarButtonItem = nil
    someData.saveInBackground { (success, error) -> Void in
        if success {
            self.navigationController?.popViewControllerAnimated(true)
            // Don't forget to re-enable the interactive gesture
            self.navigationController?.interactivePopGestureRecognizer.enabled = true
        }
        else {
            self.navigationItem.leftBarButtonItem = self.backButton
            // Handle the error
        }
    }
}


Больше на просмотр будет/показалось бы

Если вы не получили проблему viewWillAppear viewDidDisappear, пропустите пример. Скажем, у вас есть три контроллера вида:

  • ListVC: табличный вид вещей
  • DetailVC: информация о вещи
  • SettingsVC: некоторые опции для вещи

Позволяет выполнять вызовы на detailVC по мере перехода от listVC в settingsVC и обратно к listVC

Список > Подробно (нажмите detailVC) Detail.viewDidAppear < - появится
Подробно > Настройки (нажмите настройкиVC) Detail.viewDidDisappear < - исчезнуть

И как мы вернемся...
Настройки > Подробно (pop settingsVC) Detail.viewDidAppear < - появится
Подробно > Список (pop detailVC) Detail.viewDidDisappear < - исчезать

Обратите внимание, что viewDidDisappear вызывается несколько раз, причем не только при возврате, но и при переходе вперед. Для быстрой операции, которая может потребоваться, но для более сложной операции, такой как сетевой вызов для сохранения, это может быть не так.

Ответ 3

Первый метод

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

Второй метод

-(void) viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
       // back button was pressed.  We know this is true because self is no longer
       // in the navigation stack.  
    }
    [super viewWillDisappear:animated];
}

Ответ 4

Я играю (или борюсь) с этой проблемой в течение двух дней. ИМО лучшим подходом является создание класса расширения и протокола, например:

@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
 * Indicates that the back button was pressed.
 * If this message is implemented the pop logic must be manually handled.
 */
- (void)backButtonPressed;
@end

@interface UINavigationController(BackButtonHandler)
@end

@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;
    SEL backButtonPressedSel = @selector(backButtonPressed);
    if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
        [topViewController performSelector:backButtonPressedSel];
        return NO;
    }
    else {
        [self popViewControllerAnimated:YES];
        return YES;
    }
}
@end

Это работает, потому что UINavigationController будет получать вызов navigationBar:shouldPopItem: каждый раз, когда вызывается контроллер вида. Там мы обнаруживаем, была ли нажата или нет (любая другая кнопка). Единственное, что вам нужно сделать, это реализовать протокол в контроллере представления, где нажата кнопка.

Не забудьте вручную вывести контроллер представления внутри backButtonPressedSel, если все в порядке.

Если у вас уже есть подклассы UINavigationViewController и реализовано navigationBar:shouldPopItem:, не беспокойтесь, это не помешает ему.

Вы также можете быть заинтересованы в отключении жест назад.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

Ответ 5

Это работает для меня в iOS 9.3.x с Swift:

override func didMoveToParentViewController(parent: UIViewController?) {
    super.didMoveToParentViewController(parent)

    if parent == self.navigationController?.parentViewController {
        print("Back tapped")
    }
}

В отличие от других решений здесь это не кажется неожиданным.

Ответ 6

Для записи я думаю, что это больше того, что он искал...

    UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];

    self.navigationItem.leftBarButtonItem = l_backButton;


    - (void) backToRootView:(id)sender {

        // Perform some custom code

        [self.navigationController popToRootViewControllerAnimated:YES];
    }

Ответ 7

Те, кто утверждает, что это не работает, ошибаются:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isMovingFromParent {
        print("we are being popped")
    }
}

Это отлично работает. Так в чем же причина распространенного мифа?

Кажется, проблема связана с неправильной реализацией другого метода, а именно с тем, что реализация willMove(toParent:) забыла вызвать super.

Если вы реализуете willMove(toParent:) без вызова super, то self.isMovingFromParent будет иметь значение false и использование viewWillDisappear окажется неудачным. Это не подвело; ты сломал это.

Ответ 8

Как говорит purrrminator, ответ elitalon не совсем прав, так как your stuff будет выполняться даже при программном программировании контроллера.

Решение, которое я нашел до сих пор, не очень хорошо, но оно работает для меня. Кроме того, что сказал elitalon, я также проверяю, что я выскакиваю программно или нет:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

Вы должны добавить это свойство к своему контроллеру и установить его в YES перед программным нажатием:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

Спасибо за вашу помощь!

Ответ 9

Лучший способ - использовать методы делегирования UINavigationController

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated

С помощью этого вы можете узнать, какой контроллер показывает UINavigationController.

if ([viewController isKindOfClass:[HomeController class]]) {
    NSLog(@"Show home controller");
}

Ответ 10

7ynk3r ответ был действительно близок к тому, что я использовал в конце, но он нуждался в некоторых настройках:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;

    if (wasBackButtonClicked) {
        if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
            // if user did press back on the view controller where you handle the navBackButtonPressed
            [topViewController performSelector:@selector(navBackButtonPressed)];
            return NO;
        } else {
            // if user did press back but you are not on the view controller that can handle the navBackButtonPressed
            [self popViewControllerAnimated:YES];
            return YES;
        }
    } else {
        // when you call popViewController programmatically you do not want to pop it twice
        return YES;
    }
}

Ответ 11

Вы должны проверить UINavigationBarDelegate Protocol. В этом случае вы можете использовать метод navigationBar: shouldPopItem:.

Ответ 12

Как сказал Coli88, вы должны проверить протокол UINavigationBarDelegate.

В более общем виде вы также можете использовать - (void)viewWillDisapear:(BOOL)animated для выполнения пользовательской работы, когда вид, сохраненный текущим видимым контроллером представления, вот-вот исчезнет. К сожалению, это будет беспокоить толкание и всплывающие окна.

Ответ 13

Для Swift с помощью UINavigationController:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}

Ответ 14

Я решил эту проблему, добавив UIControl в navigationBar с левой стороны.

UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];

И вам нужно помнить, чтобы удалить его, когда вид исчезнет:

- (void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.leftItemControl) {
        [self.leftItemControl removeFromSuperview];
    }    
}

Что все!

Ответ 15

Вы можете использовать обратный вызов кнопки "Назад", например:

- (BOOL) navigationShouldPopOnBackButton
{
    [self backAction];
    return NO;
}

- (void) backAction {
    // your code goes here
    // show confirmation alert, for example
    // ...
}

для быстрой версии вы можете сделать что-то в вашем viewcontroller, как

extension UIViewController {
     @objc func navigationShouldPopOnBackButton() -> Bool {
     return true
    }
}

extension UINavigationController: UINavigationBarDelegate {
     public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
          return self.topViewController?.navigationShouldPopOnBackButton() ?? true
    }
}

Ниже того, что вы поместили в viewcontroller, где вы хотите контролировать действие кнопки назад:

override func navigationShouldPopOnBackButton() -> Bool {
    self.backAction()//Your action you want to perform.

    return true
}

Ответ 16

self.navigationController.isMovingFromParentViewController больше не работает на iOS8 и 9, я использую:

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.navigationController.topViewController != self)
    {
        // Is Popping
    }
}

Ответ 17

(СВИФТ)

finaly found solution.. метод, который мы искали, - это "willShowViewController", который является методом делегирования UINavigationController

//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        //set delegate to current class (self)
        navigationController?.delegate = self
    }

    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
        //MyViewController shoud be the name of your parent Class
        if var myViewController = viewController as? MyViewController {
            //YOUR STUFF
        }
    }
}