viewWillDisappear: Определите, выталкивается ли контроллер представления или показывает контроллер подвида

134

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

На данный момент я устанавливаю такие флаги, как, isShowingChildViewControllerно это становится довольно сложно. Единственный способ, которым я могу обнаружить это, в -deallocметоде.

Майкл Водопад
источник

Ответы:

228

Вы можете использовать следующее.

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  NSArray *viewControllers = self.navigationController.viewControllers;
  if (viewControllers.count > 1 && [viewControllers objectAtIndex:viewControllers.count-2] == self) {
    // View is disappearing because a new view controller was pushed onto the stack
    NSLog(@"New view controller was pushed");
  } else if ([viewControllers indexOfObject:self] == NSNotFound) {
    // View is disappearing because it was popped from the stack
    NSLog(@"View controller was popped");
  }
}

Это, конечно, возможно, потому что стек контроллера представления UINavigationController (предоставляемый через свойство viewControllers) был обновлен к моменту вызова viewWillDisappear.

Брайан Генри
источник
2
Отлично! Я не знаю, почему я не подумал об этом! Думаю, я не думал, что стек будет изменен, пока не будут вызваны исчезающие методы! Спасибо :-)
Водопад Майкл
1
Я только что пытался выполнить то же самое, но внутри, viewWillAppearи может показаться, что независимо от того, обнаруживается ли контроллер представления нажатием или что-то над ним выталкивается, массив viewControllers одинаков в обоих направлениях! Любые идеи?
Водопад Майкл
Я также должен отметить, что контроллер представления является постоянным на протяжении всей жизни приложения, поэтому я не могу выполнять свои действия, так viewDidLoadкак он вызывается только один раз! Хм, хитрый!
Водопад Майкл
4
@Sbrocket, есть ли причина, по которой ты не сделал ![viewControllers containsObject:self]вместо [viewControllers indexOfObject:self] == NSNotFound? Выбор стиля?
Zekel
24
Этот ответ устарел начиная с iOS 5. -isMovingFromParentViewControllerУпомянутый ниже метод позволяет вам проверить, явно ли выскочило представление.
grahamparks
136

Я думаю, что самый простой способ это:

 - (void)viewWillDisappear:(BOOL)animated
{
    if ([self isMovingFromParentViewController])
    {
        NSLog(@"View controller was popped");
    }
    else
    {
        NSLog(@"New view controller was pushed");
    }
    [super viewWillDisappear:animated];
}

Swift:

override func viewWillDisappear(animated: Bool)
{
    if isMovingFromParent
    {
        print("View controller was popped")
    }
    else
    {
        print("New view controller was pushed")
    }
    super.viewWillDisappear(animated)
}
RTasche
источник
Что касается iOS 5, это ответ, может быть, также проверьте isBeingDismissed
d370urn3ur
4
Для iOS7 я должен проверить [self.navigationController.viewControllers indexOfObject: self] == NSNotFound еще раз, потому что фоновое приложение также пройдет этот тест, но не удалит self из стека навигации.
Эрик Чен,
3
Apple предоставила документированный способ сделать это - stackoverflow.com/a/33478133/385708
Шиам Бхат
Проблема с использованием viewWillDisappear состоит в том, что возможно, что контроллер извлечен из стека, в то время как представление уже исчезло. Например, другой viewcontroller мог быть помещен сверху стека и затем вызвать popToRootViewControllerAnimated, обходя viewWillDisappear на тех в середине.
Джон К
Предположим, у вас есть два контроллера (root vc и еще один выдвинутый) в вашем стеке навигации. Когда третье нажатие, viewWillDisappear вызывается для второго, чье представление исчезнет, ​​верно? Поэтому, когда вы переходите к корневому контроллеру представления (извлекаете третий и второй), viewWillDisappear вызывается на третьем, то есть на последнем vc в стеке, потому что его представление находится сверху и собирается исчезнуть в это время, а второе представление уже исчезло. Вот почему этот метод называется viewWillDisappear, а не viewControllerWillBePopped.
RTasche
61

Из документации Apple в UIViewController.h:

«Эти четыре метода могут использоваться в обратных вызовах внешнего вида контроллера представления, чтобы определить, будет ли он представлен, отклонен, добавлен или удален как дочерний контроллер представления. Например, контроллер представления может проверить, исчезает ли он, потому что он был отклонен или выскочил, спросив себя в своем viewWillDisappear: метод, проверив выражение ([self isBeingDismissed] || [self isMovingFromParentViewController]). "

- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);

- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);

- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);

- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);

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

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if ([self isBeingDismissed] || [self isMovingFromParentViewController]) {
    }
}

Версия Swift 3:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if self.isBeingDismissed || self.isMovingFromParentViewController { 
    }
}
Шьям Бхат
источник
19

Если вы просто хотите знать , является ли получение совало ваше мнение, я только что обнаружил , что self.navigationControllerнаходится nilв viewDidDisappear, когда он удаляется из стека контроллеров. Так что это простой альтернативный тест.

(Это я обнаружил после того, как попробовал все виды других искажений. Я удивлен, что нет протокола контроллера навигации, чтобы зарегистрировать контроллер представления, чтобы получать уведомления о всплывающих окнах. Вы не можете использовать, UINavigationControllerDelegateпотому что на самом деле это действительно работает.)

дк.
источник
16

Swift 4

override func viewWillDisappear(_ animated: Bool)
    {
        super.viewWillDisappear(animated)
        if self.isMovingFromParent
        {
            //View Controller Popped
        }
        else
        {
            //New view controller pushed
        }
    }
Umair
источник
6

В Свифте:

 override func viewWillDisappear(animated: Bool) {
    if let navigationController = self.navigationController {
        if !contains(navigationController.viewControllers as! Array<UIViewController>, self) {
        }
    }

    super.viewWillDisappear(animated)

}
user754905
источник
Обязательно используйте как! а не как
dfmuir
2

Я нахожу документацию Apple об этом трудной для понимания. Это расширение помогает видеть состояния при каждой навигации.

extension UIViewController {
    public func printTransitionStates() {
        print("isBeingPresented=\(isBeingPresented)")
        print("isBeingDismissed=\(isBeingDismissed)")
        print("isMovingToParentViewController=\(isMovingToParentViewController)")
        print("isMovingFromParentViewController=\(isMovingFromParentViewController)")
    }
}
Норман
источник
1

Этот вопрос довольно старый, но я увидел его случайно, поэтому я хочу опубликовать лучшие практики (afaik)

ты можешь просто сделать

if([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
 // view controller popped
}
user1396236
источник
1

Это относится к iOS7 , не знаю, применимо ли это к любым другим. Из того, что я знаю, viewDidDisappearпо мнению уже было совать. Это означает, что когда вы запросите, self.navigationController.viewControllersвы получите nil. Так что просто проверьте, если это ноль.

TL; DR

 - (void)viewDidDisappear:(BOOL)animated
 {
    [super viewDidDisappear:animated];
    if (self.navigationController.viewControllers == nil) {
        // It has been popped!
        NSLog(@"Popped and Gone");
    }
 }
Байт
источник
1

Segues может быть очень эффективным способом решения этой проблемы в iOS 6+. Если вы указали конкретный идентификатор в Интерфейсном Разработчике, вы можете проверить его в prepareForSegue.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"LoginSegue"]) {
       NSLog(@"Push");
       // Do something specific here, or set a BOOL indicating
       // a push has occurred that will be checked later
    }
}
Кайл Клегг
источник
1

Спасибо @Bryan Генри, все еще работает в Swift 5

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if let controllers = navigationController?.children{
            if controllers.count > 1, controllers[controllers.count - 2] == self{
                // View is disappearing because a new view controller was pushed onto the stack
                print("New view controller was pushed")
            }
            else if controllers.firstIndex(of: self) == nil{
                // View is disappearing because it was popped from the stack
                print("View controller was popped")
            }
        }

    }
dengST30
источник
-1

Я предполагаю, что вы имеете в виду, что ваше представление перемещается вниз по стеку контроллера навигации путем нажатия нового представления, когда вы говорите, что помещено в стек. Я бы предложил использовать viewDidUnloadметод для добавления NSLogоператора, чтобы записать что-то на консоль, чтобы вы могли видеть, что происходит, вы можете добавить NSLogк viewWillDissappeer.

Аарон
источник
-1

Вот категория для выполнения того же, что и в ответе sbrocket:

Заголовок:

#import <UIKit/UIKit.h>

@interface UIViewController (isBeingPopped)

- (BOOL) isBeingPopped;

@end

Источник:

#import "UIViewController+isBeingPopped.h"

@implementation UIViewController (isBeingPopped)

- (BOOL) isBeingPopped {
    NSArray *viewControllers = self.navigationController.viewControllers;
    if (viewControllers.count > 1 && [viewControllers objectAtIndex:viewControllers.count-2] == self) {
        return NO;
    } else if ([viewControllers indexOfObject:self] == NSNotFound) {
        return YES;
    }
    return NO;
}

@end
bbrame
источник