确认UINavigationController上的后退按钮

时间:2011-12-19 17:16:28

标签: ios uinavigationcontroller back-button

我正在使用storyboard我的应用使用UINavigationController。我想在默认的后退按钮中添加一个确认对话框,这样用户就不会意外注销。有办法做到这一点吗?我发现,当UINavigationController自动创建后退按钮时,我无法简单地访问后退按钮。

有什么想法吗?

7 个答案:

答案 0 :(得分:22)

不幸的是,你不能以这种方式拦截后退按钮。最接近的传真是使用您自己的UIBarButtonItem设置到navigationItem.leftBarButtonItem并设置动作以显示您的警报等。我有一个图形设计师创建看起来像标准后退按钮的按钮图像。

顺便说一句,我需要拦截后退按钮的原因不同。我敦促你重新考虑这个设计选择。如果您要呈现用户可以进行更改的视图,并且您希望他们可以选择保存或取消IMHO,则最好使用“保存”和“取消”按钮与带警报的后退按钮。警报通常很烦人。或者,明确表示用户所做的更改是在他们创建时提交的。那么这个问题没有实际意义。

答案 1 :(得分:10)

我是如何解决这种情况的方法是将leftBarButtonItem设置为UIBarButtonSystemItemTrash样式(立即显示他们将删除草稿项目)并添加确认删除的警报视图。由于您设置了自定义leftBarButtonItem,因此它不会像后退按钮那样,因此它不会自动弹出视图!

在代码中:

- (void)viewDidLoad
{
    // set the left bar button to a nice trash can
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
                                                                                          target:self
                                                                                          action:@selector(confirmCancel)];
    [super viewDidLoad];
}

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex)
    {
        // The didn't press "no", so pop that view!
        [self.navigationController popViewControllerAnimated:YES];
    }
}

- (void)confirmCancel
{
    // Do whatever confirmation logic you want here, the example is a simple alert view
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Warning"
                                                    message:@"Are you sure you want to delete your draft? This operation cannot be undone."
                                                   delegate:self
                                          cancelButtonTitle:@"No"
                                          otherButtonTitles:@"Yes", nil];
    [alert show];
}

真的很简单!我没有看到重大问题,tbh:p

我必须添加免责声明;这样做会破坏默认的导航行为,苹果可能不喜欢开发人员这样做。我还没有使用此功能提交任何应用程序,所以我不确定苹果是否会允许您的应用程序在商店中执行此操作,但请注意;)

更新: 好消息,大家好!与此同时,我已经向App Store发布了一个应用程序(Appcident),这种行为已经到位,Apple似乎并不介意。

答案 2 :(得分:3)

实际上,您可以找到后退按钮视图并向其添加UITapGestureRecognizer。

如果你看这个图像: Back button screen 使用此代码:

@interface UIView (debug)
- (NSString *)recursiveDescription;
@end

@implementation newViewController
... 
NSLog(@"%@", [self.navigationController.navigationBar recursiveDescription]);

您可以了解如何查找后退视图按钮。 始终是导航栏的子视图数组中的最后一个。

2012-05-11 14:56:32.572 backBtn[65281:f803] <UINavigationBar: 0x6a9e9c0; frame = (0 20; 320 44); clipsToBounds = YES; opaque = NO; autoresize = W; layer = <CALayer: 0x6a9ea30>>
   | <UINavigationBarBackground: 0x6aa1340; frame = (0 0; 320 44); opaque = NO; autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x6aa13b0>>
   | <UINavigationButton: 0x6d6dde0; frame = (267 7; 48 30); opaque = NO; layer = <CALayer: 0x6d6d9f0>>
   |    | <UIImageView: 0x6d70400; frame = (0 0; 48 30); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6d6d7d0>>
   |    | <UIButtonLabel: 0x6d70020; frame = (12 7; 23 15); text = 'Edit'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6d6dec0>>
   | <UINavigationItemView: 0x6d6d3a0; frame = (160 21; 0 0); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6d6d3f0>>
   | <UINavigationItemButtonView: 0x6d6d420; frame = (5 7; 139 30); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6d6d4e0>>

所以我用过:

UIView *backButton = [[navBar subviews] lastObject];
[backButton setUserInteractionEnabled:YES];

UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(alertMsg)];
[tapGestureRecognizer setNumberOfTapsRequired:1];
[backButton addGestureRecognizer:tapGestureRecognizer];

点击后退按钮并发出声音: Back button was tapped

答案 3 :(得分:1)

这是一个解决方法:(在iOS10和11上测试)

在导航栏中添加点击手势识别器:

let tap = UITapGestureRecognizer(target: self, action: #selector(onBackPressed(gestureRecognizer:)))
tap.cancelsTouchesInView = true
self.navigationController?.navigationBar.addGestureRecognizer(tap)

cancelsTouchesInView = true确保后退按钮不会触及事件。

处理手势:

@objc func onBackPressed(gestureRecognizer: UITapGestureRecognizer) {
    guard gestureRecognizer.location(in: gestureRecognizer.view).x < 100 else {
        return
    }
    // ... back button is pressed do what you wanted to

不是使用100作为幻数,而是可以找到包含后退按钮的视图并使用它的框架来检测它上的触摸。

答案 4 :(得分:1)

尝试以下解决方案:

protocol CustomNavigationViewControllerDelegate {
    func shouldPop() -> Bool
}

class CustomNavigationViewController: UINavigationController, UINavigationBarDelegate {
    var backDelegate: CustomNavigationViewControllerDelegate?

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

class SecondViewController: UIViewController, CustomNavigationViewControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        (self.navigationController as? CustomNavigationViewController)?.backDelegate = self
    }

    func shouldPop() -> Bool {
        if (needToShowAlert) {
            showExitAlert()
            return false

        } else {
            return true
        }
    }
}

我在iOS 11和iOS 13上对其进行了测试,并且效果很好:)

答案 5 :(得分:0)

您需要做的就是轻松制作一个自定义后退按钮,复制iPhone和iPad上默认后退按钮的外观,并明确写出代码,因为我想我会在某处再次来这里寻找。

将以下函数放在UINavigationController的相关UIViewController的实现(.m)文件中,然后在viewDidLoad中运行[self setupBackButton];

无论您想要使用后退按钮做什么,请输入backButtonPressed功能。

- (void)setupBackButton {
    UIImage *leftArrowImage;
    UIImage *pressedLeftArrowImage;
    UIButton *customBackButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 48, 30)];
    [customBackButton setAutoresizingMask:UIViewAutoresizingNone];
    customBackButton.titleLabel.font=[UIFont boldSystemFontOfSize:12];
    [customBackButton setTitle:@"Back" forState:UIControlStateNormal];
    [customBackButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled];
    [customBackButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        leftArrowImage = [UIImage imageNamed:@"UINavigationBarSilverBack.png"];
        pressedLeftArrowImage = [UIImage imageNamed:@"UINavigationBarSilverBackPressed.png"];
    }
    else {
        leftArrowImage = [UIImage imageNamed:@"UINavigationBarDefaultBack.png"];
        pressedLeftArrowImage = [UIImage imageNamed:@"UINavigationBarDefaultBackPressed.png"];
    }
    UIImage *stretchableLeftArrowImage = [leftArrowImage stretchableImageWithLeftCapWidth:15.0 topCapHeight:0];
    UIImage *stretchablePressedLeftArrowImage = [pressedLeftArrowImage stretchableImageWithLeftCapWidth:15.0 topCapHeight:0];
    [customBackButton setBackgroundColor:[UIColor clearColor]];
    [customBackButton setBackgroundImage:stretchableLeftArrowImage forState:UIControlStateNormal];
    [customBackButton setBackgroundImage:stretchablePressedLeftArrowImage forState:UIControlStateHighlighted];
    [customBackButton addTarget:self action:@selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *aCustomBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:customBackButton];
    [[self navigationItem] setLeftBarButtonItem:aCustomBackButtonItem];
}

- (void)backButtonPressed:(id)sender {
    NSLog(@"back button pressed");
}

要从iOS获取确切的按钮png,我建议使用UIKit Artwork Extractor。运行项目并将图像保存在iPad Retina模拟器上,然后是iPad非视网膜模拟器后,在桌面上显示的模拟器文件夹中的“Common”文件夹中查找标题。文件名“UINavigationBar ... Back(@ 2x).png”和“UINavigationBar ... BackPressed(@ 2x).png”是你想要的。

为了方便起见,我还附上了上面代码中使用的default iOS (iPhone and iPad) back bar button pngs。请注意,对于iOS更新,默认后退barbuttonitems的外观可能会更改...

答案 6 :(得分:0)

轻松自在。只需用透明的UIControl覆盖按钮并捕获触摸即可。

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

#define COVER_HEIGHT    44
    //make this an iVar:  UIControl *backCover;
    if ( backCover == nil ) {
        CGRect cFrame = CGRectMake( 0, self.view.frame.origin.y-COVER_HEIGHT, 100, COVER_HEIGHT);
        backCover = [[UIControl alloc] initWithFrame:cFrame]; // cover the back button
        backCover.backgroundColor = [[UIColor orangeColor] colorWithAlphaComponent:0.2]; // transparent
        // use clearColor later
        [backCover addTarget:self action:@selector(backCoverAction:)
            forControlEvents:UIControlEventTouchDown];
        [self.view.window addSubview:backCover];
    }
}

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

    [backCover removeFromSuperview]; // prevent coverage on another view
    backCover = nil;
}

- (void)backCoverAction:(UIControl *)sender
{
    // decide what to do -- maybe show a dialog.
    // to actually go "Back" do this:
    [self.navigationController popViewControllerAnimated:YES]; // "Back"
}

此方案也适用于tabBar按钮,但确定位置更为复杂。