it-swarm.com.ru

Настройка действия кнопки «Назад» в контроллере навигации

Я пытаюсь переписать действие по умолчанию кнопки "Назад" в контроллере навигации. Я предоставил цели действие на пользовательской кнопке. Странно то, что при назначении его через атрибут backbutton он не обращает на них внимания, а просто выскакивает из текущего представления и возвращается к корню:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

Как только я задаю его с помощью leftBarButtonItem на navigationItem, он вызывает мое действие, однако кнопка выглядит как круглая, а не как стрелка назад:

self.navigationItem.leftBarButtonItem = backButton;

Как я могу заставить его вызывать свое пользовательское действие, прежде чем вернуться к корневому представлению? Есть ли способ перезаписать обратное действие по умолчанию или есть метод, который всегда вызывается при выходе из представления (viewDidUnload этого не делает)?

175
Parrots

Попробуйте вставить это в контроллер вида, где вы хотите обнаружить нажатие:

-(void) viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
       // back button was pressed.  We know this is true because self is no longer
       // in the navigation stack.  
    }
    [super viewWillDisappear:animated];
}
363
William Jockusch

Я реализовал IViewController-BackButtonHandler расширение. Для этого не нужно создавать подклассы, просто поместите это в свой проект и переопределите метод navigationShouldPopOnBackButton в классе UIViewController:

-(BOOL) navigationShouldPopOnBackButton {
    if(needsShowConfirmation) {
        // Show confirmation alert
        // ...
        return NO; // Ignore 'Back' button this time
    }
    return YES; // Process 'Back' button click and Pop view controler
}

Скачать пример приложения .

173
onegray

В отличие от Амаграммера, это возможно. Вы должны создать подкласс вашего navigationController. Я все объяснил здесь (включая пример кода).

42
HansPinckaers

Swift версия:

(из https://stackoverflow.com/a/19132881/826435 )

В вашем контроллере представления вы просто соблюдаете протокол и выполняете все необходимые действия:

extension MyViewController: NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool {
        performSomeActionOnThePressOfABackButton()
        return false
    }
}

Затем создайте класс, скажем NavigationController+BackButton, и просто скопируйте и вставьте код ниже:

protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool
}

extension UINavigationController {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // Prevents from a synchronization issue of popping too many navigation items
        // and not enough view controllers or viceversa from unusual tapping
        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped
        var shouldPop = true
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            shouldPop = viewController.shouldPopOnBackButtonPress()
        }

        if (shouldPop) {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            // Prevent the back button from staying in an disabled state
            for view in navigationBar.subviews {
                if view.alpha < 1.0 {
                    UIView.animate(withDuration: 0.25, animations: {
                        view.alpha = 1.0
                    })
                }
            }

        }

        return false
    }
}
13
kgaidis

По некоторым причинам многопоточности решение, упомянутое @HansPinckaers, мне не подошло, но я нашел очень простой способ поймать прикосновение к кнопке "Назад", и я хочу закрепить это здесь на случай, если это поможет избежать многих часов обмана. кто-нибудь другой. Трюк действительно прост: просто добавьте прозрачную UIButton в качестве подпредставления к вашему UINavigationBar и установите для него селекторы, как если бы это была настоящая кнопка! Вот пример, использующий Monotouch и C #, но перевод в цель-c не должен быть слишком сложным для поиска.

public class Test : UIViewController {
    public override void ViewDidLoad() {
        UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
        b.BackgroundColor = UIColor.Clear; //making the background invisible
        b.Title = string.Empty; // and no need to write anything
        b.TouchDown += delegate {
            Console.WriteLine("caught!");
            if (true) // check what you want here
                NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
        };
        NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
    }
}

Интересный факт: в целях тестирования и чтобы найти подходящие размеры для моей фальшивой кнопки, я установил синий цвет ее фона ... И он показывает сзади кнопку возврата! Во всяком случае, он все еще ловит любое прикосновение, нацеленное на оригинальную кнопку.

5
psycho

Это невозможно сделать напрямую. Есть пара альтернатив:

  1. Создайте свой собственный UIBarButtonItem, который проверяется при нажатии и появляется, если тест пройден
  2. Проверьте содержимое поля формы с помощью метода делегата UITextField, такого как -textFieldShouldReturn: , который вызывается после нажатия кнопки Return или Done на клавиатуре.

Недостатком первого варианта является то, что стиль стрелки влево на кнопке "Назад" не может быть доступен с помощью пользовательской кнопки панели. Таким образом, вы должны использовать изображение или перейти с обычной кнопки стиля.

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

5
Alex Reynolds

Этот метод позволяет изменить текст кнопки "назад", не затрагивая заголовок любого из контроллеров представления или не видя изменения текста кнопки "Назад" во время анимации.

Добавьте это к методу init в контроллере представления вызов:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
3
Jason Moore

Нашел решение, которое сохраняет стиль кнопки назад. Добавьте следующий метод к вашему контроллеру представления.

-(void) overrideBack{

    UIButton *transparentButton = [[UIButton alloc] init];
    [transparentButton setFrame:CGRectMake(0,0, 50, 40)];
    [transparentButton setBackgroundColor:[UIColor clearColor]];
    [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.navigationController.navigationBar addSubview:transparentButton];


}

Теперь предоставьте необходимую функциональность в следующем методе:

-(void)backAction:(UIBarButtonItem *)sender {
    //Your functionality
}

Все, что он делает - это закрывает кнопку назад прозрачной кнопкой;)

3
Sarasranglt

Самый простой способ

Вы можете использовать методы делегата UINavigationController. Метод willShowViewController вызывается, когда нажата кнопка "Назад" вашего VC. Все, что вы хотите, когда нажмете назад btn

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
3
Xar E Ahmer

Есть более простой способ - просто подклассифицировать метод делегатаUINavigationBar и переопределитьShouldPopItemметод.

2
jazzyjef2002

Вот Swift 3 версия ответа @ oneway для отлавливания события кнопки возврата на панели навигации до того, как оно будет запущено. Поскольку UINavigationBarDelegate нельзя использовать для UIViewController, необходимо создать делегат, который будет запускаться при вызове navigationBarshouldPop.

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < (navigationBar.items?.count)! {                
            return true
        }

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

А затем в вашем контроллере представления добавьте функцию делегата:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

Я понял, что мы часто хотим добавить контроллер предупреждений для пользователей, чтобы решить, хотят ли они вернуться. Если это так, вы всегда можете return false в функции navigationShouldPopOnBackButton() и закрыть свой контроллер вида, выполнив что-то вроде этого:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}
2
Lawliet

Я не верю, что это возможно, легко. Единственный способ, которым я верю, чтобы обойти это, состоит в том, чтобы сделать свое собственное изображение стрелки назад, чтобы разместить там. Поначалу это меня расстраивало, но я понимаю, почему, ради последовательности, это не учли.

Вы можете приблизиться (без стрелки), создав обычную кнопку и скрыв кнопку возврата по умолчанию:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
2
Meltemi

Используя Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
2
Murray Sagal

решение onegray небезопасно. Согласно официальным документам Apple, https://developer.Apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html мы следует избегать этого.

"Если имя метода, объявленного в категории, совпадает с именем метода в исходном классе или методом в другой категории в том же классе (или даже в суперклассе), поведение не определено относительно того, какая реализация метода используется во время выполнения. Это менее вероятно, будет проблемой, если вы используете категории со своими собственными классами, но могут вызвать проблемы при использовании категорий для добавления методов в стандартные классы Cocoa или Cocoa Touch. "

2
user2612791

Вот мое решение Swift. В вашем подклассе UIViewController переопределите метод navigationShouldPopOnBackButton.

extension UIViewController {
    func navigationShouldPopOnBackButton() -> Bool {
        return true
    }
}

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}
2
AutomatonTec

Ответ от @William, тем не менее, правильный, если пользователь запускает жест пролистывания, чтобы вызвать метод viewWillDisappear, и даже self не будет в стеке навигации (то есть self.navigationController.viewControllers не будет содержать self), даже если пролистывание не завершено и контроллер вида фактически не вытолкнут. Таким образом, решение будет:

  1. Отключите жест смахивания назад в viewDidAppear и разрешите использование кнопки "Назад" только с помощью:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    
  2. Или просто используйте вместо этого viewDidDisappear, как показано ниже:

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }
    
1
boherna

Этот подход работал для меня (но кнопка "Назад" не будет иметь знак "<"):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}
1
Ivan

Используйте isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
1
herrk

Для формы, которая требует пользовательского ввода, подобного этой, я бы рекомендовал вызывать ее как "модальную" вместо части вашего стека навигации. Таким образом, они должны позаботиться о бизнесе в форме, затем вы можете проверить ее и отклонить с помощью пользовательской кнопки. Вы даже можете создать навигационную панель, которая будет выглядеть так же, как и остальная часть вашего приложения, но даст вам больше контроля.

1
Travis M.

Вы можете попытаться получить доступ к элементу правой кнопки NavigationBars и установить его свойство селектора ... вот ссылка ссылка UIBarButtonItem , еще одна вещь, если эта работа, которая будет работать, - это установить элемент правой кнопки навигации панель для пользовательского UIBarButtonItem, который вы создаете и устанавливаете его селектор ... надеюсь, это поможет

1
Daniel

Чтобы перехватить кнопку "Назад", просто закройте ее прозрачным UIControl и перехватите прикосновения.

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

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

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

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

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end
1
Jeff

Версия Swift 4 для iOS 11.3:

Это основано на ответе от kgaidis от https://stackoverflow.com/a/34343418/4316579

Я не уверен, когда расширение перестало работать, но на момент написания этой статьи (Swift 4) кажется, что расширение больше не будет выполняться, пока вы не объявите соответствие UINavigationBarDelegate, как описано ниже.

Надеюсь, что это помогает людям, которые задаются вопросом, почему их расширение больше не работает.

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

    }
}
1
Edward L.

По крайней мере, в Xcode 5 есть простое и довольно хорошее (не идеальное) решение. В IB перетащите элемент "Панель кнопок" с панели "Утилиты" и поместите его в левой части панели навигации, где будет кнопка "Назад". Установите метку "Назад". У вас будет функциональная кнопка, которую вы можете привязать к вашему IBAction и закрыть свой viewController. Я делаю некоторую работу, а затем запускаю сеанс расслабления, и он работает отлично.

Что не идеально, так это то, что эта кнопка не получает стрелку <и не переносит предыдущий заголовок VC, но я думаю, что этим можно управлять. Для моих целей я установил новую кнопку "Назад" как кнопку "Готово", поэтому ее назначение понятно.

Вы также получите две кнопки "Назад" в навигаторе IB, но для наглядности достаточно легко обозначить их.

enter image description here

1
Dan Loughney

Используя переменные target и action, которые вы в настоящее время оставляете "nil", вы сможете связать свои диалоги сохранения так, чтобы они вызывались, когда кнопка "выбрана". Берегись, это может быть вызвано в странные моменты.

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

1
TahoeWolverine

Swift

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}
1
zono

Найден новый способ сделать это:

Objective-C

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

Swift

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
0
Ashish Kakkad

Решение, которое я нашел до сих пор, не очень хорошее, но оно работает для меня. Принимая это ответ , я также проверяю, высовываю ли я программно или нет:

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

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

Вы должны добавить это свойство к своему контроллеру и установить его в YES, прежде чем выполнить программный запрос:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
0
Ferran Maylinch

Быстрая версия ответа @ onegray

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

Теперь в любом контроллере просто соответствует RequestsNavigationPopVerification, и это поведение принимается по умолчанию.

0
Adam Waite