it-swarm.com.ru

обратный вызов кнопки в навигационном контроллере в iOS

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

96
Namratha

answer Уильям Джокуш решает эту проблему с помощью простого трюка.

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

На мой взгляд лучшее решение.

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

Но это работает только с iOS5 +

82
Blank

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

в viewDidLoad создайте 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 для выполнения действий при нажатии кнопки «Назад».

25
roocell

Я в конечном итоге с этими решениями. По нажатию кнопки назад вызывается метод viewDidDisappear. мы можем проверить, вызвав селектор isMovingFromParentViewController, который возвращает true. мы можем передать данные обратно (используя 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];

    }
}
8
Avijit Nagare

Для «ДО выталкивания представления из стека»:

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

Это правильный способ обнаружить это. 

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

    }
}

этот метод вызывается, когда представление также выдвигается. Поэтому проверка parent == nil предназначена для выталкивания контроллера вида из стека

6
Saad

Есть более подходящий способ, чем спрашивать 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
Carlos Guzman

Вот еще один способ, который я реализовал (не тестировал его с помощью развернутого перехода, но, вероятно, он не будет дифференцироваться, как другие заявляли в отношении других решений на этой странице), чтобы родительский контроллер представления выполнял действия перед дочерним элементом VC это нажатие выталкивается из стека представления (я использовал это на пару уровней ниже от оригинального UINavigationController). Это также может быть использовано для выполнения действий до того, как childVC будет выдвинут. Это дает дополнительное преимущество работы с кнопкой «Назад» в системе iOS, вместо создания пользовательского UIBarButtonItem или UIButton.

  1. Пусть ваш родитель VC примет протокол 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;
    

    }

3
Evan R

Если вы не можете использовать «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
George Harley

Может быть, уже слишком поздно, но я тоже хотел такого же поведения раньше. И решение, с которым я столкнулся, довольно хорошо работает в одном из приложений, которые в настоящее время находятся в 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
    }
}

Вы можете посмотреть мое демо здесь .

 enter image description here

2
yusuke024

Вот что у меня работает в Swift:

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

    super.viewWillDisappear(animated)
}
1
pableiros

Если вы используете раскадровку и переходите из пуш-сегмента, вы также можете просто переопределить shouldPerformSegueWithIdentifier:sender:.

0
Mojo66