Кнопка подтверждения на UINavigationController

Я использую storyboard для моего приложения, которое использует UINavigationController. Я хотел бы добавить диалоговое окно подтверждения к кнопке возврата по умолчанию, чтобы пользователь не вышел из системы случайно. Есть ли способ сделать это? Я обнаружил, что не могу просто получить доступ к кнопке "Назад", когда она автоматически создается UINavigationController.

Есть идеи?

Ответ 1

К сожалению, вы не можете перехватить кнопку "Назад" таким образом. Самый близкий факс - использовать свой собственный UIBarButtonItem, установленный в navigationItem.leftBarButtonItem, и установить действие для отображения вашего предупреждения и т.д. У меня был графический дизайнер, создающий изображения кнопок, которые выглядят как стандартная кнопка возврата.

В стороне, мне нужно было перехватить кнопку "назад" по другой причине. Я настоятельно рекомендую вам пересмотреть этот дизайн. Если вы представляете представление, в котором пользователи могут вносить изменения, и вы хотите, чтобы у них был выбор, чтобы сохранить или отменить IMHO, лучше использовать кнопки "Сохранить" и "Отменить" или кнопку "Назад" с предупреждением. Оповещения обычно раздражают. Кроме того, дайте понять, что изменения, внесенные вами пользователями, совершаются в момент их создания. Тогда этот вопрос является спорным.

Ответ 2

Я обошел эту ситуацию, установив 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

Я должен добавить отказ от ответственности, хотя; это нарушает поведение навигации по умолчанию, и Apple может не понравиться разработчикам. Я не представил ни одного приложения (пока) с этой функцией, поэтому я не уверен, разрешит ли Apple при этом делать ваше приложение в магазине, но будьте осторожны;)

ОБНОВЛЕНИЕ: Хорошие новости, все! Тем временем я выпустил приложение (Appcident) в App Store с таким поведением, и Apple, похоже, не против.

Ответ 3

Собственно, вы можете найти представление кнопки "Назад" и добавить к нему UITapGestureRecognizer.

Если вы посмотрите на это изображение: Back button screen Используя этот код:

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

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

Вы можете понять, как найти кнопку "Вид сзади". Это всегда последний в массиве subviews панели навигации.

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

Нажатие на кнопку возврата и voilà: Back button was tapped

Ответ 4

Ниже приведено обходное решение: (Проверено на 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 в качестве магического номера, вы можете найти представление, содержащее кнопку "Назад", и использовать его, чтобы обнаружить штрихи на нем.

Ответ 5

Я согласен с XJones. UINavigationController имеет свои ограничения. Использование leftBarButtonItem является самым простым решением.

Если вы хотите иметь кнопку "Назад", и вы не хотите полностью изменять свой интерфейс, вы всегда можете написать свой собственный контроллер навигации, используя компонент UINavigationBar. Это не так сложно.

Вы также можете попытаться наследовать UINavigationController и переопределить popViewControllerAnimated:. Не уверен, что это сработает.

Ответ 6

Вот то, что вам нужно сделать, чтобы легко создать пользовательскую кнопку "Назад" , которая воспроизводит внешний вид кнопки "Назад" по умолчанию на iPhone и iPad с кодом, написанным явно, потому что я предполагаю, что я приду сюда, ища это снова в какой-то момент,

Поместите следующие функции в файл реализации (.m) соответствующего UIViewController с помощью UINavigationController, а затем в 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");
}

Чтобы получить точную кнопку png из iOS, я рекомендую UIKit Artwork Extractor. После запуска проекта и сохранения изображений на симуляторе Retina iPad с последующим симулятором iPad без сетчатки найдите названия в папке "Common" в папке симулятора, которая появится на вашем рабочем столе. Имена файлов "UINavigationBar... Back (@2x).png" и "UINavigationBar... BackPressed (@2x).png" - это то, что вы хотите.

Я также прикрепляю по умолчанию iOS (iPhone и iPad) кнопку pngs бэк-панели, используемую в приведенном выше коде для удобства. Обратите внимание, что с обновлениями iOS внешний вид стандартных барбат-тетов по умолчанию может меняться...

Ответ 7

Очень просто. Просто закройте кнопку прозрачным 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, но она более сложна для определения местоположения.