【问题标题】:Detecting when the 'back' button is pressed on a navbar检测导航栏上的“返回”按钮何时被按下
【发布时间】:2012-01-03 22:21:44
【问题描述】:

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

我可以实现一些方法来捕获事件并触发一些操作以在屏幕消失之前暂停和保存数据吗?

【问题讨论】:

标签: iphone objective-c ios xcode


【解决方案1】:

更新: 根据一些 cmets 的说法,原始答案中的解决方案似乎在 iOS 8+ 的某些场景下不起作用。如果没有进一步的细节,我无法验证是否确实如此。

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

查看"Implementing a 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
  }
}

this question 中所述,您可以将这两个属性结合起来:

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

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

其他解决方案依赖于UINavigationBar 的存在。相反,我更喜欢我的方法,因为它将需要执行的任务与触发事件的操作(即按下后退按钮)分离。

【讨论】:

  • 我喜欢你的回答。但是你为什么使用'self.isBeingDismissed'?就我而言,“self.isBeingDismissed”中的语句没有得到执行。
  • 当我使用popToRootViewControllerAnimated 以编程方式弹出导航堆栈时,self.isMovingFromParentViewController 具有 TRUE 值 - 无需触摸后退按钮。我应该否决你的答案吗? (主题说“在导航栏上按下'返回'按钮”)
  • 很好的回答,非常感谢。在 Swift 中,我使用了:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
  • 您应该只在-viewDidDisappear: 内执行此操作,因为您可能会在没有-viewDidDisappear: 的情况下获得-viewWillDisappear:(例如当您开始滑动以关闭导航控制器项目然后取消它时滑动。
  • 看起来不再是一个可靠的解决方案。在我第一次使用它时工作(它是 iOS 10)。但是现在我偶然发现它平静地停止了工作(iOS 11)。不得不切换到“willMove(toParentViewController)”解决方案。
【解决方案2】:

虽然viewWillAppear()viewDidDisappear() 在点击返回按钮时被调用,但它们在其他时间也会被调用。有关更多信息,请参阅答案末尾。

使用 UIViewController.parent

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

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

// 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.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:事物的一些选项

当您从listVCsettingsVC 再回到listVC 时,让我们关注detailVC 上的呼叫

列表>详细信息(推送详细信息VC)Detail.viewDidAppear详细信息>设置(推送设置VC)Detail.viewDidDisappear

当我们返回时...
设置 > 详细信息(弹出 settingsVC)Detail.viewDidAppear 详细信息 > 列表 (pop detailVC) Detail.viewDidDisappear

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

【讨论】:

  • 请注意,用户didMoveToParantViewController: 在视图不再可见时执行工作。使用 interactiveGesutre 对 iOS7 很有帮助
  • didMoveToParentViewController* 有错别字
  • 别忘了调用[super willMoveToParentViewController:parent]!
  • 当您弹出到父视图控制器时,父参数为 nil,当显示此方法出现的视图时为非 nil。您可以使用该事实仅在按下 Back 按钮时执行操作,而不是在到达视图时执行操作。毕竟,那是最初的问题。 :)
  • 当以编程方式使用_ = self.navigationController?.popViewController(animated: true) 时也会调用它,因此它不仅仅是在按下后退按钮时调用。我正在寻找一个在按下 Back 时有效的呼叫。
【解决方案3】:

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

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 的使用似乎会失败。它没有失败。你把它弄坏了。

注意:真正的问题通常是 second 视图控制器检测到 first 视图控制器已弹出。另请参阅此处更一般的讨论:Unified UIViewController "became frontmost" detection?

EDIT 一条评论表明这应该是viewDidDisappear 而不是viewWillDisappear

【讨论】:

  • 此代码在点击返回按钮时执行,但如果以编程方式弹出 VC 也会执行。
  • @biomiker 当然,但其他方法也是如此。啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪响问题是当您没有以编程方式弹出时如何检测弹出。如果您以编程方式弹出,则您已经知道您正在弹出,因此没有什么可检测的。
  • 是的,其他几种方法都是如此,其中许多具有相似的 cmets。我只是澄清一下,因为这是最近的一个带有具体反驳的答案,当我读到它时,我已经抱上了希望。不过,为了记录,问题是如何检测后退按钮的按下。一个合理的论点是,在没有按下后退按钮的情况下也将执行的代码,而不指示是否按下了后退按钮,并不能完全解决真正的问题,即使问题可能更多明确指出这一点。
  • 不幸的是,这会为交互式滑动弹出手势返回true - 从视图控制器的左边缘 - 即使滑动没有完全弹出它。因此,不要在willDisappear 中检查它,而是在didDisappear 中进行检查。
  • @badhanganesh 谢谢,编辑答案以包含该信息。
【解决方案4】:

第一种方法

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

【讨论】:

  • 第二种方法是唯一对我有用的方法。第一种方法在我的视图呈现时也被调用,这对我的用例来说是不可接受的。
【解决方案5】:

我已经玩(或解决)这个问题两天了。 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;
}

【讨论】:

  • 这个答案对我来说几乎是完整的,除了我发现经常会弹出 2 个视图控制器。返回 YES 会导致调用方法调用 pop,因此调用 pop 也意味着将弹出 2 个视图控制器。请参阅另一个问题的答案以获取更多信息(一个非常好的答案,值得更多支持):stackoverflow.com/a/26084150/978083
  • 好点,我的描述并不清楚这一事实。 “如果一切正常,记得手动弹出视图控制器”仅适用于返回“NO”的情况,否则流程为正常弹出。
  • 对于“else”分支,如果不想自己处理pop,最好调用super implementation,让它返回它认为正确的东西,这主要是YES,但它也很小心然后 pop 本身并正确地为 chevron 设置动画。
【解决方案6】:

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

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

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

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

【讨论】:

  • 最好用 willMove 代替
  • 不确定willMove,因为它可能与willDisappear 有相同的问题:用户可以开始通过滑动关闭视图控制器,willDisappear 将被调用,但用户仍然可以取消刷卡!
【解决方案7】:

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

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

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

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

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
}

【讨论】:

  • 不知道为什么有人投了反对票。这似乎是迄今为止最好的答案。
  • @Avinash navigationShouldPopOnBackButton 来自哪里?它不是公共 API 的一部分。
  • @elitalon 对不起,这是一半的答案。我曾认为剩余的上下文存在问题。无论如何,现在已经更新了答案
  • 我同意。这是一个被低估的解决方案,它使用带有“
【解决方案8】:

为了记录,我认为这更像是他正在寻找的东西......

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

【讨论】:

  • 感谢 Paul,这个解决方案非常简单。不幸的是,图标不同。这是“倒带”图标,而不是后退图标。也许有一种方法可以使用后退图标...
【解决方案9】:

最好的方法是使用 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;
【解决方案10】:

您应该查看UINavigationBarDelegate Protocol。 在这种情况下,您可能需要使用 navigationBar:shouldPopItem: 方法。

【讨论】:

    【解决方案11】:

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

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

    【讨论】:

      【解决方案12】:

      正如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];
      

      感谢您的帮助!

      【讨论】:

        【解决方案13】:

        对于带有 UINavigationController 的 Swift:

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

        【讨论】:

          【解决方案14】:

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

          就是这样!

          【讨论】:

            【解决方案15】:

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

            【讨论】:

              【解决方案16】:

              我使用了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
              }
              

              我从 iOS 13 看到一些其他问题报告此方法未被调用(但其他委托方法被按预期调用)?

              iOS 13 and UINavigationBarDelegate::shouldPop()

              【讨论】:

                【解决方案17】:

                self.navigationController.isMovingFromParentViewController 在我使用的 iOS8 和 9 上不再工作:

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

                【讨论】:

                  【解决方案18】:

                  (SWIFT)

                  最终找到解决方案..我们正在寻找的方法是“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
                          }
                      }
                  }
                  

                  【讨论】:

                  • 这种方法的问题在于它将MyViewController 耦合到PushedController
                  猜你喜欢
                  • 1970-01-01
                  • 2010-10-11
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-10-05
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-11-29
                  • 1970-01-01
                  相关资源
                  最近更新 更多