检测何时在导航栏上按下“后退”按钮


Answers:


316

更新:根据一些评论,原始答案中的解决方案似乎在iOS 8+中的某些情况下不起作用。如果没有更多详细信息,我无法证实确实如此。

对于那些在那种情况下的人,还有另一种选择。可以通过覆盖检测何时弹出视图控制器willMove(toParentViewController:)。基本思想是在parentis 时弹出视图控制器nil

请查看“实现Container View Controller”以了解更多详细信息。


从iOS 5开始,我发现处理这种情况的最简单方法是使用新方法- (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。相反,更喜欢我的方法,因为它使执行任务所需的任务与触发事件的操作(即按后退按钮)分离。


我喜欢你的回答。但是,为什么要使用“ self.isBeingDismissed”?就我而言,'self.isBeingDismissed'中的语句未实现。
Rutvij Kotecha

3
self.isMovingFromParentViewController当我以编程方式弹出导航堆栈时popToRootViewControllerAnimated- 具有TRUE值-无需触摸后退按钮。我应该否决你的答案吗?(对象说“在导航栏中按下了“后退”按钮”)
kas-kad 2014年

2
很好的答案,非常感谢。在Swift中,我使用了:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
Camillo Visini

1
您只应在此范围内执行此操作,-viewDidDisappear:因为可能会得到一个-viewWillDisappear:不带“ a”的-viewDidDisappear:符号(例如,当您开始滑动以消除导航控制器项然后取消该滑动时。)
Heath Borders

3
看起来不再是一个可靠的解决方案。在我第一次使用此工具(iOS 10)时就工作了。但是现在我意外地发现它从容停止工作(iOS 11)。不得不切换到“ willMove(toParentViewController)”解决方案。
Vitalii

100

当点击后退按钮时调用viewWillAppear()和,而在其他时间也会调用它们。有关更多信息,请参见答案结尾。viewDidDisappear()

使用UIViewController.parent

当借助willMoveToParentViewController(_:)OR 将VC从其父级(NavigationController)中删除时,最好检测到后退按钮didMoveToParentViewController()

如果parent为零,则将视图控制器从导航堆栈中弹出并关闭。如果parent不为nil,则将其添加到堆栈中并显示。

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

换出willMovedidMove和检查self.parent做工作视图控制器被驳回。

停止解雇

请注意,如果需要执行某种异步保存,检查父级不允许您“暂停”过渡。为此,您可以实现以下内容。唯一的缺点是,您失去了精美的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 viewDidDisappear问题,我们来看一个例子。假设您有三个视图控制器:

  1. ListVC:事物的表视图
  2. DetailVC:关于事物的详细信息
  3. SettingsVC:事情的一些选项

detailVC当您从listVC转到settingsVC并返回到时,可以跟踪上的呼叫listVC

列表>详细信息(按下detailVC)Detail.viewDidAppear<-出现
详细信息>设置(按下settingsVC)Detail.viewDidDisappear<-消失

然后回头...
设置>详细信息(弹出设置 VC)<- Detail.viewDidAppear出现
详细信息>列表(弹出详细信息 VC)Detail.viewDidDisappear<-消失

请注意,viewDidDisappear不仅在返回时,而且在前进时,都会多次调用它。对于可能需要的快速操作,但是对于更复杂的操作(例如要保存的网络呼叫),可能不需要。


仅需注意,didMoveToParantViewController:当视图不再可见时,用户可以进行工作。交互式
Gesutre

didMoveToParentViewController *有一个拼写错误
thewormsterror 2014年

不要忘记调用[super willMoveToParentViewController:parent]!
ScottyB 2014年

2
当您弹出到父视图控制器时,parent参数为nil,而在显示此方法的视图时为nonnil。仅当按下“后退”按钮时才可以使用该事实进行操作,而在到达视图时则不能这样做。毕竟,这就是最初的问题。:)
Mike

1
当以编程方式使用时_ = self.navigationController?.popViewController(animated: true),也会调用此方法,因此不只是在按“后退”按钮时调用它。我正在寻找在按Back后才能正常使用的电话。
伊桑·艾伦

16

第一种方法

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

1
第二种方法是唯一对我有用的方法。在呈现我的视图时也调用了第一种方法,这对于我的用例是不可接受的。
marcshilling

10

那些声称这行不通的人被误认为:

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

很好 那么,是什么导致了这个事实却没有呢?

问题似乎是由于不同方法的错误实现导致的,即willMove(toParent:)忘记调用的实现super

如果您在willMove(toParent:)未调用的情况下实施super,那么self.isMovingFromParent将会出现false,并且使用viewWillDisappear将会失败。它没有失败;你把它弄坏了。

注意:真正的问题通常是第二个视图控制器检测到弹出了第一个视图控制器。请在此处查看更一般的讨论:统一的UIViewController“成为最前端”检测?

编辑评论认为,这应该是,viewDidDisappear而不是viewWillDisappear


轻按“后退”按钮时将执行此代码,但如果以编程方式弹出VC,也会执行此代码。
biomiker

@biomiker当然可以,但其他方法也是如此。弹出正在弹出。问题是当您没有以编程方式弹出时如何检测弹出。如果以编程方式弹出,则您已经知道自己正在弹出,因此没有什么可检测的。
马特

是的,其他一些方法也是如此,其中许多方法也有类似的评论。我只是在澄清一下,因为这是最近的答复,但有一个具体的反驳,当我读到它时,我的希望越来越高了。记录下来,问题是如何检测按下后退按钮。可以合理地说,即使在未按下“后退”按钮的情况下执行代码,而未指出是否已按下“后退”按钮的代码,也无法完全解决实际问题,即使该问题可能更多。在这一点上是明确的。
biomiker

1
不幸的是true,即使滑动没有完全弹出,也会从视图控制器的左边缘返回交互式滑动弹出手势。因此,与其在in进行检查willDisappear,不如在didDisappear工作中进行检查。
badhanganesh

1
@badhanganesh谢谢,已编辑答案以包含该信息。
马特

9

我已经为这个问题玩了两天。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:每次弹出视图控制器时都会收到一个调用。在那里,我们检测是否按下了back(任何其他按钮)。您唯一要做的就是在按下后退键的视图控制器中实现协议。

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

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

您可能还对禁用后退手势感兴趣。

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

1
这个答案对我来说几乎是完整的,除了我发现经常会弹出2个视图控制器。返回YES将导致调用方法调用pop,因此调用pop也意味着将弹出2个视图控制器。有关更多问题的更多信息,请参见另一个问题的答案(一个很好的答案,应该得到更多支持):stackoverflow.com/a/26084150/978083
Jason Ridge

好一点,我对这个事实的描述不清楚。“如果一切正常,请记住手动弹出视图控制器”仅适用于返回“否”的情况,否则流程是正常弹出。
7ynk3r

1
对于“ else”分支,如果您不想自己处理pop并让它返回它认为正确的任何东西,则最好调用超级实现,这通常是YES,但它也可以照顾pop本身,然后对V形人字形进行动画处理。
本·辛克莱尔

9

这适用于我在iOS 9.3.x中使用Swift的情况:

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

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

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


最好改用willMove
Eugene Gordin

4

作为记录,我认为这更多的是他在寻找...

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

1
谢谢Paul,这个解决方案非常简单。不幸的是,图标是不同的。这是“倒带”图标,而不是后退图标。也许有一种使用后退图标的方法...
Ferran Maylinch 2015年

2

如前所述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];

谢谢你的帮助!


2

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

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

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

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

这应该被标记为正确答案!可能还想再增加一行以提醒人们-> self.navigationController.delegate = self;
Mike Critchley

2

我通过在左侧的navigationBar中添加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];
    }    
}

就这样!


2

您可以使用后退按钮回调,如下所示:

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

在下面的一个中,您放入了要控制后退按钮动作的viewcontroller:

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

    return true
}

1
不知道为什么有人投了反对票。到目前为止,这似乎是最好的答案。
Avinash

@Avinash navigationShouldPopOnBackButton来自哪里?它不是公共API的一部分。
埃利塔隆

@elitalon对不起,这是一半的答案。我以为其余的上下文仍然存在问题。无论如何,现在已经更新了答案
Avinash

1

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

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


1

对于具有UINavigationController的Swift:

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

1

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


0

self.navigationController.isMovingFromParentViewController在iOS8和9上无法正常使用了:

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

-1

(迅速)

最终找到的解决方案..我们一直在寻找的方法是“ willShowViewController”,这是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
        }
    }
}

1
这种方法的麻烦在于它会耦合MyViewControllerPushedController
clozach
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.