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

180

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

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

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

self.navigationItem.leftBarButtonItem = backButton;

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

Попугаи
источник
Действие: @selector (дом)]; необходимо: после действия селектора: @selector (home :)]; иначе это не будет работать
PartySoft
7
@PartySoft Это не так, если метод не объявлен с двоеточием. Совершенно верно иметь кнопки селекторов вызова, которые не принимают никаких параметров.
mbm29414
возможный дубликат прекращения выхода из представления
self.navigationItem.leftBarButtonItem
3
Почему бы Apple не предоставить кнопку со стилем в форме кнопки возврата? Кажется довольно очевидным.
JohnK
Посмотрите на решение в этой теме
Иржи Волейник

Ответы:

363

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

-(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];
}
Уильям Джокуш
источник
1
это гладкий, чистый, красивый и очень продуманный обходной путь
boliva
6
+1 отличный хак, но не предлагает контроль над анимацией поп
мат
3
У меня не работает, если я посылаю сообщение делегату через кнопку, а делегат выдает контроллер - это все еще срабатывает.
SAHM
21
Другая проблема заключается в том, что вы не можете различить, нажал ли пользователь кнопку назад или программно вызвал [self.navigationController popViewControllerAnimated: YES]
Чейз Робертс
10
Просто к вашему сведению: Swift версия:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH
177

Я реализовал расширение UIViewController-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
}

Загрузите пример приложения .

onegray
источник
16
Это самое чистое решение, которое я видел, лучше и проще, чем использование собственной пользовательской UIButton. Спасибо!
ramirogm
4
Это navigationBar:shouldPopItem:не приватный метод, поскольку он является частью UINavigationBarDelegateпротокола.
онегр
1
но UINavigationController уже реализует делегат (для возврата YES)? или это будет в будущем? подклассы, вероятно, более безопасный вариант
Сэм
8
Я только что реализовал это (довольно круто, кстати) в iOS 7.1 и заметил, что после возврата NOкнопка возврата остается в отключенном состоянии (визуально, потому что она все еще получает и реагирует на сенсорные события). Я обошел его, добавив elseоператор к shouldPopпроверке и циклически просматривая подпредставления навигационной панели, и установив alphaзначение обратно на 1, если это необходимо, внутри блока анимации: gist.github.com/idevsoftware/9754057
boliva
2
Это одно из лучших расширений, которое я когда-либо видел. Огромное спасибо.
Срикант
42

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

HansPinckaers
источник
В документации Apple ( developer.apple.com/iphone/library/documentation/UIKit/… ) говорится, что «этот класс не предназначен для создания подклассов». Хотя я не уверен, что они имеют в виду под этим - они могут означать «вам обычно не нужно этого делать», или они могут означать «мы отклоним ваше приложение, если вы возитесь с нашим контроллером» ...
Куба Судер
Это, безусловно, единственный способ сделать это. Жаль, что я не мог бы наградить вас больше очков, Ганс!
Адам Эбербах
1
Можете ли вы на самом деле предотвратить выход из вида, используя этот метод? Что бы вы сделали методом popViewControllerAnimated, если бы вы хотели, чтобы представление не закрывалось?
JosephH
1
Да, вы можете. Только не вызывайте метод суперкласса в вашей реализации, будьте внимательны! Вы не должны этого делать, пользователь ожидает вернуться в навигацию. Что вы можете сделать, это попросить подтверждение. Согласно документации Apple, popViewController возвращает: «Контроллер представления, извлеченный из стека». Поэтому, когда ничего не появляется, вы должны вернуть ноль;
HansPinckaers
1
@HansPickaers Я думаю, что ваш ответ о запрете выхода из представления может быть несколько неправильным. Если я отображаю сообщение «Подтвердить» из реализации подклассов popViewControllerAnimated :, панель навигации все равно оживляет один уровень в дереве независимо от того, что я возвращаю. Похоже, это происходит потому, что нажатие кнопки «Назад» вызывает beforePopNavigationItem на панели навигации. Я возвращаю ноль из моего метода подклассов, как рекомендовано.
Глубокая зима
15

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
    }
}
kgaidis
источник
Может быть, я что-то пропустил, но у меня это не работает, метод executeSomeActionOnThePressOfABackButton никогда не вызывается
Turvy
@FlorentBreton может быть недоразумение? shouldPopOnBackButtonPressдолжен вызываться, пока нет ошибок. performSomeActionOnThePressOfABackButtonэто просто выдуманный метод, который не существует.
kgaidis
Я понял это, поэтому я создал метод performSomeActionOnThePressOfABackButtonв моем контроллере для выполнения определенного действия при нажатии кнопки «Назад», но этот метод никогда не вызывался, действие является нормальным возвратом назад
Turvy
1
Не работает для меня тоже. Метод shouldPop никогда не вызывается. Вы где-нибудь выставили делегата?
Тим Отин
@TimAutin Я только что проверил это снова и похоже что-то изменилось. Ключевая часть, чтобы понять, что в UINavigationController, navigationBar.delegateустановлен на контроллер навигации. Так что методы ДОЛЖНЫ быть вызваны. Однако в Swift я не могу заставить их вызываться даже в подклассе. Однако я сделал так, чтобы они вызывались в Objective-C, поэтому сейчас я просто использую версию Objective-C. Может быть, Быстрая ошибка.
kgaidis
5

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

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

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

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

Алекс Рейнольдс
источник
5

По некоторым причинам многопоточности решение, упомянутое @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
    }
}

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

псих
источник
3

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

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

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
Джейсон Мур
источник
3

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

Вы можете использовать методы делегата UINavigationController. Метод willShowViewControllerвызывается, когда нажата кнопка «Назад» вашего ВК.

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
Zar E Ahmer
источник
Убедитесь, что ваш контроллер представления устанавливает себя в качестве делегата унаследованного навигационного контроллера и соответствует протоколу UINavigationControllerDelegate
Джастин Мило
3

Вот мое решение 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
    }

}
AutomatonTec
источник
Переопределение метода navigationShouldPopOnBackButton в UIViewController не работает - программа выполняет родительский метод, а не переопределенный. Любое решение для этого? У кого-нибудь есть такая же проблема?
Павел Кала
он возвращается к rootview, если возвращает true
Pawriwes
@Pawriwes Вот решение, которое я написал, которое, кажется, работает для меня: stackoverflow.com/a/34343418/826435
kgaidis
3

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

-(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
}

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

Sarasranglt
источник
3

Переопределение navigationBar (_ navigationBar: shouldPop) : это не очень хорошая идея, даже если она работает. для меня это вызвало случайные сбои при переходе назад. Я советую вам просто переопределить кнопку возврата, удалив заднюю кнопку backButton из navigationItem и создав пользовательскую кнопку возврата, как показано ниже:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

========================================

Опираясь на предыдущие ответы с UIAlert в Swift5 в асинхронном режиме


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}

На вашем контроллере


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}
brahimm
источник
Можете ли вы сообщить подробности о том, что происходит с проверкой if viewControllers.count <navigationBar.items! .Count {return true}, пожалуйста?
H4Hugo
// Предотвращает из-за проблемы синхронизации выскочить слишком много элементов навигации // и недостаточно контроллеров представления или наоборот от необычного постукивания
brahimm
2

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

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

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
Мельтеми
источник
2
Да, проблема в том, что я хочу, чтобы она выглядела как обычная кнопка «назад», просто нужно, чтобы она сначала
вызывала
2

Там более простой способ, просто наследование метода делегата из UINavigationBarи переопределить в ShouldPopItemметоде .

jazzyjef2002
источник
Я думаю, что вы хотите сказать подкласс класса UINavigationController и реализовать метод shouldPopItem. Это хорошо работает для меня. Однако этот метод не должен просто возвращать YES или NO, как вы ожидаете. Объяснение и решение доступно здесь: stackoverflow.com/a/7453933/462162
arlomedia
2

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

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

user2612791
источник
2

Используя Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
Мюррей Сагал
источник
Начиная с iOS 10, а может и раньше, это больше не работает.
Мюррей Сагал
2

Вот версия Swon 3 ответа @oneway для отлавливания события кнопки возврата на панели навигации до того, как оно будет запущено. Как UINavigationBarDelegateнельзя использовать UIViewController, вам нужно создать делегат, который будет запускаться при navigationBar shouldPopвызове.

@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")       
}
Lawliet
источник
Я получаю сообщение об ошибке: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' когда я пытаюсь скомпилировать ваш код, для строки if vc.responds(to: #selector(v...также self.topViewControllerвозвращается необязательный параметр, и для этого также есть предупреждение.
Санкар
FWIW, я исправил этот код, сделав: let vc = self.topViewController as! MyViewControllerи пока он работает нормально. Если вы считаете, что это правильное изменение, вы можете отредактировать код. Также, если вы чувствуете, что этого не следует делать, я буду рад узнать, почему. Спасибо за этот код. Вы, вероятно, должны написать сообщение в блоге об этом, так как этот ответ похоронен согласно голосам.
Санкар
@SankarP Причина, по которой вы получили эту ошибку, заключается в том, что вы, MyViewControllerвозможно, не соответствуете BackButtonDelegate. Вместо того, чтобы принудительно разворачивать, вы должны сделать, guard let vc = self.topViewController as? MyViewController else { return true }чтобы избежать возможного сбоя.
Lawliet
Спасибо. Я думаю, что защитное заявление должно стать следующим: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }убедиться, что экран перемещается на нужную страницу в случае, если его нельзя правильно воспроизвести. Теперь я понимаю, что navigationBarфункция вызывается во всех VC, а не только в viewcontroller, где этот код существует. Может быть, будет полезно обновить код и в вашем ответе? Спасибо.
Санкар
2

Версия 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

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

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

TahoeWolverine
источник
Я согласен, что вы могли бы сделать фотошоп, и я думаю, что я мог бы сделать это, если бы я действительно этого хотел, но теперь решил немного изменить внешний вид и почувствовать, чтобы это работало так, как я хочу.
Джон Баллинджер
Да, за исключением того, что действия не запускаются, когда они присоединены к backBarButtonItem. Я не знаю, это ошибка или особенность; Возможно, что даже Apple не знает. Что касается упражнения по фотосъемке, опять же, я бы опасался, что Apple отклонит приложение за неправильное использование канонического символа.
Amagrammer
Heads-up: этот ответ был объединен с дубликатом.
Shog9
1

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

Даниил
источник
Heads-up: этот ответ был объединен с дубликатом.
Shog9
1

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

Трэвис М.
источник
Heads-up: этот ответ был объединен с дубликатом.
Shog9
1

Чтобы перехватить кнопку «Назад», просто закройте ее прозрачным 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
Джефф
источник
Heads-up: этот ответ был объединен с дубликатом.
Shog9
1

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

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

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

введите описание изображения здесь

Дэн Лафни
источник
1

стриж

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
}
Zono
источник
1

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

- (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

Быстрая версия ответа @ 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и это поведение принимается по умолчанию.

Адам Уэйт
источник
1

использование isMovingFromParentViewController

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

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
herrk
источник
Не могли бы вы объяснить, как это доказывает, что кнопка «назад» была нажата?
Мюррей Сагал
Это просто, но работает, только если вы уверены, что вернетесь к этому представлению из любого дочернего представления, которое вы можете загрузить. Если дочерний элемент пропускает это представление при возвращении к родителю, ваш код не будет вызываться (представление уже исчезло, не переместившись из родительского объекта). Но это та же проблема с обработкой только событий на триггере кнопки «Назад», как это было задано ОП. Так что это простой ответ на его вопрос.
CMont
Это супер просто и элегантно. Я люблю это. Только одна проблема: это также сработает, если пользователь проведет пальцем назад, даже если он отменит на полпути. Возможно, лучшим решением было бы поместить этот код в viewDidDisappear. Таким образом, он будет срабатывать только после того, как вид окончательно исчезнет.
Фонтейн Джадд
1

Ответ от @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.
        }
    }
boherna
источник
0

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

- (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

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

Objective-C

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

стриж

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
Ашиш Каккад
источник