检测导航栏上按下“返回”按钮的时间

我需要执行一些操作时,返回按钮(返回到上一个屏幕,返回到父视图)按下的导航栏。

是否有一些方法,我可以实现捕捉事件和触发一些行动暂停和保存数据之前屏幕消失?

153366 次浏览

你应该看看 代理协议。 在这种情况下,您可能希望使用导航栏: should dPopItem: 方法。

正如 Coli88所说,您应该检查 UINavigationBarGenerate 协议。

在更一般的方式中,当当前可见视图控制器保留的视图即将消失时,还可以使用 - (void)viewWillDisapear:(BOOL)animated执行自定义工作。不幸的是,这将涵盖推和流行的情况下打扰。

更新: 根据一些评论,原始答案中的解决方案在 iOS8 + 的某些情况下似乎不起作用。没有进一步的细节,我无法证实这一点。

然而,对于你们这些处于那种情况的人来说,还有另一种选择。通过覆盖 willMove(toParentViewController:)可以检测视图控制器何时弹出。其基本思想是当 parentnil时弹出一个视图控制器。

详情请参阅 “实现容器视图控制器”


从 iOS5开始,我发现处理这种情况最简单的方法就是使用新的方法 - (BOOL)isMovingFromParentViewController:

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];


if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}

当您在导航堆栈中推送和弹出控制器时,- (BOOL)isMovingFromParentViewController是有意义的。

然而,如果你呈现的是模态视图控制器,你应该使用 - (BOOL)isBeingDismissed代替:

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];


if (self.isBeingDismissed) {
// Do your stuff here
}
}

正如在 这个问题中指出的,您可以将两个属性组合在一起:

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];


if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}

其他解决方案依赖于 UINavigationBar的存在。相反,我更喜欢我的方法,因为它将需要执行的任务与触发事件的动作分离开来,例如,按一个后退按钮。

当点击后退按钮时,viewWillAppear()viewDidDisappear()被调用,它们在其他时候也被调用。有关更多信息,请参见答案的末尾。

使用 UIViewController.rent

willMoveToParentViewController(_:)didMoveToParentViewController()的帮助下,当 VC 从它的父级(NavigationController)移除时,检测后退按钮更好

如果父级为空,则视图控制器将从导航堆栈中弹出并解除。如果父级不为空,则将其添加到堆栈中并显示。

// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}




// Swift
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}

willMove替换为 didMove,并检查 self. father 来做工作 之后视图控制器被解散。

停止撤案

请注意,如果您需要执行某种异步保存,检查父级不允许您“暂停”转换。为此,您可以实现以下内容。唯一的缺点是你失去了花哨的 iOS 风格/动画后退按钮。在这里还要注意交互式滑动手势。使用以下方法处理此案件。

var backButton : UIBarButtonItem!


override func viewDidLoad() {
super.viewDidLoad()
     

// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
    

// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}


// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}

更多的画面即将出现 如果你没有得到‘ viewWillAppear’‘ viewDidDisaper’的问题,让我们来看一个例子,假设你有三个视图控制器:
  1. ListVC: 事物的表格视图
  2. 详情: 一件事的细节
  3. 设置: 一个东西的一些选择

当你从 listVCsettingsVC再回到 listVC时,让我们跟随 detailVC上的呼叫

列表 > 详细信息 (按详细信息 VC) Detail.viewDidAppear <-显示
详细信息 > 设置 (推送设置 sVC) Detail.viewDidDisappear <-消失

当我们回到过去。
设置 > 详细信息 (弹出设置 sVC) Detail.viewDidAppear <-显示
详细信息 > List (pop detailVC) Detail.viewDidDisappear <-消失

请注意,viewDidDisappear被多次调用,不仅在向后调用,而且在向前调用。对于可能需要的快速操作,但对于更复杂的操作(如要保存的网络调用) ,则可能不需要。

郑重声明,我觉得这才是他想要的。

    UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];


self.navigationItem.leftBarButtonItem = l_backButton;




- (void) backToRootView:(id)sender {


// Perform some custom code


[self.navigationController popToRootViewControllerAnimated:YES];
}

第一种方法

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

第二种方法

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

(SWIFT)

最后找到的解决方案. . 方法我们正在寻找的是“ will ShowViewController”,这是 UINavigationController 的委托方法

//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {


override func viewDidLoad() {
//set delegate to current class (self)
navigationController?.delegate = self
}


func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
//MyViewController shoud be the name of your parent Class
if var myViewController = viewController as? MyViewController {
//YOUR STUFF
}
}
}

正如 purrrminator所说,由 elitalon得到的答案并不完全正确,因为即使以编程方式弹出控制器,也会执行 your stuff

到目前为止我找到的解决方案不是很好,但是对我来说很有效。除了 elitalon所说的,我还检查了我是否以编程方式弹出:

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];


if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}

您必须将该属性添加到您的控制器中,并在以编程方式弹出之前将其设置为 YES:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

谢谢你的帮助!

我已经花了两天时间来解决这个问题。IMO 最好的办法就是创建一个扩展类和一个协议,像这样:

@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
@end


@interface UINavigationController(BackButtonHandler)
@end


@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = @selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
@end

这是因为每次弹出视图控制器时,UINavigationController都会接收到对 navigationBar:shouldPopItem:的调用。在那里,我们检测是否返回被按下或没有(任何其他按钮)。 您唯一需要做的事情就是在按回车键的视图控制器中实现协议。

如果一切正常,请记住手动弹出 backButtonPressedSel中的视图控制器。

如果您已经子类化了 UINavigationViewController并实现了 navigationBar:shouldPopItem:,不要担心,这不会干扰它。

您可能还有兴趣禁用背部手势。

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

你可以使用回调按钮,像这样:

- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}


- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}

对于快速版本,您可以在全局范围内执行类似的操作

extension UIViewController {
@objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}


extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}

下面一个是你放在视图控制器中想要控制后退按钮的操作:

override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.


return true
}

最好的方法是使用 UINavigationController 委托方法

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated

使用它,您可以知道哪个控制器正在显示 UINavigationController。

if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}

IsMovingFromParentViewController 在 iOS8和9上不再工作了,我使用:

-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}

对于带有 UINavigationController 的 Swift:

override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}

这对我在 iOS 9.3. x 和 Swift 中的应用很有效:

override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)


if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}

与这里的其他解决方案不同,这似乎不会意外触发。

我已经通过向左侧的导航栏添加一个 UIControl 来解决这个问题。

UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];

当视图消失时,你需要记住移除它:

- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}

仅此而已!

7ynk3r 的回答与我最后使用的非常接近,但它需要一些调整:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {


UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;


if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:@selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}

那些声称这不起作用的人错了:

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}

这很好,那么是什么导致了这个广为流传的流言呢?

问题似乎是由于 与众不同方法的实现不正确,即 willMove(toParent:)的实现忘记调用 super

如果没有调用 super就实现了 willMove(toParent:),那么 self.isMovingFromParent将是 false,而使用 viewWillDisappear将显得失败。它没有失败,是你弄坏的。

注意: 真正的问题通常是 第二视图控制器检测到 第一视图控制器被弹出。请参阅这里更一般性的讨论: 统一 UIViewController“成为最前端”检测?

有评论建议这应该是 viewDidDisappear而不是 viewWillDisappear

我使用的是 Pedro Magalhães解决方案,但是在这样的扩展中使用时没有调用 navigationBar:shouldPop:

extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}

但同样的事情在 UINavigationController子类工作得很好。

class NavigationController: UINavigationController, UINavigationBarDelegate {


func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}

我看到一些其他的问题,报告这个方法没有被调用(但其他委托方法正如预期的那样被调用) ,从 iOS13?

IOS13和 UINavigationBarGenerate: : should dPop ()