iOS中NavigationController中的后退按钮回调


102

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



签出此[solution] [1],它也保留后退按钮样式。[1]:stackoverflow.com/a/29943156/3839641
Sarasranglt,2015年

Answers:


162

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

32
该代码不仅在用户单击“后退”按钮时执行,而且在每种情况下都会弹出视图(例如,在右侧具有“完成”或“保存”按钮时)。
含义-

7
或前进到新视图时。
GuybrushThreepwood 2013年

当用户从左侧边缘平移(interactivePopGestureRecognizer)时,也会调用此方法。就我而言,我专门在寻找用户不向左平移而向后按下的情况。
凯尔·克莱格

2
并不意味着后退按钮是原因。例如,可能是闲逛。
smileBot 2014年

1
我有一个疑问,为什么我们不应该在viewDidDisappear中这样做呢?
JohnVanDijk

85

我认为最好的解决方案。

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

但仅适用于iOS5 +


3
这项技术无法区分后退按钮轻按和展开按钮。
smileBot 2014年

willMoveToParentViewController和viewWillDisappear方法不能解释控制器必须销毁,didMoveToParentViewController是正确的
Hank

27

最好覆盖后退按钮,以便您可以弹出视图以处理诸如用户确认之类的事件之前处理该事件。

在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委托方法来代替在创建新的后退按钮时执行后退按钮的操作。


UINavigationControllerDelegate没有时后退按钮被点击时调用的方法。
含义-2013年

此技术允许验证视图控制器的数据以及从导航控制器的后退按钮有条件返回。
gjpc

此解决方案打破了iOS 7+的边缘轻扫功能
Liron Yahdav 2015年

9

这是检测到此错误的正确方法。

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

    }
}

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


9

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

    }
}

别忘了[super viewDidDisappear:animated]
-SamB

9

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

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

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

另一个问题是,如果用户点击“后退”按钮,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(_:)。如果您未实现此方法,则默认行为将是用户像往常一样点击后退按钮时立即弹出视图控制器。

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

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

在此处输入图片说明


这是一个很棒的解决方案,应该发布到博客中!似乎对我现在正在搜索的内容过于矫kill,但是在其他情况下,这确实值得尝试。
ASSeeger

6

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

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

5

有一种比询问viewControllers更合适的方法。您可以使控制器成为具有back按钮的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
对我没用..可怜,因为它很瘦。“ ***由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'无法在控制器管理的UINavigationBar上手动设置委托。'”
DynamicDan

不幸的是,这将不适用于UINavigationController,相反,您需要一个带有UINavigationBar的标准UIViewController。这确实意味着您无法利用NavigationController为您提供的多种自动视图控制器推送和弹出功能。抱歉!
卡洛斯·古兹曼

我只是使用UINavigationBar而不是NavigationBarController,然后它工作正常。我知道问题是关于NavigationBarController的,但是这种解决方案是精益的。
appsunit2014年

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

3

这是我实现的另一种方式(没有用轻松的测试来测试它,但是它可能不会有所区别,正如其他人对此页面上其他解决方案所说的那样),以使父视图控制器在它推送的子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;

    }


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

0

如果您正在使用情节提要,并且您来自推播功能,则也可以覆盖shouldPerformSegueWithIdentifier:sender:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.