зворотний виклик кнопки повернення в навігаційному контролері в iOS


102

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



Оформити замовлення [рішення] [1], яке також зберігає стиль кнопки "назад". [1]: stackoverflow.com/a/29943156/3839641
Sarasranglt

Відповіді:


162

Вільям Джокуші в відповідь вирішити цю проблему з допомогою простого трюку.

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

32
Цей код виконується не лише тоді, коли користувач натискає кнопку "назад", але в кожному випадку вигляд вискакує (наприклад, при натисканні кнопки натискання або збереження праворуч).
значення-справи

7
Або при русі вперед до нового виду.
GuybrushThreepwood

Це також називається, коли користувач переходить з лівого краю (interactivePopGestureRecognizer). У моєму випадку я спеціально шукаю, коли користувач натискає назад, а НЕ панірує з лівого краю.
Кайл Клегг

2
Це не означає, що кнопка повернення була причиною. Можливо, наприклад, розгортається мозок.
посмішкаБот

1
У мене виникають сумніви: Чому ми не повинні зробити це з оглядуDidDisappear?
JohnVanDijk

85

На мій погляд, найкраще рішення.

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

Але він працює лише з iOS5 +


3
Ця методика не може розрізнити між натисканням кнопки "назад" та "розмотанням".
усмішкаБот

Метод willMoveToParentViewController та viewWillDisappear не пояснює, що контролер повинен бути знищений, didMoveToParentViewController правильний
Hank

27

напевно, краще замінити кнопку назад, щоб ви могли обробляти подію до того, як буде відкрито подання для таких речей, як підтвердження користувача.

з урахуваннямDidLoad створити UIBarButtonItem та встановити self.navigationItem.leftBarButtonItem до нього, передаючи в sel

- (void) viewDidLoad
{
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”back
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];

self.navigationItem.leftBarButtonItem = backButton;
[backButton release];

}
- (void) handleBack:(id)sender
{
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];

}

Тоді ви можете виконати такі дії, як підняти UIAlertView для підтвердження дії, а потім відкрити контролер перегляду тощо.

Або замість того, щоб створити нову кнопку назад, ви можете відповідати методам делегування UINavigationController для виконання дій при натисканні кнопки "назад".


UINavigationControllerDelegateНе мають методи, які викликаються , коли спина кнопка прослуховуються.
значення-справи

Ця методика дозволяє перевірити дані контролера перегляду та умовне повернення за допомогою кнопки "назад" навігаційного контролера.
gjpc

Це рішення порушує функцію
перенесення

9

Це правильний спосіб виявити це.

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        //do stuff

    }
}

цей метод називається при натисканні на перегляд. Тож перевірка батьківського == нуля призначена для вискакування контролера перегляду зі стека


9

Я закінчую ці рішення. Коли ми натискаємо кнопку назад, називається метод viewDidDisappear. ми можемо перевірити, зателефонувавши на селектор isMovingFromParentViewController, який повертає істину. ми можемо передати дані назад (за допомогою Delegate).

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

    }
    if (self.isMovingFromParentViewController) {
       //moving back
        //pass to viewCollection delegate and update UI
        [self.delegateObject passBackSavedData:self.dataModel];

    }
}

Не забувайте[super viewDidDisappear:animated]
SamB

9

Можливо, трохи пізно, але я теж хотів такої ж поведінки і раніше. І рішення, з яким я працював, працює досить добре в одному з додатків, які зараз перебувають у App Store. Оскільки я не бачив, щоб хтось ходив подібним методом, я хотів би поділитися цим тут. Мінусом цього рішення є те, що воно вимагає підкласифікації UINavigationController. Хоча використання методу Swizzling може допомогти уникнути цього, я не пішов так далеко.

Отже, кнопкою повернення за замовчуванням насправді керує UINavigationBar. Коли користувач натискає кнопку "Назад", UINavigationBarзапитайте свого делегата, чи повинен він звертатися до вершини UINavigationItem, зателефонувавши navigationBar(_:shouldPop:). UINavigationControllerнасправді це реалізовують, але він публічно не заявляє, що приймає UINavigationBarDelegate(чому !?). Щоб перехопити цю подію, створіть підклас UINavigationController, заявіть про його відповідність UINavigationBarDelegateта реалізуйте navigationBar(_:shouldPop:). Повертайтеся, trueякщо верхній предмет повинен вискочити. Поверненняfalse якщо воно залишиться.

Є дві проблеми. Перший полягає в тому, що ви повинні зателефонувати у UINavigationControllerверсію програми navigationBar(_:shouldPop:)в якийсь момент. Але UINavigationBarControllerпублічно не заявляє про його відповідністьUINavigationBarDelegate , спроба викликати його призведе до помилки часу компіляції. Рішення, з яким я пішов, - це використовувати час виконання Objective-C, щоб отримати реалізацію безпосередньо та викликати її. Будь ласка, дайте мені знати, чи є хтось кращого рішення.

Інша проблема полягає в тому, що navigationBar(_:shouldPop:)викликається спочатку, після чого popViewController(animated:)користувач натискає кнопку назад. Порядок змінюється, якщо контролер перегляду вискакує, зателефонувавши popViewController(animated:). У цьому випадку я використовую булевий сигнал, щоб виявити, чи popViewController(animated:)викликається раніше, navigationBar(_:shouldPop:)що означає, що користувач натиснув кнопку "назад".

Крім того, я роблю розширення, UIViewControllerщоб дозволити контролеру навігації запитати контролер перегляду, чи слід його вискакувати, якщо користувач натискає кнопку назад. Контролери перегляду можуть повертатися falseта виконувати будь-які необхідні дії та дзвонити popViewController(animated:)пізніше.

class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
    // If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
    // If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
    private var didCallPopViewController = false

    override func popViewController(animated: Bool) -> UIViewController? {
        didCallPopViewController = true
        return super.popViewController(animated: animated)
    }

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
        if didCallPopViewController {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        }

        // The following code is called only when the user taps on the back button.

        guard let vc = topViewController, item == vc.navigationItem else {
            return false
        }

        if vc.shouldBePopped(self) {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        } else {
            return false
        }
    }

    func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
        didCallPopViewController = false
    }

    /// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
    /// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
    /// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
    private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
        let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
        typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
        let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
        return shouldPop(self, sel, navigationBar, item)
    }
}

extension UIViewController {
    @objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        return true
    }
}

А у вас перегляд контролерів, реалізуйте shouldBePopped(_:). Якщо ви не реалізуєте цей метод, поведінка за замовчуванням полягає в тому, щоб відкрити контролер перегляду, як тільки користувач натискає кнопку назад, як звичайне.

class MyViewController: UIViewController {
    override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        let alert = UIAlertController(title: "Do you want to go back?",
                                      message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
            navigationController.popViewController(animated: true)
        }))
        present(alert, animated: true, completion: nil)
        return false
    }
}

Ви можете подивитися мою демонстрацію тут .

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


Це приголомшливе рішення, і його слід запекти в блозі! Здається, непосильним є те, що я зараз шукаю, але за інших обставин це, безумовно, варто спробувати.
ASSeeger

6

Для "ДО СПОСІБУВАННЯ перегляду зі стека":

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}

5

Існує більш підходящий спосіб, ніж запитання у viewControllers. Ви можете зробити свій контролер делегатом навігаційної панелі, яка має кнопку "назад". Ось приклад. Під час реалізації контролера, де потрібно обробляти натискання кнопки "назад", скажіть йому, що він реалізує протокол UINavigationBarDelegate:

@interface MyViewController () <UINavigationBarDelegate>

Потім десь у коді ініціалізації (можливо, у viewDidLoad) зробіть контролеру делегатом його панелі навігації:

self.navigationController.navigationBar.delegate = self;

Нарешті, застосуйте метод shouldPopItem. Цей метод викликається правильно, коли натискається кнопка "назад". Якщо у стеці є декілька контролерів або елементів навігації, ви, ймовірно, захочете перевірити, який із цих елементів навігації вискакує (параметр елемента), так що ви можете робити свої власні речі лише тоді, коли цього плануєте. Ось приклад:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}

4
не працювало для мене .. пітне, тому що воно худорляве. "*** Закінчення програми через невиконаний виняток" NSInternalInconsistencyException ", причина:" Не вдається вручну встановити делегата на UINavigationBar, керований контролером. ""
DynamicDan

На жаль, це не працюватиме з UINavigationController, натомість вам потрібен стандартний UIViewController з UINavigationBar в ньому. Це означає, що ви не можете скористатися декількома автоматичними кнопками та натисканнями, які вам дає NavigationController. Вибачте!
Карлос Гузман

Я щойно використав UINavigationBar замість NavigationBarController, і тоді він прекрасно працює. Я знаю, що питання щодо NavigationBarController, але це рішення є худорлявим.
appsunited

3

Якщо ви не можете використовувати "viewWillDisappear" або подібний метод, спробуйте підкласировать UINavigationController. Це клас заголовка:

#import <Foundation/Foundation.h>
@class MyViewController;

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Клас реалізації:

#import "CCNavigationController.h"
#import "MyViewController.h"

@implementation CCNavigationController {

}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    @"This is the moment for you to do whatever you want"
    [self.viewController doCustomMethod];
    return [super popViewControllerAnimated:animated];
}

@end

З іншого боку, вам потрібно зв'язати цей viewController зі своїм користувальницьким NavigationController, тому у вашому методі viewDidLoad для вашого звичайного viewController зробіть це:

@implementation MyViewController {
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        ((CCNavigationController*)self.navigationController).viewController = self;
    }
}

3

Ось ще один спосіб, який я реалізував (не перевіряв його за допомогою розмотуваного шва, але він, ймовірно, не відрізнятиметься, як заявили інші щодо інших рішень на цій сторінці), щоб змусити контролер подання батьків виконувати дії перед дочірнім ПК, на який він натиснув вискакує стек перегляду (я використав це на пару рівнів від початкового UINavigationController). Це також може бути використане для виконання дій, перш ніж childVC натисне. Це має додаткову перевагу роботи з кнопкою повернення системи iOS, замість того, щоб створювати власні UIBarButtonItem або UIButton.

  1. Попросіть вашого батьківського контролера прийняти UINavigationControllerDelegateпротокол та зареєструватися для делегатних повідомлень:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. Реалізуйте цей UINavigationControllerDelegateпримірник у MyParentViewController:

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        // Test if operation is a pop; can also test for a push (i.e., do something before the ChildVC is pushed
        if (operation == UINavigationControllerOperationPop) {
            // Make sure it's the child class you're looking for
            if ([fromVC isKindOfClass:[ChildViewController class]]) {
                // Can handle logic here or send to another method; can also access all properties of child VC at this time
                return [self didPressBackButtonOnChildViewControllerVC:fromVC];
            }
        }
        // If you don't want to specify a nav controller transition
        return nil;
    }
  3. Якщо ви вказали певну функцію зворотного виклику у вищевказаному UINavigationControllerDelegateметоді

    -(id <UIViewControllerAnimatedTransitioning>)didPressBackButtonOnAddSearchRegionsVC:(UIViewController *)fromVC {
        ChildViewController *childVC = ChildViewController.new;
        childVC = (ChildViewController *)fromVC;
    
        // childVC.propertiesIWantToAccess go here
    
        // If you don't want to specify a nav controller transition
        return nil;

    }


1

Це те, що мені працює у Swift:

override func viewWillDisappear(_ animated: Bool) {
    if self.navigationController?.viewControllers.index(of: self) == nil {
        // back button pressed or back gesture performed
    }

    super.viewWillDisappear(animated)
}

0

Якщо ви використовуєте дошку розкадрувань і ви приїжджаєте з натискання на поштовх, ви також можете просто перекрити shouldPerformSegueWithIdentifier:sender:.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.