iOS中的navigationController中的后退按钮回调

时间:2011-03-07 09:36:24

标签: ios callback uinavigationcontroller uinavigationitem back

我已将视图推到导航控制器上,当我按下后退按钮时,它会自动转到上一个视图。我想在按下后退按钮之前做一些事情,然后再将视图弹出堆栈。哪个是后退按钮回调函数?

12 个答案:

答案 0 :(得分:160)

William Jockusch的{​​{3}}通过简单的技巧解决了这个问题。

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

答案 1 :(得分:82)

在我看来是最好的解决方案。

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

但它只适用于iOS5 +

答案 2 :(得分:26)

最好覆盖后退按钮,以便在之前处理事件,以便为用户确认等内容弹出视图。

在viewDidLoad中创建一个UIBarButtonItem并将self.navigationItem.leftBarButtonItem设置为传递给它的

- (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委托方法,以便在按下后退按钮时执行操作。

答案 3 :(得分:8)

我最终得到了这个解决方案。当我们点击后退按钮viewDidDisappear方法调用时。我们可以通过调用返回true的isMovingFromParentViewController选择器来检查。我们可以传回数据(使用委托).hope这有助于某人。

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

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

    }
}

答案 4 :(得分:7)

这是检测此问题的正确方法。

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

    }
}

在推送视图时也会调用此方法。所以检查parent == nil是用于从堆栈中弹出视图控制器

答案 5 :(得分:6)

对于“BEFORE从堆栈中弹出视图”:

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}

答案 6 :(得分:4)

比询问viewControllers更合适。您可以使控制器成为具有后退按钮的navigationBar的委托。这是一个例子。在您要处理按下后退按钮的控制器的实现中,告诉它它将实现UINavigationBarDelegate协议:

@interface MyViewController () <UINavigationBarDelegate>

然后在初始化代码中的某处(可能在viewDidLoad中)使您的控制器成为其导航栏的代理:

self.navigationController.navigationBar.delegate = self;

最后,实现shouldPopItem方法。按下后退按钮时会调用此方法。如果堆栈中有多个控制器或导航项,您可能想要检查哪些导航项被弹出(item参数),这样您只能在预期时执行自定义操作。这是一个例子:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}

答案 7 :(得分:3)

如果您不能使用“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,因此,在常规viewController的viewDidLoad方法中执行此操作:

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

答案 8 :(得分:3)

这是我实施的另一种方式(没有用unwind segue测试它,但它可能不会像其他人在本页的其他解决方案中所说的那样区分)父视图控制器在推送的子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;
    

    }

答案 9 :(得分:2)

也许为时已晚,但我之前也想要相同的行为。我使用的解决方案在App Store当前使用的一种应用程序中效果很好。由于我还没有看到有人采用类似的方法,因此我想在这里分享。该解决方案的缺点是它需要子类UINavigationController。尽管使用Method Swizzling可能有助于避免这种情况,但我没有走那么远。

因此,默认的后退按钮实际上是由UINavigationBar管理的。当用户点击后退按钮时,UINavigationBar会询问其委托人是否应该通过调用UINavigationItem来弹出顶部的navigationBar(_:shouldPop:)UINavigationController实际上实现了此功能,但没有公开声明采用UINavigationBarDelegate(为什么!?)。要拦截此事件,请创建UINavigationController的子类,声明其符合UINavigationBarDelegate并实现navigationBar(_:shouldPop:)。如果应该弹出顶部项目,请返回true。返回false(如果应保留)。

有两个问题。首先是您必须在某个时候调用UINavigationController的{​​{1}}版本。但是navigationBar(_:shouldPop:)并未公开声明其符合UINavigationBarController,试图调用它会导致编译时错误。我使用的解决方案是使用Objective-C运行时直接获取实现并对其进行调用。请让我知道是否有人有更好的解决方案。

另一个问题是,如果用户点击“后退”按钮,则UINavigationBarDelegate首先被调用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(_:)

您可以看一下我的演示here

enter image description here

答案 10 :(得分:1)

这就是它在Swift中的作用:

override func viewWillDisappear(_ animated: Bool) {
    if self.navigationController?.viewControllers.index(of: self) == nil {
        // back button pressed or back gesture performed
    }

    super.viewWillDisappear(animated)
}

答案 11 :(得分:0)

如果您使用的是故事板,并且您来自推送segue,您也可以覆盖shouldPerformSegueWithIdentifier:sender: