【问题标题】:Checking if a UIViewController is about to get Popped from a navigation stack?检查 UIViewController 是否即将从导航堆栈中弹出?
【发布时间】:2010-10-13 03:23:19
【问题描述】:

我需要知道我的视图控制器何时将从导航堆栈中弹出,以便执行操作。

我不能使用 -viewWillDisappear,因为当视图控制器出于任何原因移出屏幕时会调用它(比如将新的视图控制器推到顶部)。

我特别需要知道控制器何时会自行弹出。

任何想法都会很棒,在此先感谢。

【问题讨论】:

  • 即使这个问题已有 6 年历史并已得到解答,但您仍然没有阅读问题的第二行,其中我声明“我不能使用 -viewWillDisappear,因为当视图控制器因任何原因被移出屏幕(就像一个新的视图控制器被推到顶部)。”

标签: iphone objective-c uikit uiviewcontroller uinavigationbar


【解决方案1】:

在提供的 VC 中覆盖 viewWillDisappear 方法,然后检查覆盖中的 isMovingFromParentViewController 标志并执行特定逻辑。就我而言,我隐藏了导航控制器工具栏。仍然需要你介绍的 VC 明白它是被推动的,尽管它并不完美。

【讨论】:

  • 这是 iOS 5+ 中的一个干净的解决方案,而现在谁还没有使用 iOS 5?
  • 来自 Apple 文档。 “...例如,视图控制器可以通过检查表达式 ([self isBeingDismissed] || [self isMovingFromParentViewController]) 在其 viewWillDisappear: 方法中询问自己来检查它是否因为被解除或弹出而消失”
  • 感谢@Pei 的评论。如果您能添加指向此 Apple 文档的链接,我将不胜感激。
  • 它实际上来自 iOS SDK 文档。从 Xcode 5.1.1 开始,您可以在 UIViewController.h 的第 229 到 232 行找到它。
  • 从 Xcode 6.1.1 cc 开始,行数已更改为 270-275:@Pei
【解决方案2】:

幸运的是,在调用 viewWillDisappear 方法时,viewController 已经从堆栈中移除,所以我们知道 viewController 正在弹出,因为它不再在 self.navigationController.viewControllers 中

斯威夫特 4

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if let nav = self.navigationController {
        let isPopping = !nav.viewControllers.contains(self)
        if isPopping {
            // popping off nav
        } else {
            // on nav, not popping off (pushing past, being presented over, etc.)
        }
    } else {
        // not on nav at all
    }
}

原始代码

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if ((self.navigationController) && 
        (![self.navigationController.viewControllers containsObject:self])) {
        NSLog(@"I've been popped!");
    }
}

【讨论】:

  • 绝对是这里更好的答案,也是当前有效的答案。消除了对子类化的需求,虽然这对某些人来说很方便,但可能有点过头了。
  • 调用respondsToSelector 是不必要的。每个 UINavigationController 都支持 popToRootViewControllerAnimated:
  • 另外,谓词测试不好。它只检查具有相同类的控制器是否在列表中,而不检查该特定控制器是否存在。最好使用更简单的东西,例如:[self.navigationController.viewControllers containsObject:self]
  • Jakob Egger 很到位。我已经根据他的建议更新了代码。
  • 感谢 Caoimhghin(准确地说,是对最后一个 i 的 fada)(代名词:kwee-veen)——尽管我认为我可能会使用 MattDiPasquale 的替代,因为它更简单一些
【解决方案3】:

尝试在UIViewController 的自定义子类中覆盖willMoveToParentViewController:(而不是viewWillDisappear:)。

在视图控制器被添加或从容器视图控制器中删除之前调用。

- (void)willMoveToParentViewController:(UIViewController *)parent
{
    [super willMoveToParentViewController:parent];
    if (!parent) {
        // `self` is about to get popped.
    }
}

【讨论】:

  • 听起来这是正确的方法!迫不及待想试试这个。 +1
  • 确实!当 viewDidDisappear 的可靠性远低于 willMoveToParentViewController 时,为什么有人会使用它:使用 iOS 8.4,我认为这应该是公认的答案。
  • 这个方法的另一个好处是视图控制器仍然引用导航控制器(如果有的话)
  • 想补充一点,这并不像我最初想象的那样防弹。而不是覆盖“willMoveToParentViewController”,您应该使用内部相同的代码覆盖“didMoveToParentViewController”。这背后的原因是即使用户没有使用交互式手势完成弹出,“willMoveToParentViewController”也会触发——你会得到一个误报;另一方面,“didMoveToParentViewController”在完全转换完成之前不会触发。
  • 当控制器出现和消失时也会触发
【解决方案4】:

我认为没有明确的消息,但您可以继承 UINavigationController 并覆盖 - popViewControllerAnimated(尽管我自己之前没有尝试过)。

或者,如果没有其他对视图控制器的引用,您可以添加到它的-dealloc 吗?

【讨论】:

  • dealloc 只会在 弹出之后被调用,而不是之前。
  • 我认为这不是最好的解决方案。我想在应用程序的其他地方使用这个控制器,而我想要执行的行为是特定于这个控制器的,并且必须在弹出控制器时发生。我不想对这个 viewController 出现的每个 navController 进行子类化。
  • 试试这个:子类 UIViewController,覆盖 popViewController:animated: 并向 UIViewController 的委托发送自定义消息。然后,代理可以决定在每种情况下需要做什么。
  • 子类化 'UINavigationController' 会导致应用被苹果拒绝。 documentation
  • 子类不会让你被苹果拒绝。类只是不打算用于子类化,因为苹果使用的 NSNavigaionController 实例也无法访问,但子类化本身就存在。
【解决方案5】:

这对我有用。

- (void)viewDidDisappear:(BOOL)animated
{
    if (self.parentViewController == nil) {
        NSLog(@"viewDidDisappear doesn't have parent so it's been popped");
        //release stuff here
    } else {
        NSLog(@"PersonViewController view just hidden");
    }
}

【讨论】:

  • 这正是我所需要的。谢谢。
  • 还有一个副作用是全屏 uipopovercontrollers 或模态视图控制器出现并触发这些。
【解决方案6】:

你可以在这里抓住它。

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

    if (viewController == YourAboutToAppearController) {
            // do something
    }
}

这将在新视图显示之前触发。还没有人动。我一直在 asinine NavigationController 前做魔术。您可以设置标题和按钮标题并在那里做任何事情。

【讨论】:

  • 我的实验表明实际上[UINavigationController visibleViewController] 已经设置为YourAboutToAppearController。虽然动画确实还没有开始。
  • 使用 UINavigationControllerDelegate 似乎比继承 UINavigationController 更好。
【解决方案7】:

我也有同样的问题。我尝试使用 viewDisDisappear,但我没有调用函数 :((不知道为什么,可能是因为我所有的 VC 都是 UITableViewController)。 Alex 的建议工作正常,但如果您的导航控制器显示在更多选项卡下,它会失败。在这种情况下,您的导航控制器的所有 VC 都将 navigationController 作为 UIMoreNavigationController,而不是您已子类化的导航控制器,因此当 VC 即将弹出时,导航不会通知您。
最后,我解决了一个 UINavigationController 类别的问题,只需重写 - (UIViewController *)popViewControllerAnimated:(BOOL)animated

- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
   NSLog(@"UINavigationController(Magic)");
   UIViewController *vc = self.topViewController;
   if ([vc respondsToSelector:@selector(viewControllerWillBePopped)]) {
      [vc performSelector:@selector(viewControllerWillBePopped)];
   }
   NSArray *vcs = self.viewControllers;
   UIViewController *vcc = [vcs objectAtIndex:[vcs count] - 2];
   [self popToViewController:vcc animated:YES];
   return vcc;}

对我来说效果很好:D

【讨论】:

  • 这是一个很好的解决方案,不像其他建议那样脆弱。也可以使用通知,以便任何想了解弹出视图的人都可以收听。
  • 是的,这将是一个很好的答案,超级快,无需委托,无需通知......谢谢。向 viewDidDisapper 添加逻辑并不完美,例如,当在其中推送或呈现另一个视图控制器时,也会调用 viewDidDisAppear ......这就是我非常喜欢这个选项的原因。
  • 其实 subclass 会是更好的选择,否则会有警告,但你可以通过以下方式抑制它:#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation " .......... #pragma clang 诊断弹出
【解决方案8】:

我试过了:

- (void) viewWillDisappear:(BOOL)animated {
    // If we are disappearing because we were removed from navigation stack
    if (self.navigationController == nil) {
        // YOUR CODE HERE
    }

    [super viewWillDisappear:animated];
}

这个想法是,在弹出时,视图控制器的 navigationController 设置为 nil。 因此,如果视图要消失,并且它不再有一个导航控制器,我得出的结论是它被弹出了。 (在其他情况下可能不起作用)。

不能保证 viewWillDisappear 会在弹出时被调用,因为文档中没有提到它。当视图是顶视图和顶视图下方时,我尝试了它 - 它在两者中都有效。

祝你好运, 奥德。

【讨论】:

  • 一个有趣的想法和方法,但我担心它可能有点太脆弱了。它依赖于可能随时更改的实现细节。
  • 同意,因此是最后的怀疑。
  • 感谢 Oded,这个小 sn-p 帮了大忙!
【解决方案9】:

子类UINavigationController 并覆盖popViewController

斯威夫特 3

protocol CanPreventPopProtocol {
    func shouldBePopped() -> Bool
}
  
class MyNavigationController: UINavigationController {
    override func popViewController(animated: Bool) -> UIViewController? {
        let viewController = self.topViewController
        
        if let canPreventPop = viewController as? CanPreventPopProtocol {
            if !canPreventPop.shouldBePopped() {
                return nil
            }
        }
        return super.popViewController(animated: animated)
    }

    //important to prevent UI thread from freezing
    //
    //if popViewController is called by gesture recognizer and prevented by returning nil
    //UI will freeze after calling super.popViewController
    //so that, in order to solve the problem we should not return nil from popViewController
    //we interrupt the call made by gesture recognizer to popViewController through
    //returning false on gestureRecognizerShouldBegin
    //
    //tested on iOS 9.3.2 not others
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        let viewController = self.topViewController
        
        if let canPreventPop = viewController as? CanPreventPopProtocol {
            if !canPreventPop.shouldBePopped() {
                return false
            }
        }
        
        return true
    }

}

【讨论】:

  • 如果有人在使用上述代码后遇到冻结问题,请发表评论。
  • 我应该有这个问题吗?我现在看不到它,但这可能是一个错误吗?
  • @RoiMulia 我在滑动手势时得到了它。在 iOS 9.3.3 中。检查您是否在滑动过程中看到该问题。
  • 谢谢,我会仔细检查的
  • 谢谢,您的回答确实对我有帮助,但它并没有立即生效,所以我已经更改了它,并将发布另一个答案。
【解决方案10】:

你可以用这个:

if(self.isMovingToParentViewController)
{
    NSLog(@"Pushed");
}
else
{
    NSLog(@"Popped");
}

【讨论】:

    【解决方案11】:

    也许你可以使用 UINavigationBarDelegate 的 navigationBar:shouldPopItem 协议方法。

    【讨论】:

    • 我先试过了。但是,我的导航栏由导航控制器管理,并且手动将栏的委托设置为我的视图控制器会导致异常,说明如果栏由导航管理,则不允许在导航栏上手动设置委托控制器。
    【解决方案12】:

    尝试在 viewwilldisappear 中进行此检查 if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) { //这个视图的弹出已经发生。 }

    【讨论】:

      【解决方案13】:

      您可以观察通知:

      - (void)viewDidLoad{
          [super viewDidLoad];
          [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(navigationControllerWillShowViewController:) name:@"UINavigationControllerWillShowViewControllerNotification" object:nil];
      }
      
      - (void)navigationControllerDidShowViewController:(NSNotification *)notification{
          UIViewController *lastVisible = notification.userInfo[@"UINavigationControllerLastVisibleViewController"];
          if(lastVisible == self){
              // we are being popped
          }
      }
      

      【讨论】:

        【解决方案14】:

        我有时还需要防止弹出,所以对我来说最好的答案是由 Orkhan Alikhanov 写的。但是因为没有设置delegate所以没用,所以我做了最终版本:

        import UIKit
        
        class CustomActionsNavigationController: UINavigationController, 
                                                 UIGestureRecognizerDelegate {
            override func viewDidLoad() {
                super.viewDidLoad()
                interactivePopGestureRecognizer?.delegate = self
            }
        
            override func popViewController(animated: Bool) -> UIViewController? {
                if let delegate = topViewController as? CustomActionsNavigationControllerDelegate {
                    guard delegate.shouldPop() else { return nil }
                }
                return super.popViewController(animated: animated)
            }
        
            // important to prevent UI thread from freezing
            //
            // if popViewController is called by gesture recognizer and prevented by returning nil
            // UI will freeze after calling super.popViewController
            // so that, in order to solve the problem we should not return nil from popViewController
            // we interrupt the call made by gesture recognizer to popViewController through
            // returning false on gestureRecognizerShouldBegin
            func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
                if let delegate = topViewController as? CustomActionsNavigationControllerDelegate {
                    if !delegate.shouldPop() {
                        return false
                    }
                }
        
                // This if statement prevents navigation controller to pop when there is only one view controller
                if viewControllers.count == 1 {
                    return false
                }
        
                return true
            }
        }
        
        protocol CustomActionsNavigationControllerDelegate {
            func shouldPop() -> Bool
        }
        

        更新

        我添加了viewControllers.count == 1 case,因为如果堆栈中有一个控制器并且用户做出手势,它将冻结您的应用程序的 UI。

        【讨论】:

          【解决方案15】:
          - (void)viewDidDisappear:(BOOL)animated {
              [super viewDidDisappear:animated];
          
              const BOOL removingFromParent = ![self.navigationController.viewControllers containsObject:self.parentViewController];
              if ( removingFromParent ) {
                  // cleanup
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2011-03-12
            • 2018-04-12
            • 1970-01-01
            • 1970-01-01
            • 2012-12-31
            • 2021-01-19
            • 2013-02-19
            • 2011-01-07
            • 2017-08-04
            相关资源
            最近更新 更多