首页 文章

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

提问于
浏览
96

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

12 回答

  • 159

    William Jockusch的answer用简单的技巧解决了这个问题 .

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

    在我看来,最好的解决方案 .

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

    但它只适用于iOS5

  • 26

    覆盖后退按钮可能更好,这样您就可以在弹出视图之前处理事件,例如用户确认 .

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

  • 3

    我最终得到了这个解决方案 . 当我们点击后退按钮viewDidDisappear方法调用时 . 我们可以通过调用返回true的isMovingFromParentViewController选择器来检查 . 我们可以传回数据(使用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];
    
        }
    }
    
  • 0

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

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

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

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

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

  • 1

    有一种比询问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;
    }
    
  • 4

    如果您不能使用“viewWillDisappear”或类似的方法,请尝试子类UINavigationController . 这是 Headers 类:

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

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

    • 让您的父VC采用 UINavigationControllerDelegate 协议并注册委托消息:
    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
    
    • 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 (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;
    }
    
    • 如果在上面的 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;
    

    }

  • 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)
    }
    
  • 8

    也许它看到有人采用类似的方法,我想在这里分享一下 . 该解决方案的缺点是它需要子类化 UINavigationController . 虽然使用Method Swizzling可能有助于避免这种情况,但我没有那么做 .

    因此,默认后退按钮实际上由 UINavigationBar 管理 . 当用户点击后退按钮时, UINavigationBar 通过调用 navigationBar(_:shouldPop:) 询问其代表是否应该弹出顶部 UINavigationItem . UINavigationController 实际上实现了这一点,但它没有公开声明它采用 UINavigationBarDelegate (为什么!?) . 要拦截此事件,请创建 UINavigationController 的子类,声明其与 UINavigationBarDelegate 的一致性并实现 navigationBar(_:shouldPop:) . 如果应该弹出顶部项目,则返回 true . 返回 false 如果它应该留下来 .

    有两个问题 . 该首先,你必须在某个时候调用_26696701的 UINavigationController 版本 . 但 UINavigationBarController 没有公开声明它与 UINavigationBarDelegate 的一致性,试图调用它将导致编译时错误 . 我使用的解决方案是使用Objective-C运行时直接获取实现并调用它 . 如果有人有更好的解决方案,请告诉我 .

    另一个问题是,如果用户点击后退按钮,则首先调用 navigationBar(_:shouldPop:) ,然后调用 popViewController(animated:) . 如果通过调用 popViewController(animated:) 弹出视图控制器,则顺序会反转 . 在这种情况下,我使用布尔值来检测 navigationBar(_:shouldPop:) 之前是否调用了 popViewController(animated:) ,这意味着用户已经点击了后退按钮 .

    此外,我进行 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
        }
    }
    

    你可以看一下我的演示here .

    enter image description here

  • 3

    如果你来自push segue,你也可以覆盖 shouldPerformSegueWithIdentifier:sender: .

相关问题