ios 确认UINavigationController上的返回按钮

c90pui9n  于 2023-07-01  发布在  iOS
关注(0)|答案(8)|浏览(124)

我正在使用storyboard为我的应用程序,这是使用UINavigationController。我想在默认的后退按钮上添加一个确认对话框,这样用户就不会意外注销。有什么办法可以做到吗?我发现我不能简单地访问后退按钮时,它已自动创建的UINavigationController
有什么想法吗

3z6pesqy

3z6pesqy1#

不幸的是,你不能以这种方式拦截后退按钮。最接近的传真是使用您自己的UIBarButtonItem设置为navigationItem.leftBarButtonItem,并设置一个操作来显示您的警报等。我有一个平面设计师创建按钮图像,看起来像标准的后退按钮。
顺便说一句,我需要拦截后退按钮是出于另一个原因。我强烈建议您重新考虑这个设计选择。如果你展示的是一个用户可以进行更改的视图,并且你希望他们可以选择保存或取消,恕我直言,最好使用“保存”和“取消”按钮,而不是带警告的后退按钮。警报通常很烦人。或者,明确用户正在进行的更改是在他们进行更改时提交的。那这个问题就没意义了

b09cbbtk

b09cbbtk2#

我如何解决这种情况是通过将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),苹果似乎并不介意。
am46iovg

am46iovg3#

实际上,您可以找到Back按钮视图并将UITapGestureRecognizer添加到其中。
如果你看这张图片:

使用以下代码:

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

点击后退按钮,瞧:

5tmbdcev

5tmbdcev4#

试试这个解决方案:

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上测试了它,它运行得很好:)

kkih6yb8

kkih6yb85#

我想出了一个简单的解决方案,开箱即用:
1.)您需要创建一个自定义导航控制器:

//
//  MyNavigationController.swift
//

import UIKit

// Marker protocol for all the VC that requires confirmation on Pop
protocol PopRequiresConfirmation {}

class MyNavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

// MARK: - UINavigationBarDelegate Conformance
extension MyNavigationController: UINavigationBarDelegate {
    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        guard shouldAskConfirmation(for: item) else {
            return true
        }

        self.askUserForConfirmation()
        return false
    }
    
    private func shouldAskConfirmation(for item: UINavigationItem) -> Bool {
        guard
            let vc = self.viewControllers.last(where: { $0.navigationItem === item}),
            vc is PopRequiresConfirmation
        else {
            return false
        }
        
        return true
    }
    
    func askUserForConfirmation() {
        let alertController = UIAlertController(
            title: "Cancel Insertion",
            message: "Do you really want to go back? If you proceed, all the inserted data will be lost.",
            preferredStyle: .alert
        )
        
        alertController.addAction(
            .init(
                title: "Yes, cancel",
                style: .destructive,
                handler: { [weak self] _ in
                    self?.popViewController(animated: true)
                }
            )
        )
        
        alertController.addAction(
            .init(
                title: "No, continue",
                style: .cancel,
                handler: nil
            )
        )
        
        self.present(alertController, animated: true, completion: nil)
    }
}

2.)将以下代码添加到所有需要确认“PopRequiresConfirmation”的ViewController:

//
//  ViewController2.swift
//

import UIKit

class ViewController2: UIViewController, PopRequiresConfirmation {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

2vuwiymt

2vuwiymt6#

下面是你需要做的,以轻松地制作一个自定义后退按钮,复制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获得确切的按钮pngs,我推荐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的更新,默认后退按钮项的外观可能会发生变化...

nbewdwxp

nbewdwxp7#

下面是一个解决方法:(在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作为魔术数字,你可以找到包含后退按钮的视图,并使用它的框架来检测触摸。

vs91vp4v

vs91vp4v8#

很简单。只要用一个透明的UI控件覆盖按钮并捕捉触摸。

- (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按钮,但是确定位置要复杂得多。

相关问题