检测导航栏上按下“后退”按钮的时间

时间:2011-11-22 14:27:48

标签: iphone objective-c ios xcode

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

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

17 个答案:

答案 0 :(得分:296)

更新:根据一些评论,原始答案中的解决方案在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的存在。而不是像我的方法更多,因为它从触发事件的动作中解除了执行所需的任务,即按下后退按钮。

答案 1 :(得分:90)

点击后退按钮时,viewWillAppear()viewDidDisappear() 被调用,但其他时间也会调用它们。有关详细信息,请参阅答案的结尾。

使用UIViewController.parent

willMoveToParentViewController(_:)didMoveToParentViewController()

的帮助下从VC的父级(NavigationController)中删除VC时,最好先检测后退按钮

如果parent为nil,则视图控制器将从导航堆栈弹出并被解除。如果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(toParentViewController parent: UIViewController?) {
    super.willMove(toParentViewController: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:某物的一些选项
  4. 让我们在detailVClistVC之后跟随settingsVC上的来电,然后回到listVC

    列表>细节(推送detailVC)Detail.viewDidAppear< - 出现
    详细信息>设置(推送设置VC)Detail.viewDidDisappear< - 消失

    当我们回去时......
    设置>细节(pop settingsVC)Detail.viewDidAppear< - 出现
    详细信息>列表(pop detailVC)Detail.viewDidDisappear< - 消失

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

答案 2 :(得分: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];
}

答案 3 :(得分: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:的调用。在那里我们检测是否按下了背部(任何其他按钮)。 您唯一需要做的就是在视图控制器中实现协议,在该控制器中按下后退。

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

如果您已经有子UINavigationViewController子句并且已经执行navigationBar:shouldPopItem:,请不要担心,这不会干扰它。

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

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

答案 4 :(得分:7)

这适用于iOS 9.3.x with Swift:

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

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

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

答案 5 :(得分:4)

那些声称这不起作用的人被误认为:

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

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

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

如果在不调用willMove(toParent:)的情况下实现super,则self.isMovingFromParent将为false,并且viewWillDisappear的使用将失败。它没有失败;你把它弄坏了。

答案 6 :(得分: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];
    }

答案 7 :(得分: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];

感谢您的帮助!

答案 8 :(得分:2)

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

<!doctype html>
<html>
<body>
  <div id="header">Header</div>
       
  <div id='mid' style="">
    <div style="display:flex">
      <div id='leftnav'>Left nav</div>      
      <div id='content'></div>
    </div>
  </div>
  <div id="footer">Footer</div>
</body>
</html>

使用此功能,您可以知道控制器正在显示UINavigationController。

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

答案 9 :(得分:2)

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

答案 10 :(得分:1)

对于带有UINavigationController的Swift:

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

答案 11 :(得分:1)

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

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

答案 12 :(得分:1)

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

这就是全部!

答案 13 :(得分:1)

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

答案 14 :(得分:0)

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

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

    return true
}

对于swift版本,您可以在viewcontroller中执行某些操作,例如

<div class="headers">
    <div id="header-1">Header 1</div>
    <div id="header-2 selected">Header 2</div>
</div>
<div class="contents">
    <div id="contents-2-1">Сontents 2.1</div>
    <div id="contents-2-2">Сontents 2.2</div>
</div>

答案 15 :(得分:0)

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

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

答案 16 :(得分:-1)

(SWIFT)

终于找到了解决方案..我们正在寻找的方法是&#34; willShowViewController#34;这是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
        }
    }
}