it-swarm.com.ru

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

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

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

117
user440096

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

Для тех из вас, однако, в этой ситуации есть альтернатива. Обнаружение, когда контроллер представления выталкивается, возможно путем переопределения 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)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. Вместо этого мне больше нравится мой подход, потому что он отделяет требуемые задачи от действия, которое вызвало событие, то есть нажатие кнопки "назад".

297
elitalon

Хотя viewWillAppear() и viewDidDisappear() вызываются при нажатии кнопки "назад", они также вызываются в другое время. Смотрите конец ответа для более подробной информации.

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

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

Если parent равен nil, контроллер представления извлекается из стека навигации и удаляется. Если parent не равен nil, он добавляется в стек и представляется.

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


Больше на вид будет/действительно появится

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

  1. ListVC: Табличное представление вещей
  2. DetailVC: Подробности о вещи
  3. Настройки ВК: Некоторые варианты вещи

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

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

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

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

91
WCByrne

Первый метод

- (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];
}
16
Xar E Ahmer

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

@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;
}
9
7ynk3r

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

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

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

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

7
Chris Villa

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

    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];
    }
4
Paul Brady

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

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 окажется неудачным. Это не подвело; ты сломал это.

4
matt

Как говорит 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];

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

2
Ferran Maylinch

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

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

Используя это, вы можете узнать, какой контроллер показывает UINavigationController.

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

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;
    }
}
2
micromanc3r

Для Swift с UINavigationController:

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

Как сказал Coli88, вам следует проверить протокол UINavigationBarDelegate.

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

1
ramdam

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

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];
    }    
}

Это все!

1
Eric

Вы должны проверить INavigationBarDelegate Protocol . В этом случае вы можете захотеть использовать навигационный бар: shouldPopItem: метод.

1
Coli88

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

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

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

для Swift версии вы можете сделать что-то вроде в глобальной области видимости

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
}
0
Pedro Magalhães

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

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