3

由于我试图拒绝但没有奏效的奇怪请求,我不得不覆盖导航栏的后退按钮。

我制作了一个自定义 UINavigationController 子类并破解了该 - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item方法。

这是我的代码:

@interface CustomUINavigationController ()

@end

@implementation CustomUINavigationController


#pragma mark - UINavigationBar delegate methods

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

    if ([[self.viewControllers lastObject] isKindOfClass:[ViewController1 class]]) {
        ViewController1 *vc1 = (ViewController1 *)[self.viewControllers lastObject];
        [vc1 handleBackAction];
        if (vc1.canPopVC == YES) { 
            [self popViewControllerAnimated:YES];
            return YES;
        } else {
            return NO;
        }
    }

    [self popViewControllerAnimated:YES];
    return YES;
}

@end

一切正常,除非我以编程方式弹出 viewController。每次我想在弹出后执行推送时,应用程序都会崩溃。TurningNSZombie on显示,当以编程方式弹出 viewController 时,它的父 viewController 被释放。此时,制作自定义 backButton 不是一个选项,因为它会失去原生 iOS 7 滑动到 popViewController 的功能。

崩溃日志:

*** -[ContactsDetailViewController performSelector:withObject:withObject:]: message sent to deallocated instance 0x1806b790
4

5 回答 5

14

(我之前的帖子是完全错误的。这是一个完整的重写与适当的解决方案。)

当我在转换为 ARC 时选择删除一些生成警告的代码时,我弹出了这种行为——我认为没有被调用的代码。

情况如下:

如果你navigationBar:shouldPopItem:在 UINavigationController 的子类中隐藏,那么当用户触摸 NavBar 的 BACK 按钮时,当前的视图控制器将不会被弹出。但是,如果你popViewControllerAnimated:直接调用,你的navigationBar:shouldPopItem:仍然会被调用,并且视图控制器会弹出。

这就是当用户触摸 BACK 按钮时视图控制器无法弹出的原因:

UINavigationController 有一个名为navigationBar:shouldPopItem:. 该方法在用户点击BACK按钮时被调用,是用户点击BACK按钮时正常调用的方法popViewControllerAnimated:

当您使用 shadownavigationBar:shouldPopItem:时,不会调用超类的实现,因此不会弹出 ViewController。

为什么你不应该popViewControllerAnimated:在你的子类中调用' navigationBar:shouldPopItem:

如果调用popViewControllerAnimated:inside navigationBar:shouldPopItem:,当您单击 NavBar 上的 BACK 按钮时,您将看到您想要的行为:您可以确定是否要弹出,并且您的视图控制器会根据需要弹出。

但是,如果您popViewControllerAnimated:直接调用,您最终会弹出两个视图控制器:一个来自您对 的直接调用popViewControllerAnimated:,另一个来自您添加到 within 的调用navigationBar:shouldPopItem:

我认为安全的解决方案:

您的自定义导航控制器应声明如下:

@interface CustomNavigationController : UINavigationController <UINavigationBarDelegate> 
{
    // .. any ivars you want
}
@end

您的实现应包含如下所示的代码:

// Required to prevent a warning for the call [super navigationBar:navigationBar shouldPopItem:item]
@interface UINavigationController () <UINavigationBarDelegate>
@end


@implementation CustomNavigationController

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    BOOL rv = TRUE;

    if ( /* some condition to determine should NOT pop */ )
    {
        // we won't pop
        rv = FALSE;

        // extra code you might want to execute ...
    } else
    {
        // It's not documented that the super implements this method, so we're being safe
        if ([[CustomNavigationController superclass]
                instancesRespondToSelector:@selector(navigationBar:shouldPopItem:)])
        {
            // Allow the super class to do its thing, which includes popping the view controller 
            rv = [super navigationBar:navigationBar shouldPopItem:item];

        }
    }

    return rv;
}
于 2014-09-28T10:53:09.003 回答
3

我不是 100% 确定,但我认为您实际上不应该在该委托方法中弹出视图控制器。

“应该”委托方法通常不会做某事。他们只是断言应该或不应该做某事。

将您的方法更改为此...

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

    if ([[self.viewControllers lastObject] isKindOfClass:[ViewController1 class]]) {
        ViewController1 *vc1 = (ViewController1 *)[self.viewControllers lastObject];
        [vc1 handleBackAction];
        if (vc1.canPopVC == YES) { 
            return YES;
        } else {
            return NO;
        }
    }

    return YES;
}

看看它是否有效。

我所做的只是删除了popViewController电话。

编辑 - 如何添加自定义后退按钮

在一个类别中UIBarButtonItem...

+ (UIBarButtonItem *)customBackButtonWithTarget:(id)target action:(@SEL)action
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setBackgroundImage:[UIImage imageNamed:@"Some image"] forState:UIControlStateNormal];
    [button setTitle:@"Some Title" forState:UIControlStateNormal];
    [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithCustomView:button];

    return barButtonItem;
}

现在,每当您想设置自定义后退按钮时,只需使用...

UIBarButtonItem *backButton = [UIBarButtonItem customBackButtonWithTarget:self action:@selector(backButtonPressed)];
于 2013-12-02T11:37:33.567 回答
1

我会建议一种完全不同的方法。

为您在导航堆栈上推送的视图控制器创建一个基类。在该viewDidLoad方法中,将您的自定义按钮设置为leftBarButtonItemnavigationItem添加一个-backAction:调用popViewControllerAnimated:导航控制器方法的按钮。

这样,您就不会关心诸如丢失UINavigationController滑动弹出功能之类的事情,并且您根本不必重写该navigationBar:shouldPopItem:方法。

于 2013-12-02T11:50:46.863 回答
0

您可能需要 do[super shouldPop...而不是实际[self popViewControllerAnimated:YES];.

原因是UINavigationController实现堆栈的方式是私有的,所以你应该尽可能少地弄乱方法调用。

无论如何,这看起来像一个黑客。此外,用户不会有任何视觉线索表明您正在阻止导航操作。通过以下方式禁用按钮有什么问题:

self.navigationController.navigationItem.backBarButtonItem.enabled = NO; 
于 2013-12-02T11:44:13.987 回答
0

这是我对 Xcode 11 的 @henryaz 答案的修复:

  @interface UINavigationControllerAndNavigationBarDelegate : UINavigationController<UINavigationBarDelegate>

   @end

    @interface CustomNavigationController : UINavigationControllerAndNavigationBarDelegate
    @end

// changed this method just a bit
            - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
            BOOL shouldPop = // detect if need to pop

        if (shouldPop) {
            shouldPop = [super navigationBar:navigationBar shouldPopItem:item]; // before my fix this code failed with compile error
        }

        return shouldPop;
    }
于 2020-02-03T12:43:15.163 回答