【问题标题】:iOS how to detect programmatically when top view controller is popped?iOS如何在弹出顶视图控制器时以编程方式检测?
【发布时间】:2012-09-05 01:03:43
【问题描述】:

假设我有一个带有 2 个视图控制器的导航控制器堆栈:VC2 位于顶部,VC1 位于下方。我可以在 VC1 中包含哪些代码来检测 VC2 刚刚从堆栈中弹出吗?

由于我试图从 VC1 的代码中检测 VC2 的弹出,因此 viewWillAppearviewDidAppear 之类的东西似乎不起作用,因为这些方法每次显示 VC1 时触发,包括首次压入堆栈时。

编辑:看来我对我最初的问题不是很清楚。这就是我想要做的:确定由于 VC2 从堆栈顶部弹出而显示 VC1 的时间。这就是我不想做的事情:确定何时显示 VC1,因为它被推到了堆栈的顶部。我需要一些方法来检测第一个动作而不是第二个动作。

注意:我并不特别关心 VC2,它可以是任何数量的其他 VC 从堆栈中弹出,我关心的是 VC1 何时由于其他一些 VC 开始而再次成为堆栈顶部从顶部弹出。

【问题讨论】:

    标签: ios uinavigationcontroller stack


    【解决方案1】:

    iOS 5 引入了两种新方法来处理这种情况。你要找的是-[UIViewController isMovingToParentViewController]。来自docs

    isMovingToParentViewController

    返回一个布尔值,表示 视图控制器正在被添加到父级。

    - (BOOL)isMovingToParentViewController

    返回值
    YES 如果视图控制器出现是因为它是作为容器的子级添加的 视图控制器,否则否。

    讨论
    此方法仅在从内部调用时返回 YES 以下方法:

    -viewWillAppear:
    -viewDidAppear:

    在您的情况下,您可以像这样实现-viewWillAppear:

    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
    
        if (self.isMovingToParentViewController == NO)
        {
            // we're already on the navigation stack
            // another controller must have been popped off
        }
    }
    

    编辑:这里有一个微妙的语义差异需要考虑——您是否对 VC2 特别从堆栈中弹出这一事实感兴趣,或者您是否希望每次 VC1 显示为任何控制器弹出的结果?在前一种情况下,委托是一个更好的解决方案。如果您从不打算重用 VC2,对 VC1 的直接弱引用也可以工作。

    编辑 2:我通过反转逻辑而不是提前返回使示例更加明确。

    【讨论】:

    • 当一个 VC 被添加为另一个 VC 的子级时,似乎 isMovingToParentViewController 返回 YES。我看不出这如何帮助我判断 VC2 何时被弹出。顺便说一句,我并不特别关心 VC2,我希望每次 VC1 显示为新的堆栈顶部时都能收到通知。
    • 如果 VC1 由于另一个控制器从堆栈中弹出而被显示,则当从 -viewWillAppear: 调用时,-isMovingToParentViewController 将返回 NO。有意义吗?
    • 我从 VC1 的 viewWillAppear 调用这个函数。并且它总是返回 NO,即使 VC1 出现时弹出 VC1 上面的 VC2。我的解决方案只使用 hasAppeared 属性。问题是VC1的父视图控制器不是navigationController,而是tabBarController。
    • @Legoless 这是有道理的,因为 VC1 和 2 已经是UITabBarController 子级。您还可以分配UITabBarController.delegate 并实现-tabBarController:didSelectViewController: 以在所选VC 更改时得到通知。
    • @RyderMackay 委托方法不起作用,因为在我的情况下,VC2 被推送到导航堆栈并且不是UITabBarController 的一部分。
    【解决方案2】:

    isMovingTo/FromParentViewController 不能用于推送和弹出导航控制器堆栈。

    这是一种可靠的方法(不使用委托),但它可能仅限于 iOS 7+。

    UIViewController *fromViewController = [[[self navigationController] transitionCoordinator] viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    if ([[self.navigationController viewControllers] containsObject:fromViewController])
    {
        //we're being pushed onto the nav controller stack.  Make sure to fetch data.
    } else {
        //Something is being popped and we are being revealed
    }
    

    在我的例子中,使用委托意味着让视图控制器的行为与拥有导航堆栈的委托更紧密地耦合在一起,我想要一个更独立的解决方案。这行得通。

    【讨论】:

    • 在确定视图控制器是否被弹出时,检查 isMovingFromParentViewController() 内的 viewWillDisappear(_:) 对我来说效果很好。
    • 就我而言,这是这个问题的最佳答案/解决方案,因为它直接连接到导航控制器,并会告诉您您是否是正在弹出的视图控制器堆栈,或者如果您是由于弹出某些东西而被抚养的视图控制器。谢谢!
    【解决方案3】:

    您可以解决此问题的一种方法是为 VC2 声明一个委托协议,如下所示:

    在 VC1.h 中

    @interface VC1 : UIViewController <VC2Delegate> {
    ...
    }
    

    在 VC1.m 中

    -(void)showVC2 {
        VC2 *vc2 = [[VC2 alloc] init];
        vc2.delegate = self;
        [self.navigationController pushViewController:vc2 animated:YES];
    }
    
    -(void)VC2DidPop {
        // Do whatever in response to VC2 being popped off the nav controller
    }
    

    在 VC2.h 中

    @protocol VC2Delegate <NSObject>
    -(void)VC2DidPop;
    @end
    
    @interface VC2 : UIViewController {
    
        id<VC2Delegate> delegate;
    }
    
    @property (nonatomic, assign) id delegate;
    
    ...
    
    @end
    

    VC2.m

    -(void)viewDidUnload {
        [super viewDidUnload];
        [self.delegate VC2DidPop];
    }
    

    有一篇关于协议和委托基础知识的好文章here

    【讨论】:

    • - (void)viewDidUnload 自 iOS 6.0 起已弃用。
    【解决方案4】:

    您也可以在视图控制器中检测到正在弹出的

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

    【讨论】:

      【解决方案5】:

      这对我有用

      UIViewController *fromViewController = [[[self navigationController] transitionCoordinator] viewControllerForKey:UITransitionContextFromViewControllerKey];
      if (![[self.navigationController viewControllers] containsObject:fromViewController] && !self.presentedViewController)
      {
        //Something is being popped and we are being revealed 
      }
      

      【讨论】:

        【解决方案6】:

        你具体想做什么?

        如果您尝试检测 VC1 即将显示,this 答案应该可以帮助您。使用UINavigationControllerDelegate

        如果您要检测 VC2 即将被隐藏,我会使用 VC2 的 viewWillDisappear:

        【讨论】:

        • 我需要在 VC1 中检测何时弹出 VC1 顶部的视图控制器(在我的示例中为 VC2),将 VC1 显示为新的堆栈顶部。我检查了您链接到的答案,但似乎 willShowViewController 委托方法无法区分 VC1 被显示是因为它被推送到堆栈上,而 VC1 被显示是因为 VC2 被从堆栈中弹出。
        • 解决该问题的一个简单方法是初始化 VC1,将 BOOL 设置为 NO,然后在 viewWillAppear: 中检查 BOOL。如果BOOLNO,则设置为YES,否则执行你的处理。这将保证每当 VC1 第一次被压入堆栈时,处理都不会完成,但在任何其他情况下都会完成。
        【解决方案7】:

        您可以专门为您的 VC2 添加 NSNotification 观察者。

        // pasing the "VC2" here will tell the notification to only listen for notification from
        // VC2 rather than every single other objects
        [[NSNotitificationCenter defaultCenter] addObserver:self selector:@selector(doSomething:) object:VC2];
        

        现在在你的 VC2 的视图中会消失,你可以发布通知:

        -(void)viewWillDisappear
        {
            [[NSNotificationCenter defaultCenter] postNotificationNamed:@"notif_dismissingVC2" object:nil];
        }
        

        【讨论】:

        • 添加if (self.isMovingFromParentViewController),这是最好的答案。
        【解决方案8】:

        斯威夫特 3

        override func viewWillAppear(_ animated: Bool) {
                super.viewWillAppear(animated)
                if self.isMovingToParentViewController {
                    print("View is moving to ParentViewControll")
                }
        }
        

        【讨论】:

          【解决方案9】:

          我遇到了同样的情况,但有更具体的用例。就我而言 我们想确定当用户点击 VC2 的后退按钮时是否出现/显示 VC1,其中 VC2 被推到 navigationController 上而不是 VC1。

          所以我使用snarshad's answer 的帮助来根据我的需要进行定制。这是 swift 3 中 VC1 的 viewDidAppear 中的代码。

          // VC1: ParentViewController
          // VC2: ChildViewController
          
              override func viewDidAppear(_ animated: Bool) {
                  super.viewDidAppear(animated)
          
                  if let transitionCoordinator = navigationController?.transitionCoordinator,
                      let fromVC = transitionCoordinator.viewController(forKey: UITransitionContextViewControllerKey.from),
                      let toVC = transitionCoordinator.viewController(forKey: UITransitionContextViewControllerKey.to),
                      fromVC is ChildViewController,
                      toVC is ParentViewController {
          
                      print("Back button pressed on ChildViewController, and as a result ParentViewController appeared")
                  }
              }
          

          【讨论】:

            【解决方案10】:

            是的,在 VC1 中您可以检查 VC2 是否弹出。 UINavigationController 有一种方法 viewControllers 将返回被推送的 Controllers 数组,它们在堆栈中(即已被推送)。

            所以你通过比较类来迭代循环。如果 VC2 存在,则匹配,否则不匹配。

            【讨论】:

            • 但是好像VC1第一次入栈时,VC2不会出现在入栈的控制器数组中,所以这并不能区分VC1第一次入栈, VC2 被弹出后,VC1 移动到栈顶。
            • 你需要设置一个布尔值,无论何时推送保持为真,弹出保持为假,然后基于你可以使用。
            猜你喜欢
            • 2018-06-25
            • 2013-04-15
            • 2016-01-29
            • 2011-05-09
            • 2016-07-22
            • 1970-01-01
            • 2019-06-09
            • 1970-01-01
            • 2019-10-24
            相关资源
            最近更新 更多