简体   繁体   English

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

[英]Detecting when the 'back' button is pressed on a navbar

I need to perform some actions when the back button(return to previous screen, return to parent-view) button is pressed on a Navbar.当在导航栏上按下后退按钮(返回上一屏幕,返回父视图)按钮时,我需要执行一些操作。

Is there some method I can implement to catch the event and fire off some actions to pause and save data before the screen disappears?有什么方法我可以实现来捕捉事件并在屏幕消失之前触发一些操作来暂停和保存数据?

UPDATE: According to some comments, the solution in the original answer does not seem to work under certain scenarios in iOS 8+.更新:根据一些评论,原始答案中的解决方案似乎在 iOS 8+ 的某些场景下不起作用。 I can't verify that that is actually the case without further details.如果没有进一步的细节,我无法验证是否确实如此。

For those of you however in that situation there's an alternative.但是,对于你们这些人来说,在这种情况下,还有另一种选择。 Detecting when a view controller is being popped is possible by overriding willMove(toParentViewController:) .通过覆盖willMove(toParentViewController:)可以检测何时弹出视图控制器。 The basic idea is that a view controller is being popped when parent is nil .基本思想是当parentnil时弹出视图控制器。

Check out "Implementing a Container View Controller" for further details.查看“实现容器视图控制器”了解更多详细信息。


Since iOS 5 I've found that the easiest way of dealing with this situation is using the new method - (BOOL)isMovingFromParentViewController :从 iOS 5 开始,我发现处理这种情况的最简单方法是使用新方法- (BOOL)isMovingFromParentViewController

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

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

- (BOOL)isMovingFromParentViewController makes sense when you are pushing and popping controllers in a navigation stack. - (BOOL)isMovingFromParentViewController在导航堆栈中推送和弹出控制器时有意义。

However, if you are presenting modal view controllers you should use - (BOOL)isBeingDismissed instead:但是,如果您正在展示模态视图控制器,您应该使用- (BOOL)isBeingDismissed代替:

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

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

As noted in this question , you could combine both properties:this question所述,您可以结合这两个属性:

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

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

Other solutions rely on the existence of a UINavigationBar .其他解决方案依赖于UINavigationBar的存在。 Instead like my approach more because it decouples the required tasks to perform from the action that triggered the event, ie pressing a back button.相反,我更喜欢我的方法,因为它将执行所需的任务与触发事件的操作(即按下后退按钮)分离。

While viewWillAppear() and viewDidDisappear() are called when the back button is tapped, they are also called at other times.当点击后退按钮调用viewWillAppear()viewDidDisappear() ,在其他时候也会调用它们。 See end of answer for more on that.有关更多信息,请参见答案的结尾。

Using UIViewController.parent使用 UIViewController.parent

Detecting the back button is better done when the VC is removed from its parent (the NavigationController) with the help of willMoveToParentViewController(_:) OR didMoveToParentViewController()当在willMoveToParentViewController(_:)didMoveToParentViewController()的帮助下将 VC 从其父级(导航控制器)中移除时,检测后退按钮会更好

If parent is nil, the view controller is being popped off the navigation stack and dismissed.如果 parent 为 nil,则视图控制器将从导航堆栈中弹出并关闭。 If parent is not nil, it is being added to the stack and presented.如果 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
    }
}

Swap out willMove for didMove and check self.parent to do work after the view controller is dismissed.willMove didMove并在视图控制器关闭检查 self.parent 以完成工作。

Stopping the dismiss停止解雇

Do note, checking the parent doesn't allow you to "pause" the transition if you need to do some sort of async save.请注意,如果您需要进行某种异步保存,检查父级不允许您“暂停”转换。 To do that you could implement the following.为此,您可以实现以下内容。 Only downside here is you lose the fancy iOS styled/animated back button.唯一的缺点是你失去了花哨的 iOS 风格/动画后退按钮。 Also be careful here with the interactive swipe gesture.使用交互式滑动手势也要小心。 Use the following to handle this case.使用以下方法来处理这种情况。

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

### More on view will/did appear If you didn't get the `viewWillAppear` `viewDidDisappear` issue, Let's run through an example. ### 更多关于视图将/确实出现如果您没有遇到 `viewWillAppear` `viewDidDisappear` 问题,让我们通过一个示例来运行。 Say you have three view controllers: 假设您有三个视图控制器:
  1. ListVC: A table view of things ListVC:事物的表格视图
  2. DetailVC: Details about a thing DetailVC:关于事物的详细信息
  3. SettingsVC: Some options for a thing SettingsVC:事物的一些选项

Lets follow the calls on the detailVC as you go from the listVC to settingsVC and back to listVC当您从listVC转到settingsVC并返回listVC时,让我们关注detailVC上的调用

List > Detail (push detailVC) Detail.viewDidAppear <- appear List > Detail (push detailVC) Detail.viewDidAppear <- 出现
Detail > Settings (push settingsVC) Detail.viewDidDisappear <- disappear详细信息>设置(推送设置VC) Detail.viewDidDisappear <-消失

And as we go back...而当我们回去...
Settings > Detail (pop settingsVC) Detail.viewDidAppear <- appear Settings > Detail (pop settingsVC) Detail.viewDidAppear <- 出现
Detail > List (pop detailVC) Detail.viewDidDisappear <- disappear Detail > List (pop detailVC) Detail.viewDidDisappear <-消失

Notice that viewDidDisappear is called multiple times, not only when going back, but also when going forward.请注意, viewDidDisappear被多次调用,不仅在返回时,而且在前进时。 For a quick operation that may be desired, but for a more complex operation like a network call to save, it may not.对于可能需要的快速操作,但对于更复杂的操作(例如要保存的网络调用),可能不需要。

Those who claim that this doesn't work are mistaken:那些声称这不起作用的人是错误的:

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

That works fine.这很好用。 So what is causing the widespread myth that it doesn't?那么是什么导致了它没有的普遍神话呢?

The problem seems to be due to an incorrect implementation of a different method, namely that the implementation of willMove(toParent:) forgot to call super .问题似乎是由于不同方法的不正确实现,即willMove(toParent:)的实现忘记调用super

If you implement willMove(toParent:) without calling super , then self.isMovingFromParent will be false and the use of viewWillDisappear will appear to fail.如果您在没有调用super的情况下实现willMove(toParent:) ,则self.isMovingFromParent将为false并且viewWillDisappear的使用将出现失败。 It didn't fail;它没有失败。 you broke it.你把它弄坏了。

NOTE: The real problem is usually the second view controller detecting that the first view controller was popped.注意:真正的问题通常是第二个视图控制器检测到第一个视图控制器被弹出。 Please see also the more general discussion here: Unified UIViewController "became frontmost" detection?另请参阅此处更一般的讨论: Unified UIViewController "became frontmost" detection?

EDIT A comment suggests that this should be viewDidDisappear rather than viewWillDisappear .编辑评论建议这应该是viewDidDisappear而不是viewWillDisappear

First Method第一种方法

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

Second Method第二种方法

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

I've playing (or fighting) with this problem for two days.我已经玩(或打)这个问题两天了。 IMO the best approach is just to create an extension class and a protocol, like this: 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

This works because UINavigationController will receive a call to navigationBar:shouldPopItem: every time a view controller is popped.这是因为UINavigationController会在每次弹出视图控制器时收到对navigationBar:shouldPopItem:的调用。 There we detect if back was pressed or not (any other button).在那里我们检测是否按下了返回(任何其他按钮)。 The only thing you have to do is implement the protocol in the view controller where back is pressed.您唯一需要做的就是在按下返回的视图控制器中实现协议。

Remember to manually pop the view controller inside backButtonPressedSel , if everything is ok.如果一切正常,请记住在backButtonPressedSel中手动弹出视图控制器。

If you already have subclassed UINavigationViewController and implemented navigationBar:shouldPopItem: don't worry, this won't interfere with it.如果您已经继承了UINavigationViewController并实现了navigationBar:shouldPopItem:不用担心,这不会干扰它。

You may also be interested in disable the back gesture.您可能还对禁用后退手势感兴趣。

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

This works for me in iOS 9.3.x with Swift:这适用于带有 Swift 的 iOS 9.3.x 中的我:

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

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

Unlike other solutions here, this doesn't seem to trigger unexpectedly.与此处的其他解决方案不同,这似乎不会意外触发。

You can use the back button callback, like this:您可以使用后退按钮回调,如下所示:

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

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

for swift version you can do something like in global scope对于 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
    }
}

Below one you put in the viewcontroller where you want to control back button action:在您想要控制后退按钮操作的视图控制器中放置一个:

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

    return true
}

For the record, I think this is more of what he was looking for…为了记录,我认为这更像是他正在寻找的……

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

The best way is to use the UINavigationController delegate methods最好的方法是使用 UINavigationController 委托方法

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

Using this you can know what controller is showing the UINavigationController.使用它你可以知道哪个控制器正在显示 UINavigationController。

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

For Swift with a UINavigationController:对于带有 UINavigationController 的 Swift:

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

As purrrminator says, the answer by elitalon is not completely right, since your stuff would be executed even when popping the controller programmatically.正如purrrminator所说, elitalon的答案并不完全正确,因为即使以编程方式弹出控制器, your stuff也会被执行。

The solution I have found so far is not very nice, but it works for me.到目前为止我找到的解决方案不是很好,但它对我有用。 Besides what elitalon said, I also check whether I'm popping programmatically or not:除了elitalon所说的,我还检查了我是否以编程方式弹出:

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

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

You have to add that property to your controller and set it to YES before popping programmatically:在以编程方式弹出之前,您必须将该属性添加到控制器并将其设置为 YES:

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

Thanks for your help!谢谢你的帮助!

I have solved this problem by adding a UIControl to the navigationBar on the left side .我通过向左侧的导航栏添加 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];

And you need to remember to remove it when view will disappear:当视图消失时,您需要记住将其删除:

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

That's all!就这样!

7ynk3r 's answer was really close to what I did use in the end but it needed some tweaks: 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;
    }
}

I used Pedro Magalhães solution, except navigationBar:shouldPop was not called when I used it in an extension like this:我使用了Pedro Magalhães解决方案,除了在这样的扩展中使用navigationBar:shouldPop时没有调用它:

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

But the same thing in a UINavigationController subclass worked fine.但是在UINavigationController子类中同样的事情工作得很好。

class NavigationController: UINavigationController, UINavigationBarDelegate {

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

I see some other questions reporting this method not being called (but the other delegate methods being called as expected), from iOS 13?我从 iOS 13 看到一些其他问题报告此方法未被调用(但其他委托方法被按预期调用)?

iOS 13 and UINavigationBarDelegate::shouldPop() iOS 13 和 UINavigationBarDelegate::shouldPop()

You should check out the UINavigationBarDelegate Protocol .您应该查看UINavigationBarDelegate 协议 In this case you might want to use the navigationBar:shouldPopItem: method.在这种情况下,您可能想要使用 navigationBar:shouldPopItem: 方法。

As Coli88 said, you should check the UINavigationBarDelegate protocol.正如 Coli88 所说,您应该检查 UINavigationBarDelegate 协议。

In a more general way, you can also use the - (void)viewWillDisapear:(BOOL)animated to perform custom work when the view retained by the currently visible view controller is about to disappear.更一般的方式,你也可以使用- (void)viewWillDisapear:(BOOL)animated在当前可见的视图控制器保留的视图即将消失时执行自定义工作。 Unfortunately, this would cover bother the push and the pop cases.不幸的是,这将涵盖推送和弹出案例。

self.navigationController.isMovingFromParentViewController is not working anymore on iOS8 and 9 I use : self.navigationController.isMovingFromParentViewController 在我使用的 iOS8 和 9 上不再工作:

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

(SWIFT) (迅速)

finaly found solution.. method we were looking for is "willShowViewController" which is delegate method of UINavigationController最终找到解决方案..我们正在寻找的方法是“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
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM