Настройка действия кнопки «Назад» в контроллере навигации - PullRequest
175 голосов
/ 01 августа 2009

Я пытаюсь переписать действие по умолчанию кнопки «Назад» в контроллере навигации. Я предоставил цели действие на пользовательской кнопке. Странно то, что при назначении его через атрибут backbutton он не обращает на них внимания, а просто выводит текущее представление и возвращается к корню:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

Как только я установлю его через leftBarButtonItem на navigationItem, он вызовет мое действие, однако тогда кнопка будет выглядеть как круглая, а не как стрелка назад:

self.navigationItem.leftBarButtonItem = backButton;

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

Ответы [ 28 ]

2 голосов
/ 19 апреля 2016

Использование Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
2 голосов
/ 28 февраля 2017

Ниже приведена версия Swift 3 ответа @ oneway для отлавливания события кнопки возврата на панели навигации, прежде чем оно будет запущено. Поскольку UINavigationBarDelegate нельзя использовать для UIViewController, необходимо создать делегат, который будет запускаться при вызове navigationBar shouldPop.

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < (navigationBar.items?.count)! {                
            return true
        }

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

А затем в вашем контроллере представления добавьте функцию делегата:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

Я понял, что мы часто хотим добавить контроллер оповещений для пользователей, чтобы решить, хотят ли они вернуться. Если это так, вы всегда можете return false в функции navigationShouldPopOnBackButton() и закрыть контроллер вида, выполнив что-то вроде этого:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}
2 голосов
/ 15 марта 2015

Вот мое решение Swift. В вашем подклассе UIViewController переопределите метод navigationShouldPopOnBackButton.

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

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}
2 голосов
/ 01 августа 2009

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

Закрыть можно (без стрелки), создав обычную кнопку и скрыв кнопку возврата по умолчанию:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
2 голосов
/ 27 августа 2013

Существует более простой способ - просто подклассифицировать метод делегата переопределения UINavigationBar и ShouldPopItem метод .

1 голос
/ 28 февраля 2015

Swift

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}
1 голос
/ 27 мая 2015

Использование isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
1 голос
/ 02 мая 2015

Этот подход работал для меня (но кнопка «Назад» не будет иметь знак «<»): </p>

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}
1 голос
/ 03 апреля 2014

По крайней мере, в Xcode 5 есть простое и довольно хорошее (не идеальное) решение. В IB перетащите элемент «Панель кнопок» с панели «Утилиты» и поместите его в левой части панели навигации, где будет кнопка «Назад». Установите метку «Назад». У вас будет функциональная кнопка, которую вы можете привязать к вашему IBAction и закрыть свой viewController. Я делаю некоторую работу, а затем запускаю сеанс раскручивания, и он отлично работает.

Что не идеально, так это то, что эта кнопка не получает стрелку <и не переносит предыдущий заголовок ВК, но я думаю, что этим можно управлять. Для моих целей я установил новую кнопку «Назад» как кнопку «Готово», чтобы ее назначение было ясным. </p>

Вы также получите две кнопки «Назад» в навигаторе IB, но это достаточно просто обозначить для ясности.

enter image description here

1 голос
/ 26 января 2016

Ответ от @William верен, однако, если пользователь запускает жест с прокруткой назад и обратно, вызывается метод viewWillDisappear и даже self не будет в стеке навигации (то есть self.navigationController.viewControllers не будет содержать self), даже если прокрутка не завершена и контроллер представления фактически не извлечен. Таким образом, решение будет:

  1. Отключите жест смахивания назад в viewDidAppear и разрешите использовать кнопку «назад» только с помощью:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    
  2. Или просто используйте вместо него viewDidDisappear, как показано ниже:

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }
    
...