Обнаружение, когда кнопка "назад" нажата на навигационной панели
Мне нужно выполнить некоторые действия, когда кнопка "Назад" (возврат к предыдущему экрану, возврат к родительскому виду) нажата на навигационной панели.
Есть ли какой-нибудь метод, который я могу реализовать, чтобы поймать событие и отключить некоторые действия для приостановки и сохранения данных до исчезновения экрана?
Ответы
Ответ 1
ОБНОВЛЕНИЕ: Согласно некоторым комментариям, решение в первоначальном ответе, похоже, не работает при определенных сценариях в iOS 8+. Я не могу подтвердить, что это действительно так, без дальнейших подробностей.
Для тех из вас, однако, в этой ситуации есть альтернатива. Обнаружение willMove(toParentViewController:)
контроллера представления возможно путем переопределения willMove(toParentViewController:)
. Основная идея заключается в том, что контроллер представления выталкивается, когда parent
равен nil
.
Проверьте "Реализация Контроллера Контейнера Представления" для получения дополнительной информации.
Начиная с iOS 5, я обнаружил, что самый простой способ справиться с этой ситуацией - использовать новый метод - (BOOL)isMovingFromParentViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
имеет смысл, когда вы - (BOOL)isMovingFromParentViewController
контроллеры в стеке навигации.
Однако, если вы представляете контроллеры модального представления, вы должны использовать вместо этого - (BOOL)isBeingDismissed
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
Как отмечено в этом вопросе, вы можете объединить оба свойства:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
Другие решения основаны на существовании UINavigationBar
. Вместо этого мне больше нравится мой подход, потому что он отделяет требуемые задачи от действия, которое вызвало событие, то есть нажатие кнопки возврата.
Ответ 2
Пока кнопки viewWillAppear()
и viewDidDisappear()
вызываются при нажатии кнопки "Назад", их также вызывают в другое время. Подробнее см. В конце ответа.
Использование UIViewController.parent
Обнаружение кнопки "Назад" лучше сделать, когда VC будет удален из нее родительским (NavigationController) с помощью willMoveToParentViewController(_:)
ИЛИ didMoveToParentViewController()
Если родительский элемент равен нулю, контроллер просмотра выгружается из навигационного стека и отклоняется. Если parent не равен нулю, он добавляется в стек и отображается.
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController:parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
Поменяйте willMove
на didMove
и убедитесь, что self.parent выполняет работу после отклонения контроллера вида.
Остановка увольнения
Обратите внимание, что проверка родителя не позволяет вам "приостановить" переход, если вам нужно выполнить какое-то асинхронное сохранение. Для этого вы можете реализовать следующее. Единственным недостатком здесь является то, что вы теряете причудливую кнопку стилизованной/анимированной кнопки iOS. Также будьте осторожны с интерактивным жестом салфетки. Для обработки этого случая используйте следующее.
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
Больше на просмотр будет/показалось бы
Если вы не получили проблему viewWillAppear
viewDidDisappear
, пропустите пример. Скажем, у вас есть три контроллера вида:
- ListVC: табличный вид вещей
- DetailVC: информация о вещи
- SettingsVC: некоторые опции для вещи
Позволяет выполнять вызовы на detailVC
по мере перехода от listVC
в settingsVC
и обратно к listVC
Список > Подробно (нажмите detailVC) Detail.viewDidAppear
< - появится
Подробно > Настройки (нажмите настройкиVC) Detail.viewDidDisappear
< - исчезнуть
И как мы вернемся...
Настройки > Подробно (pop settingsVC) Detail.viewDidAppear
< - появится
Подробно > Список (pop detailVC) Detail.viewDidDisappear
< - исчезать
Обратите внимание, что viewDidDisappear
вызывается несколько раз, причем не только при возврате, но и при переходе вперед. Для быстрой операции, которая может потребоваться, но для более сложной операции, такой как сетевой вызов для сохранения, это может быть не так.
Ответ 3
Первый метод
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
Второй метод
-(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];
}
Ответ 4
Я играю (или борюсь) с этой проблемой в течение двух дней. ИМО лучшим подходом является создание класса расширения и протокола, например:
@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
@end
@interface UINavigationController(BackButtonHandler)
@end
@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = @selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
@end
Это работает, потому что UINavigationController
будет получать вызов navigationBar:shouldPopItem:
каждый раз, когда вызывается контроллер вида. Там мы обнаруживаем, была ли нажата или нет (любая другая кнопка).
Единственное, что вам нужно сделать, это реализовать протокол в контроллере представления, где нажата кнопка.
Не забудьте вручную вывести контроллер представления внутри backButtonPressedSel
, если все в порядке.
Если у вас уже есть подклассы UINavigationViewController
и реализовано navigationBar:shouldPopItem:
, не беспокойтесь, это не помешает ему.
Вы также можете быть заинтересованы в отключении жест назад.
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
Ответ 5
Это работает для меня в iOS 9.3.x с Swift:
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
В отличие от других решений здесь это не кажется неожиданным.
Ответ 6
Для записи я думаю, что это больше того, что он искал...
UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];
self.navigationItem.leftBarButtonItem = l_backButton;
- (void) backToRootView:(id)sender {
// Perform some custom code
[self.navigationController popToRootViewControllerAnimated:YES];
}
Ответ 7
Те, кто утверждает, что это не работает, ошибаются:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
Это отлично работает. Так в чем же причина распространенного мифа?
Кажется, проблема связана с неправильной реализацией другого метода, а именно с тем, что реализация willMove(toParent:)
забыла вызвать super
.
Если вы реализуете willMove(toParent:)
без вызова super
, то self.isMovingFromParent
будет иметь значение false
и использование viewWillDisappear
окажется неудачным. Это не подвело; ты сломал это.
Ответ 8
Как говорит purrrminator
, ответ elitalon
не совсем прав, так как your stuff
будет выполняться даже при программном программировании контроллера.
Решение, которое я нашел до сих пор, не очень хорошо, но оно работает для меня. Кроме того, что сказал elitalon
, я также проверяю, что я выскакиваю программно или нет:
- (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];
Спасибо за вашу помощь!
Ответ 9
Лучший способ - использовать методы делегирования UINavigationController
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
С помощью этого вы можете узнать, какой контроллер показывает UINavigationController.
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}
Ответ 10
7ynk3r ответ был действительно близок к тому, что я использовал в конце, но он нуждался в некоторых настройках:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:@selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}
Ответ 11
Вы должны проверить UINavigationBarDelegate Protocol.
В этом случае вы можете использовать метод navigationBar: shouldPopItem:.
Ответ 12
Как сказал Coli88, вы должны проверить протокол UINavigationBarDelegate.
В более общем виде вы также можете использовать - (void)viewWillDisapear:(BOOL)animated
для выполнения пользовательской работы, когда вид, сохраненный текущим видимым контроллером представления, вот-вот исчезнет. К сожалению, это будет беспокоить толкание и всплывающие окна.
Ответ 13
Для Swift с помощью UINavigationController:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
Ответ 14
Я решил эту проблему, добавив UIControl в navigationBar с левой стороны.
UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];
И вам нужно помнить, чтобы удалить его, когда вид исчезнет:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
Что все!
Ответ 15
Вы можете использовать обратный вызов кнопки "Назад", например:
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
для быстрой версии вы можете сделать что-то в вашем viewcontroller, как
extension UIViewController {
@objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}
Ниже того, что вы поместили в viewcontroller, где вы хотите контролировать действие кнопки назад:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}
Ответ 16
self.navigationController.isMovingFromParentViewController больше не работает на iOS8 и 9, я использую:
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}
Ответ 17
(СВИФТ)
finaly found solution.. метод, который мы искали, - это "willShowViewController", который является методом делегирования UINavigationController
//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
//set delegate to current class (self)
navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
//MyViewController shoud be the name of your parent Class
if var myViewController = viewController as? MyViewController {
//YOUR STUFF
}
}
}