尝试处理iOS中的“返回”导航按钮操作

76

我需要检测用户何时点击导航栏上的“返回”按钮,以便在发生此情况时执行一些操作。我正在尝试手动设置该按钮的操作方式,如下所示:

[self.navigationItem.backBarButtonItem setAction:@selector(performBackNavigation:)];

- (void)performBackNavigation:(id)sender
{
   // Do operations

   [self.navigationController popViewControllerAnimated:NO];
}

起初我把那段代码放在视图控制器中,但我发现 self.navigationItem.backBarButtonItem 似乎是 nil ,所以我把同样的代码移到了父视图控制器中,它将前者推入导航堆栈。但我仍然无法使它工作。我阅读了一些关于这个问题的文章,其中一些说选择器需要在父视图控制器中设置,但是无论如何对我来说都不起作用... 我做错了什么?

谢谢


在viewWillDisappear中放置所需的代码是否足够好? - DogCoffee
1
使用 UINavigationControllerDelegate 上的方法。 - Mike Weller
@Smick 不好意思,在我的情况下那样做是不够的... - AppsDev
@MikeWeller 我尝试了,但是我无法让它工作。 - AppsDev
请查看此问题的答案。这是我找到的最佳解决方案。https://dev59.com/k3M_5IYBdhLWcg3w02v8#19132881 - James Parker
10个回答

131

尝试使用VIewWillDisappear方法来检测导航栏返回按钮的按下:

-(void) viewWillDisappear:(BOOL)animated
{
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) 
    {
        // Navigation button was pressed. Do some stuff 
        [self.navigationController popViewControllerAnimated:NO];
    }
    [super viewWillDisappear:animated];
}

或者有另一种方法来获取导航返回按钮的操作。

为UINavigationItem的返回按钮创建自定义按钮。

例如:

在ViewDidLoad中:

- (void)viewDidLoad 
{
    [super viewDidLoad];
    UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:@"Home" style:UIBarButtonItemStyleBordered target:self action:@selector(home:)];
    self.navigationItem.leftBarButtonItem=newBackButton;
}

-(void)home:(UIBarButtonItem *)sender 
{
    [self.navigationController popToRootViewControllerAnimated:YES];
}

Swift:


(说明:这是一个关于Swift编程语言的标题)
override func willMoveToParentViewController(parent: UIViewController?) 
{
    if parent == nil 
    {
        // Back btn Event handler
    }
}

3
我认为在viewWillDisappear中不需要使用[self.navigationController popViewControllerAnimated:NO]。 - Robert Kang
2
我认为这种方法在iOS 8上不再适用,因为self.navigationController.viewControllers仅包含一个元素,== self。 - Sébastien Stormacq
1
@Sébastien Stormacq 为什么你这么说?它在iOS 8中可以工作。 - 蘇健豪
1
如果您在 ViewWillDisappear 中调用此方法,它不仅会在返回按钮被点击时被调用。每当 VC 被弹出或推入新 VC 时,它都将被调用。 - NSNoob

41

快速

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        //"Back pressed"
    }
}

5
这个解决方案的唯一问题是,如果你想滑动返回却改变了主意,那么这个操作就会被触发。 - chris P

18

也许这个答案并不符合你的问题标题所描述的,但它有助于你了解何时点击了UINavigationBar上的后退按钮。

在这种情况下,您可以使用UINavigationBarDelegate协议并实现其中一个方法:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item;

当调用didPopItem方法时,这是因为你要么点击了后退按钮,要么使用了[UINavigationBar popNavigationItemAnimated:]方法,导航栏弹出了该项。

现在,如果您想知道是什么操作触发了didPopItem方法,您可以使用标志。

通过这种方法,我不需要手动添加带有箭头图像的左栏按钮项目,以使其类似于iOS返回按钮,并能够设置自定义目标/操作。


让我们看一个例子:

我有一个视图控制器,其中包含一个页面视图控制器和自定义页面指示器视图。我还使用自定义UINavigationBar显示标题以了解我所在的页面以及返回到上一页的后退按钮。我还可以在页面控制器上向前/向后滑动页面。

#pragma mark - UIPageViewController Delegate Methods
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {

    if( completed ) {

        //...

        if( currentIndex > lastIndex ) {

            UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:@"Some page title"];

            [[_someViewController navigationBar] pushNavigationItem:navigationItem animated:YES];
            [[_someViewController pageControl] setCurrentPage:currentIndex];
        } else {
            _autoPop = YES; //We pop the item automatically from code.
            [[_someViewController navigationBar] popNavigationItemAnimated:YES];
            [[_someViewController pageControl] setCurrentPage:currentIndex];
        }
    }

}

然后我实现了UINavigationBar代理方法:

#pragma mark - UINavigationBar Delegate Methods
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
    if( !_autoPop ) {
        //Pop by back button tap
    } else {
        //Pop from code
    }

    _autoPop = NO;

    return YES;
}
在这种情况下,我使用了shouldPopItem,因为弹出是带有动画效果的,我想立即处理返回按钮而不必等到过渡结束。

12

didMoveToParentViewController 方法的问题在于它在父视图完全可见后才会被调用,因此如果您需要在此之前执行一些任务,则无法正常工作。

而且它不能与驱动动画手势一起使用。使用 willMoveToParentViewController 更好。

Objective-C

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        // ...
    }
}

Swift

override func willMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        // ...  
    }
}

6

这是dadachi的答案的Objective-C版本:

Objective-C

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

3

在Swift 4或更高版本中:

override func didMove(toParent parent: UIViewController?) {
    if parent == nil {
        //"Back pressed"
    }
}

3

设置 UINavigationBar 的委托,然后使用:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
    //handle the action here
}

2
如果您正在使用UINavigationController来管理导航栏并尝试设置代理,则会引发异常:“ *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',reason:'Cannot manually set the delegate on a UINavigationBar managed by a controller.'”。 UINavigationController是代理。这意味着您可以子类化控制器并覆盖UINavigationBarDelegate方法(可能调用super)。 - Geoff Hackworth
但是你不能直接调用super,因为UINavigationController没有公开遵守UINavigationBarDelegate,这会导致编译器错误!可能有一种使用UINavigationControllerDelegate的解决方案。 - Geoff Hackworth

3

其他方法对我都没有用,但这个方法可以:

创建自己的UINavigationController子类,使其实现UINavigationBarDelegate(无需手动设置导航栏的代理),添加一个UIViewController扩展,定义一个在返回按钮按下时调用的方法,然后在你的UINavigationController子类中实现这个方法:

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

你能详细说明一下你的答案,并展示如何在视图控制器中使用它吗? - zeeshan

2

设置UINavigationControllerDelegate并实现此委托函数(Swift):

func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    if viewController is <target class> {
        //if the only way to get back - back button was pressed
    }
}

1
使用自定义的UINavigationController子类,该子类实现了shouldPop方法。
在Swift中:
class NavigationController: UINavigationController, UINavigationBarDelegate
{
    var shouldPopHandler: (() -> Bool)?

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool
    {
        if let shouldPopHandler = self.shouldPopHandler, !shouldPopHandler()
        {
            return false
        }
        self.popViewController(animated: true) // Needed!
        return true
    }
}

当设置时,将调用您的shouldPopHandler()来决定控制器是否弹出。当未设置时,它将像通常一样被弹出。
最好禁用UINavigationControllerinteractivePopGestureRecognizer,否则手势不会调用您的处理程序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接