viewWillDisappear: 判断视图控制器是被弹出还是展示子视图控制器

138

我正在努力寻找一个好的解决方法来解决这个问题。在视图控制器的-viewWillDisappear:方法中,我需要找到一种方式来确定是因为视图控制器被推到导航控制器的堆栈上,还是因为视图控制器消失了,因为它已经被弹出。

目前,我正在设置标志,例如isShowingChildViewController,但它变得相当复杂。我认为唯一可以检测它的方法是在-dealloc方法中。

13个回答

234
你可以使用以下内容。
- (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");
  }
}

当然,这是可能的,因为在调用viewWillDisappear方法时,UINavigationController的视图控制器堆栈(通过viewControllers属性公开)已经被更新。


2
太好了!我不知道为什么我没想到那个!我猜我没有想到堆栈会在 disappear 方法被调用之前被改变!谢谢 :-) - Michael Waterfall
1
我刚刚尝试在viewWillAppear方法中执行相同的操作,无论是通过推出视图控制器还是弹出上面的视图控制器,viewControllers数组都是相同的!有什么想法吗? - Michael Waterfall
4
@Sbrocket,你为什么没有使用![viewControllers containsObject:self]而是使用了[viewControllers indexOfObject:self] == NSNotFound?这是出于风格的选择还是其他原因? - zekel
如果你正在处理由选项卡控制器管理的视图控制器,它仍将位于 self.navigiationController.viewControllers 中。我发现检查控制器是否已呈现另一个视图控制器更容易:if (self.presentedViewController == nil) - pr1001
25
自iOS 5以后,此答案已经过时。下面提到的-isMovingFromParentViewController方法允许您测试视图是否被显式弹出。 - grahamparks
显示剩余4条评论

141

我认为最简单的方法是:

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

迅捷:

override func viewWillDisappear(animated: Bool)
{
    if isMovingFromParent
    {
        print("View controller was popped")
    }
    else
    {
        print("New view controller was pushed")
    }
    super.viewWillDisappear(animated)
}

从iOS 5开始,这就是答案,也许还要检查isBeingDismissed。 - d370urn3ur
4
对于iOS7,我需要再次检查[self.navigationController.viewControllers indexOfObject:self] == NSNotFound,因为将应用程序放在后台也会通过此测试,但不会将self从导航堆栈中移除。 - Eric Chen
3
苹果提供了一种记录下来的方法来实现这个 - https://dev59.com/PnI-5IYBdhLWcg3whIqk#33478133 - Shyam Bhat
使用viewWillDisappear的问题在于可能在视图已经消失时控制器从堆栈中弹出。例如,另一个视图控制器可以被推到堆栈顶部,然后调用popToRootViewControllerAnimated跳过了中间视图控制器上的viewWillDisappear方法。 - John K
假设您的导航栈上有两个控制器(根视图控制器和另一个已推送的控制器)。当第三个控制器被推送时,第二个控制器的viewWillDisappear方法会被调用,因为它的视图即将消失。所以当您弹出到根视图控制器(弹出第三个和第二个控制器)时,viewWillDisappear方法会在第三个控制器上被调用,因为它的视图在顶部并且此时将要消失,而第二个控制器的视图已经消失了。这就是为什么这个方法被称为viewWillDisappear而不是viewControllerWillBePopped的原因。 - RTasche
有时候这个方法不起作用。我建议使用Bryan Henry的答案而不是这个。 - coolcool1994

64

来自苹果 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 { 
    }
}

20

Swift 4

override func viewWillDisappear(_ animated: Bool)
    {
        if self.isMovingFromParent
        {
            //View Controller Popped
        }
        else
        {
            //New view controller pushed
        }
       super.viewWillDisappear(animated)
    }

18
如果你只是想知道你的视图是否正在被弹出,我刚刚发现当它从控制器栈中移除时,self.navigationControllerviewDidDisappear 中是 nil。因此这是一个简单的替代测试。
(在尝试了各种其他扭曲之后,我发现了这一点。我很惊讶没有导航控制器协议来注册视图控制器以便在弹出时通知。您不能使用UINavigationControllerDelegate,因为它实际上会执行真正的显示操作。)

5

在Swift语言中:

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

    super.viewWillDisappear(animated)

}

请使用 as! 而不是 as,以确保类型转换成功。 - dfmuir

2

感谢 @Bryan Henry,仍然适用于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")
            }
        }

    }

1
我发现苹果的文档很难理解。这个扩展帮助查看每个导航的状态。
extension UIViewController {
    public func printTransitionStates() {
        print("isBeingPresented=\(isBeingPresented)")
        print("isBeingDismissed=\(isBeingDismissed)")
        print("isMovingToParentViewController=\(isMovingToParentViewController)")
        print("isMovingFromParentViewController=\(isMovingFromParentViewController)")
    }
}

0

这个问题相当老了,但我无意中看到了它,所以我想发布最佳实践(据我所知)

你可以直接这样做

if([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
 // view controller popped
}

0

这适用于iOS7,不知道是否适用于其他版本。据我所知,在viewDidDisappear中,视图已经被弹出。这意味着当您查询self.navigationController.viewControllers时,您将得到一个nil。因此,只需检查它是否为零。

简而言之

 - (void)viewDidDisappear:(BOOL)animated
 {
    [super viewDidDisappear:animated];
    if (self.navigationController.viewControllers == nil) {
        // It has been popped!
        NSLog(@"Popped and Gone");
    }
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接