简体   繁体   English

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

[英]back button callback in navigationController in iOS

I have pushed a view onto the navigation controller and when I press the back button it goes to the previous view automatically. 我已将视图推到导航控制器上,当我按下后退按钮时,它会自动转到上一个视图。 I want to do a few things when back button is pressed before popping the view off the stack. 我想在按下后退按钮之前做一些事情,然后再将视图弹出堆栈。 Which is the back button callback function? 哪个是后退按钮回调功能?

William Jockusch's answer solve this problem with easy trick. William Jockusch的回答用简单的技巧解决了这个问题。

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

In my opinion the best solution. 在我看来,最好的解决方案。

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

But it only works with iOS5+ 但它只适用于iOS5 +

it's probably better to override the backbutton so you can handle the event before the view is popped for things such as user confirmation. 覆盖后退按钮可能更好,这样您就可以弹出视图之前处理事件例如用户确认。

in viewDidLoad create a UIBarButtonItem and set self.navigationItem.leftBarButtonItem to it passing in a sel 在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];

}

Then you can do things like raise an UIAlertView to confirm the action, then pop the view controller, etc. 然后你可以做一些事情,如提升UIAlertView来确认动作,然后弹出视图控制器等。

Or instead of creating a new backbutton, you can conform to the UINavigationController delegate methods to do actions when the back button is pressed. 或者,不是创建新的后退按钮,而是可以符合UINavigationController委托方法,以便在按下后退按钮时执行操作。

I end up with this solutions. 我最终得到了这个解决方案。 As we tap back button viewDidDisappear method called. 当我们点击后退按钮viewDidDisappear方法调用时。 we can check by calling isMovingFromParentViewController selector which return true. 我们可以通过调用返回true的isMovingFromParentViewController选择器来检查。 we can pass data back (Using Delegate).hope this help someone. 我们可以传回数据(使用Delegate).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];

    }
}

This is the correct way to detect this. 这是检测此问题的正确方法。

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

    }
}

this method is called when view is pushed as well. 在推送视图时也会调用此方法。 So checking parent==nil is for popping view controller from stack 所以检查parent == nil是用于从堆栈弹出视图控制器

For "BEFORE popping the view off the stack" : 对于“BEFORE弹出堆栈视图”:

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

There's a more appropriate way than asking the viewControllers. 有一种比询问viewControllers更合适的方法。 You can make your controller a delegate of the navigationBar that has the back button. 您可以使控制器成为具有后退按钮的navigationBar的委托。 Here's an example. 这是一个例子。 In the implementation of the controller where you want to handle the press of the back button, tell it that it will implement the UINavigationBarDelegate protocol: 在您要处理按下后退按钮的控制器的实现中,告诉它它将实现UINavigationBarDelegate协议:

@interface MyViewController () <UINavigationBarDelegate>

Then somewhere in your initialization code (probably in viewDidLoad) make your controller the delegate of its navigation bar: 然后在初始化代码中的某个地方(可能在viewDidLoad中)使您的控制器成为其导航栏的委托:

self.navigationController.navigationBar.delegate = self;

Finally, implement the shouldPopItem method. 最后,实现shouldPopItem方法。 This method gets called right when the back button is pressed. 按下后退按钮时会调用此方法。 If you have multiple controllers or navigation Items in the stack, you'll probably want to check which of those navigation items is getting popped (the item parameter), so that you only do your custom stuff when you expect to. 如果堆栈中有多个控制器或导航项,您可能想要检查哪些导航项被弹出(item参数),以便您只在预期时执行自定义操作。 Here's an example: 这是一个例子:

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

If you can't use "viewWillDisappear" or similar method, try to subclass UINavigationController. 如果您不能使用“viewWillDisappear”或类似的方法,请尝试子类UINavigationController。 This is the header class: 这是标题类:

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

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Implementation class: 实施班:

#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

In the other hand, you need to link this viewController to your custom NavigationController, so, in your viewDidLoad method for your regular viewController do this: 另一方面,您需要将此viewController链接到您的自定义NavigationController,因此,在常规viewController的viewDidLoad方法中执行以下操作:

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

Here's another way I implemented (didn't test it with an unwind segue but it probably wouldn't differentiate, as others have stated in regards to other solutions on this page) to have the parent view controller perform actions before the child VC it pushed gets popped off the view stack (I used this a couple levels down from the original UINavigationController). 这是我实现的另一种方式(没有使用unwind segue测试它,但它可能不会像其他人在本页中的其他解决方案中所说的那样区分)让父视图控制器在子VC推送之前执行操作从视图堆栈中弹出(我从原始的UINavigationController中使用了几个级别)。 This could also be used to perform actions before the childVC gets pushed, too. 这也可以用于在推送childVC之前执行操作。 This has the added advantage of working with the iOS system back button, instead of having to create a custom UIBarButtonItem or UIButton. 这具有使用iOS系统后退按钮的附加优势,而不必创建自定义UIBarButtonItem或UIButton。

  1. Have your parent VC adopt the UINavigationControllerDelegate protocol and register for delegate messages: 让您的父VC采用UINavigationControllerDelegate协议并注册委托消息:

     MyParentViewController : UIViewController <UINavigationControllerDelegate> -(void)viewDidLoad { self.navigationcontroller.delegate = self; } 
  2. Implement this UINavigationControllerDelegate instance method in MyParentViewController : MyParentViewController实现此UINavigationControllerDelegate实例方法:

     - (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 (ie, 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. If you specify a specific callback function in the above UINavigationControllerDelegate instance method 如果在上面的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; 

    } }

Maybe it's a little too late, but I also wanted the same behavior before. 也许这有点太晚了,但我之前也想要同样的行为。 And the solution I went with works quite well in one of the apps currently on the App Store. 我使用的解决方案在App Store上的一个应用程序中运行良好。 Since I haven't seen anyone goes with similar method, I would like to share it here. 由于我没有看到任何人使用类似的方法,我想在这里分享。 The downside of this solution is that it requires subclassing UINavigationController . 这个解决方案的缺点是它需要子类化UINavigationController Though using Method Swizzling might help avoiding that, I didn't go that far. 虽然使用Method Swizzling可能有助于避免这种情况,但我没有那么做。

So, the default back button is actually managed by UINavigationBar . 因此,默认后退按钮实际上由UINavigationBar管理。 When a user taps on the back button, UINavigationBar ask its delegate if it should pop the top UINavigationItem by calling navigationBar(_:shouldPop:) . 当用户点击后退按钮时, UINavigationBar询问其委托是否应该通过调用navigationBar(_:shouldPop:)来弹出顶部UINavigationItem UINavigationController actually implement this, but it doesn't publicly declare that it adopts UINavigationBarDelegate (why!?). UINavigationController实际上实现了这一点,但它没有公开声明它采用UINavigationBarDelegate (为什么!?)。 To intercept this event, create a subclass of UINavigationController , declare its conformance to UINavigationBarDelegate and implement navigationBar(_:shouldPop:) . 要拦截此事件,请创建UINavigationController的子类,声明其与UINavigationBarDelegate一致性并实现navigationBar(_:shouldPop:) Return true if the top item should be popped. 如果应弹出顶部项,则返回true Return false if it should stay. 如果它应该停留,则返回false

There are two problems. 有两个问题。 The first is that you must call the UINavigationController version of navigationBar(_:shouldPop:) at some point. 首先,您必须在某个时刻调用navigationBar(_:shouldPop:)UINavigationController版本。 But UINavigationBarController doesn't publicly declare it conformance to UINavigationBarDelegate , trying to call it will result in a compile time error. 但是UINavigationBarController没有公开声明它与UINavigationBarDelegate一致性,试图调用它将导致编译时错误。 The solution I went with is to use Objective-C runtime to get the implementation directly and call it. 我使用的解决方案是使用Objective-C运行时直接获取实现并调用它。 Please let me know if anyone has a better solution. 如果有人有更好的解决方案,请告诉我。

The other problem is that navigationBar(_:shouldPop:) is called first follows by popViewController(animated:) if the user taps on the back button. 另一个问题是如果用户点击后退按钮,则首先调用navigationBar(_:shouldPop:) ,然后调用popViewController(animated:) The order reverses if the view controller is popped by calling popViewController(animated:) . 如果通过调用popViewController(animated:)弹出视图控制器,则顺序会反转。 In this case, I use a boolean to detect if popViewController(animated:) is called before navigationBar(_:shouldPop:) which mean that the user has tapped on the back button. 在这种情况下,我使用布尔值来检测是否在navigationBar(_:shouldPop:)之前调用了popViewController(animated:) navigationBar(_:shouldPop:) ,这意味着用户已经点击了后退按钮。

Also, I make an extension of UIViewController to let the navigation controller ask the view controller if it should be popped if the user taps on the back button. 此外,我对UIViewController进行了扩展,让导航控制器询问视图控制器是否应该弹出,如果用户点击后退按钮。 View controllers can return false and do any necessary actions and call popViewController(animated:) later. 视图控制器可以返回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
    }
}

And in you view controllers, implement shouldBePopped(_:) . 在您查看控制器时,实现shouldBePopped(_:) If you don't implement this method, the default behavior will be to pop the view controller as soon as the user taps on the back button just like normal. 如果您没有实现此方法,则默认行为是在用户点击后退按钮时立即弹出视图控制器。

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

You can look at my demo here . 你可以在这里查看我的演示。

在此输入图像描述

This is what it works for me in Swift: 这是它在Swift中对我有用的东西:

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

    super.viewWillDisappear(animated)
}

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM