ios iOS导航控制器中的后退按钮回调

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5217992/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 19:09:09  来源:igfitidea点击:

back button callback in navigationController in iOS

ioscallbackuinavigationcontrolleruinavigationitemback

提问by Namratha

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?

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

回答by ymutlu

William Jockusch's answersolve 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];
}

回答by Blank

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+

回答by roocell

it's probably better to override the backbutton so you can handle the event beforethe 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的委托方法按下后退按钮时做的动作。

回答by Avijit Nagare

I end up with this solutions. As we tap back button viewDidDisappear method called. we can check by calling isMovingFromParentViewController selector which return true. we can pass data back (Using Delegate).hope this help someone.

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

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

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

    }
}

回答by Saad

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 是为了从堆栈中弹出视图控制器

回答by yusuke024

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. 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. Though using Method Swizzlingmight help avoiding that, I didn't go that far.

也许为时已晚,但我之前也想要相同的行为。我采用的解决方案在目前 App Store 上的其中一款应用程序中运行良好。由于我还没有看到有人使用类似的方法,所以我想在这里分享一下。此解决方案的缺点是它需要子类化UINavigationController。虽然使用Method Swizzling可能有助于避免这种情况,但我并没有走那么远。

So, the default back button is actually managed by UINavigationBar. When a user taps on the back button, UINavigationBarask its delegate if it should pop the top UINavigationItemby calling navigationBar(_:shouldPop:). UINavigationControlleractually implement this, but it doesn't publicly declare that it adopts UINavigationBarDelegate(why!?). To intercept this event, create a subclass of UINavigationController, declare its conformance to UINavigationBarDelegateand implement navigationBar(_:shouldPop:). Return trueif the top item should be popped. Return falseif it should stay.

所以,默认的后退按钮实际上是由UINavigationBar. 当返回按钮,用户水龙头,UINavigationBar要求其委托是否应该弹出的顶部UINavigationItem通过调用navigationBar(_:shouldPop:)UINavigationController实际上实现了这一点,但它没有公开声明它采用UINavigationBarDelegate(为什么!?)。要拦截此事件,请创建 的子类UINavigationController,声明其符合UINavigationBarDelegate并实现navigationBar(_:shouldPop:)true如果应弹出顶部项目,则返回。false如果它应该留下就回来。

There are two problems. The first is that you must call the UINavigationControllerversion of navigationBar(_:shouldPop:)at some point. But UINavigationBarControllerdoesn't publicly declare it conformance to UINavigationBarDelegate, trying to call it will result in a compile time error. The solution I went with is to use Objective-C runtime to get the implementation directly and call it. Please let me know if anyone has a better solution.

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

The other problem is that navigationBar(_:shouldPop:)is called first follows by popViewController(animated:)if the user taps on the back button. The order reverses if the view controller is popped by calling 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:)。如果通过调用弹出视图控制器,则顺序相反popViewController(animated:)。在这种情况下,我使用布尔值来检测是否popViewController(animated:)在之前被调用,navigationBar(_:shouldPop:)这意味着用户已经点击了后退按钮。

Also, I make an extension of UIViewControllerto let the navigation controller ask the view controller if it should be popped if the user taps on the back button. View controllers can return falseand do any necessary actions and call popViewController(animated:)later.

另外,我做了一个扩展,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
    }
}

And in you view controllers, implement 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.

在你的视图控制器中,实现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
    }
}

You can look at my demo here.

你可以在这里查看我的演示。

enter image description here

enter image description here

回答by Anum Malik

For "BEFORE popping the view off the stack" :

对于“在从堆栈中弹出视图之前”:

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

回答by Carlos Guzman

There's a more appropriate way than asking the viewControllers. You can make your controller a delegate of the navigationBar that has the back button. 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:

有比询问 viewControllers 更合适的方法。您可以使您的控制器成为具有后退按钮的导航栏的代表。这是一个例子。在要处理后退按钮的按下,告诉它,它会实现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. 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. Here's an example:

最后,实现 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;
}

回答by George Harley

If you can't use "viewWillDisappear" or similar method, try to subclass UINavigationController. This is the header class:

如果您不能使用“viewWillDisappear”或类似方法,请尝试继承 UINavigationController。这是头类:

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

回答by Evan R

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). This could also be used to perform actions before the childVC gets pushed, too. This has the added advantage of working with the iOS system back button, instead of having to create a custom UIBarButtonItem or UIButton.

这是我实现的另一种方式(没有用 unwind segue 测试它,但它可能不会区分,正如其他人在本页上的其他解决方案中所述)让父视图控制器在它推送的子 VC 之前执行操作从视图堆栈中弹出(我在原始 UINavigationController 的几个级别下使用了它)。这也可以用于在 childVC 被推送之前执行操作。这具有使用 iOS 系统后退按钮的额外优势,而不必创建自定义 UIBarButtonItem 或 UIButton。

  1. Have your parent VC adopt the UINavigationControllerDelegateprotocol and register for delegate messages:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
    
  2. Implement this UINavigationControllerDelegateinstance method in 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. If you specify a specific callback function in the above UINavigationControllerDelegateinstance method

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

    }

  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;
    

    }