繁体   English   中英

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

[英]back button callback in navigationController in iOS

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

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

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

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

但它只适用于iOS5 +

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

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

我最终得到了这个解决方案。 当我们点击后退按钮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];

    }
}

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

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

    }
}

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

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

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

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

如果您不能使用“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;
    }
}

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

  1. 让您的父VC采用UINavigationControllerDelegate协议并注册委托消息:

     MyParentViewController : UIViewController <UINavigationControllerDelegate> -(void)viewDidLoad { self.navigationcontroller.delegate = self; } 
  2. 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. 如果在上面的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; 

    }

也许这有点太晚了,但我之前也想要同样的行为。 我使用的解决方案在App Store上的一个应用程序中运行良好。 由于我没有看到任何人使用类似的方法,我想在这里分享。 这个解决方案的缺点是它需要子类化UINavigationController 虽然使用Method Swizzling可能有助于避免这种情况,但我没有那么做。

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

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

另一个问题是如果用户点击后退按钮,则首先调用navigationBar(_:shouldPop:) ,然后调用popViewController(animated:) 如果通过调用popViewController(animated:)弹出视图控制器,则顺序会反转。 在这种情况下,我使用布尔值来检测是否在navigationBar(_:shouldPop:)之前调用了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
    }
}

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

在此输入图像描述

这是它在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