обратный вызов кнопки в навигационном контроллере в iOS - PullRequest
97 голосов
/ 07 марта 2011

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

Ответы [ 12 ]

160 голосов
/ 09 июля 2012

Ответ Уильяма Джокюша решить эту проблему с помощью простого трюка.

-(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];
}
82 голосов
/ 30 мая 2013

На мой взгляд лучшее решение.

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

Но работает только с iOS5 +

26 голосов
/ 20 апреля 2013

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

в viewDidLoad создайте UIBarButtonItem и установите для него self.navigationItem.leftBarButtonItem, передав в sel

- (void) viewDidLoad
{
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”back”
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];

self.navigationItem.leftBarButtonItem = backButton;
[backButton release];

}
- (void) handleBack:(id)sender
{
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];

}

Затем вы можете сделать такие вещи, как поднять UIAlertView для подтверждения действия, затем открыть контроллер представления и т. Д.

Или вместо создания новой кнопки вы можете использовать методы делегата UINavigationController для выполнения действий при нажатии кнопки "Назад".

9 голосов
/ 26 ноября 2015

Я в конечном итоге с этими решениями. По нажатию кнопки назад вызывается метод viewDidDisappear. мы можем проверить, вызвав селектор isMovingFromParentViewController, который возвращает true. мы можем передать данные обратно (используя Delegate). надеюсь, это кому-нибудь поможет.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

    }
    if (self.isMovingFromParentViewController) {
       //moving back
        //pass to viewCollection delegate and update UI
        [self.delegateObject passBackSavedData:self.dataModel];

    }
}
7 голосов
/ 13 июля 2016

Это правильный способ обнаружить это.

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        //do stuff

    }
}

этот метод вызывается, когда также отображается представление.Поэтому проверка parent == nil предназначена для выталкивания контроллера представления из стека

6 голосов
/ 18 марта 2014

Для «ДО выталкивания представления из стека»:

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}
4 голосов
/ 10 ноября 2013

Есть более подходящий способ, чем запросить viewControllers. Вы можете сделать свой контроллер делегатом навигационной панели с кнопкой возврата. Вот пример. В реализации контроллера, где вы хотите обрабатывать нажатие кнопки «назад», скажите ему, что он будет реализовывать протокол UINavigationBarDelegate:

@interface MyViewController () <UINavigationBarDelegate>

Затем где-нибудь в вашем коде инициализации (возможно, в viewDidLoad) сделайте ваш контроллер делегатом его панели навигации:

self.navigationController.navigationBar.delegate = self;

Наконец, реализуйте метод shouldPopItem. Этот метод вызывается правильно при нажатии кнопки «Назад». Если у вас в стеке несколько контроллеров или элементов навигации, вы, вероятно, захотите проверить, какой из этих элементов навигации выталкивается (параметр элемента), так что вы можете выполнять свои собственные действия только тогда, когда ожидаете. Вот пример:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}
3 голосов
/ 07 марта 2019

Может быть, уже слишком поздно, но я тоже хотел такое же поведение раньше.И решение, с которым я столкнулся, довольно хорошо работает в одном из приложений, которые в настоящее время находятся в App Store.Поскольку я не видел, чтобы кто-то использовал подобный метод, я хотел бы поделиться им здесь.Недостатком этого решения является то, что оно требует подкласса UINavigationController.Хотя использование метода Swizzling могло бы помочь избежать этого, я не пошел так далеко.

Итак, кнопка возврата по умолчанию фактически управляется UINavigationBar.Когда пользователь нажимает кнопку «Назад», UINavigationBar спрашивает своего делегата, должен ли он выдвинуть верхнюю часть UINavigationItem, вызывая navigationBar(_:shouldPop:).UINavigationController фактически реализует это, но публично не заявляет, что принимает UINavigationBarDelegate (почему !?).Чтобы перехватить это событие, создайте подкласс UINavigationController, объявите его соответствие UINavigationBarDelegate и внедрите navigationBar(_:shouldPop:).Верните true, если верхний элемент должен быть вытолкнут.Верните false, если оно должно остаться.

Есть две проблемы.Во-первых, в какой-то момент вы должны вызвать UINavigationController версию navigationBar(_:shouldPop:).Но UINavigationBarController публично не объявляет о его соответствии UINavigationBarDelegate, попытка вызвать его приведет к ошибке времени компиляции.Решение, которое я выбрал, состоит в том, чтобы использовать среду выполнения Objective C, чтобы получить реализацию и вызвать ее.Пожалуйста, дайте мне знать, если у кого-то есть лучшее решение.

Другая проблема заключается в том, что navigationBar(_:shouldPop:) вызывается первым, затем следует popViewController(animated:), если пользователь нажимает кнопку возврата.Порядок меняется на обратный, если контроллер представления прерывается с помощью вызова popViewController(animated:).В этом случае я использую логическое значение, чтобы определить, вызывается ли popViewController(animated:) до navigationBar(_:shouldPop:), что означает, что пользователь нажал на кнопку возврата.

Кроме того, я делаю расширение UIViewController, чтобынавигационный контроллер спрашивает контроллер представления, должен ли он быть выдвинут, если пользователь нажимает кнопку возврата.Контроллеры представления могут возвращать false и выполнять любые необходимые действия, а затем вызывать popViewController(animated:).

class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
    // If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
    // If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
    private var didCallPopViewController = false

    override func popViewController(animated: Bool) -> UIViewController? {
        didCallPopViewController = true
        return super.popViewController(animated: animated)
    }

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
        if didCallPopViewController {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        }

        // The following code is called only when the user taps on the back button.

        guard let vc = topViewController, item == vc.navigationItem else {
            return false
        }

        if vc.shouldBePopped(self) {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        } else {
            return false
        }
    }

    func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
        didCallPopViewController = false
    }

    /// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
    /// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
    /// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
    private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
        let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
        typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
        let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
        return shouldPop(self, sel, navigationBar, item)
    }
}

extension UIViewController {
    @objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        return true
    }
}

И в ваших контроллерах представления реализовывать shouldBePopped(_:).Если вы не реализуете этот метод, поведение по умолчанию будет включать контроллер представления, как только пользователь нажимает кнопку «Назад», как обычно.

class MyViewController: UIViewController {
    override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        let alert = UIAlertController(title: "Do you want to go back?",
                                      message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
            navigationController.popViewController(animated: true)
        }))
        present(alert, animated: true, completion: nil)
        return false
    }
}

Вы можете посмотреть мою демонстрационную версию здесь .

enter image description here

3 голосов
/ 30 июня 2014

Вот еще один способ, которым я реализовал (не тестировал его с перемоткой, но он, вероятно, не будет дифференцироваться, как другие заявляли в отношении других решений на этой странице), чтобы родительский контроллер представления выполнял действия перед дочерним VC, который он выдвинул, выскочил из стека представлений (я использовал это на пару уровней ниже от оригинального UINavigationController). Это также может быть использовано для выполнения действий до того, как childVC будет выдвинут. Это дает дополнительное преимущество работы с кнопкой «Назад» в системе iOS, вместо создания пользовательского UIBarButtonItem или UIButton.

  1. Ваш родительский VC должен принять протокол UINavigationControllerDelegate и зарегистрироваться для сообщений делегата:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
    
  2. Реализуйте этот UINavigationControllerDelegate метод экземпляра в MyParentViewController:

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        // Test if operation is a pop; can also test for a push (i.e., do something before the ChildVC is pushed
        if (operation == UINavigationControllerOperationPop) {
            // Make sure it's the child class you're looking for
            if ([fromVC isKindOfClass:[ChildViewController class]]) {
                // Can handle logic here or send to another method; can also access all properties of child VC at this time
                return [self didPressBackButtonOnChildViewControllerVC:fromVC];
            }
        }
        // If you don't want to specify a nav controller transition
        return nil;
    }
    
  3. Если вы указали конкретную функцию обратного вызова в приведенном выше UINavigationControllerDelegate методе экземпляра

    -(id <UIViewControllerAnimatedTransitioning>)didPressBackButtonOnAddSearchRegionsVC:(UIViewController *)fromVC {
        ChildViewController *childVC = ChildViewController.new;
        childVC = (ChildViewController *)fromVC;
    
        // childVC.propertiesIWantToAccess go here
    
        // If you don't want to specify a nav controller transition
        return nil;
    

    } ​​

3 голосов
/ 13 июля 2013

Если вы не можете использовать «viewWillDisappear» или подобный метод, попробуйте создать подкласс UINavigationController. Это заголовок класса:

#import <Foundation/Foundation.h>
@class MyViewController;

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Класс реализации:

#import "CCNavigationController.h"
#import "MyViewController.h"

@implementation CCNavigationController {

}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    @"This is the moment for you to do whatever you want"
    [self.viewController doCustomMethod];
    return [super popViewControllerAnimated:animated];
}

@end

С другой стороны, вам нужно связать этот viewController с вашим пользовательским NavigationController, поэтому в вашем методе viewDidLoad для вашего обычного viewController сделайте следующее:

@implementation MyViewController {
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        ((CCNavigationController*)self.navigationController).viewController = self;
    }
}
...