在我的 viewDidAppear 中,我怎么知道它何时被孩子解开?

Posted

技术标签:

【中文标题】在我的 viewDidAppear 中,我怎么知道它何时被孩子解开?【英文标题】:In my viewDidAppear, how do I know when it's being unwound by a child? 【发布时间】:2015-06-19 12:52:50 【问题描述】:

当我的孩子执行 unwind segue 时,我的控制器的 viewDidAppear 被调用。

在这个方法中(而且只有这个方法,我需要知道它是否来自于展开)

注意:孩子正在展开到第一个视图控制器,所以这是一个中间视图控制器,而不是真正的根。

【问题讨论】:

它将得到viewWillAppear:viewDidAppear: 但我怎么知道它是来自放松而不是别的什么? 我不确定你为什么要把这两件事放在一起......过程。但是对于您的问题....从技术上讲是的。重要的是要记住 viewWillAppear:viewDidAppear: 将在视图出现时被调用。不管它是否是由一个 unwind segue 处理的。 无论如何想象用例有一些问题,如果你想放松到根视图控制器,为什么要出现中间控制器? viewDidAppear 会被调用吗? 你确定吗?根据我在文档中的理解:developer.apple.com/library/ios/technotes/tn2298/_index.html 似乎并非如此。看起来它是源控制器和目标控制器之间的直接转换。在中间视图控制器上触发 viewWillAppear 也没有什么意义,因为它们永远不会“出现”。 【参考方案1】:

您应该能够使用以下内容在每个控制器中检测视图控制器的暴露是由于被推送/呈现,还是由于弹出/关闭/展开而被暴露。

这可能足以满足您的需求。

- (void) viewDidAppear:(BOOL)animated
    [super viewDidAppear:animated];

    // Handle controller being exposed from push/present or pop/dismiss
    if (self.isMovingToParentViewController || self.isBeingPresented)
        // Controller is being pushed on or presented.
    
    else
        // Controller is being shown as result of pop/dismiss/unwind.
    

如果您想知道调用 viewDidAppear 是因为展开 segue 与调用传统的 pop/dismiss 不同,那么您需要添加一些代码来检测是否发生了展开。为此,您可以执行以下操作:

对于您想要检测纯粹展开的任何中间控制器,添加表单的属性:

/** BOOL property which when TRUE indicates an unwind occured. */
@property BOOL unwindSeguePerformed;

然后重写unwind segue方法canPerformUnwindSegueAction:fromViewController:withSender:方法如下:

- (BOOL)canPerformUnwindSegueAction:(SEL)action
                 fromViewController:(UIViewController *)fromViewController
                         withSender:(id)sender
  // Set the flag indicating an unwind segue was requested and then return
  // that we are not interested in performing the unwind action.
  self.unwindSeguePerformed = TRUE;

  // We are not interested in performing it, so return NO. The system will
  // then continue to look backwards through the view controllers for the 
  // controller that will handle it.
  return NO;

现在您有了一个检测展开的标志和一种在展开之前检测到展开的方法。然后调整viewDidAppear 方法以包含此标志。

- (void) viewDidAppear:(BOOL)animated
    [super viewDidAppear:animated];

    // Handle controller being exposed from push/present or pop/dismiss
    // or an unwind
    if (self.isMovingToParentViewController || self.isBeingPresented)
        // Controller is being pushed on or presented.
        // Initialize the unwind segue tracking flag.
        self.unwindSeguePerformed = FALSE;
    
    else if (self.unwindSeguePerformed)
        // Controller is being shown as a result of an unwind segue
    
    else
        // Controller is being shown as result of pop/dismiss.
    

希望这能满足您的要求。

有关处理 unwind segue 链的文档,请参阅:https://developer.apple.com/library/ios/technotes/tn2298/_index.html

【讨论】:

用完整的解决方案更新了答案,包括检测展开与弹出/关闭。 这个答案不正确。当弹出视图控制器时调用viewDidAppear: 时,self.isMovingToParentViewController || self.isBeingPresented 为真。 (是的,我确定是弹出视图控制器,而不是后面那个) 确实,答案是错误的。自 2015 年编写以来,这似乎发生了变化?【参考方案2】:

你的问题对我来说真的很有趣,因为我以前从未使用过 IB 和 segues(不要因此而评判我)并且想学习一些新东西。正如您在 cmets 中所述:

viewDidAppear 将在C 倒退到A 时在B 上调用

所以我想出了一个简单的自定义解决方案:

protocol ViewControllerSingletonDelegate: class 

    func viewControllerWillUnwind(viewcontroller: UIViewController, toViewController: UIViewController)


class ViewControllerSingleton 

    static let sharedInstance = ViewControllerSingleton()

    private var delegates: [ViewControllerSingletonDelegate] = []

    func addDelegate(delegate: ViewControllerSingletonDelegate) 

        if !self.containsDelegate(delegate) 

            self.delegates.append(delegate)
        
    

    func removeDelegate(delegate: ViewControllerSingletonDelegate) 

        /* implement any other function by your self :) */
    

    func containsDelegate(delegate: ViewControllerSingletonDelegate) -> Bool 

        for aDelegate in self.delegates 

            if aDelegate === delegate  return true 
        

        return false
    

    func forwardToDelegate(closure: (delegate: ViewControllerSingletonDelegate) -> Void) 

        for aDelegate in self.delegates  closure(delegate: aDelegate) 
    



class SomeViewController: UIViewController, ViewControllerSingletonDelegate 

    let viewControllerSingleton = ViewControllerSingleton.sharedInstance

    func someFunction()  // some function where you'll set the delegate

        self.viewControllerSingleton.addDelegate(self)
    

    /* I assume you have something like this in your code */
    @IBAction func unwindToSomeOtherController(unwindSegue: UIStoryboardSegue) 

        self.viewControllerSingleton.forwardToDelegate  (delegate) -> Void in

            delegate.viewControllerWillUnwind(unwindSegue.sourceViewController, toViewController: unwindSegue.destinationViewController)
        

        /* do something here */
    

    // MARK: - ViewControllerSingletonDelegate
    func viewControllerWillUnwind(viewcontroller: UIViewController, toViewController: UIViewController) 

        /* do something with the callback */
        /* set some flag for example inside your view controller so your viewDidAppear will know what to do */
    

您还可以修改回调函数以返回其他内容,例如控制器标识符而不是控制器本身。

我以编程方式做所有事情,所以请不要也以此评判我。 ;)

如果此代码 sn-p 不能帮助您,我仍然希望看到一些反馈。

【讨论】:

【参考方案3】:

假设segue导航是ViewController -> FirstViewController -> SecondViewController。从SecondViewControllerViewController 有一个放松。您可以在中介FirstViewController 中添加以下代码来检测展开动作。

import UIKit

class FirstViewController: UIViewController 

    var unwindAction:Bool = false
    override func viewDidAppear(animated: Bool) 
        if unwindAction 
            println("Unwind action")
            unwindAction = false
        
    
    override func viewControllerForUnwindSegueAction(action: Selector, fromViewController: UIViewController, withSender sender: AnyObject?) -> UIViewController? 
        self.unwindAction = true

         return super.viewControllerForUnwindSegueAction(action, fromViewController: fromViewController, withSender: sender)
    


编辑 在考虑了这一点之后,我决定解决这个问题取决于您在这里处理的复杂性。当你做 unwind segue 的时候你到底在做什么?此处给出的解决方案是可行的并且有效——仅当您想检测它是否是展开操作时。如果您想在展开发生的点之间传递数据到根节点怎么办?如果您想在其中一个中间视图控制器中进行一系列复杂的准备工作怎么办?如果你想同时做这两个怎么办?

在如此复杂的场景中,我会立即排除覆盖视图控制器的展开方法。在那里做这样的操作会起作用,但它不会是干净的。一个方法将做它不应该做的事情。闻到了吗?那是代码味道。

如果某个视图控制器可以通过某种方式通知层次结构中的下一个视图控制器发生的事件怎么办?更好的是,我们如何在不将这两者紧密耦合的情况下做到这一点?

协议。

有一个类似的协议定义:

protocol UnwindResponding 
    prepareForUnwindSegue(segue:UISegue , formViewController:UIViewController, withImportantInfo info:[String,AnyObject])

使用协议,您将保持对象之间的关系——在这种情况下是视图控制器的层次结构——显式。在特定事件发生时,您将调用委托给层次结构中的下一个控制器,通知另一个视图控制器中特定事件的发生。这是一个例子:

override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) 

    if let unwindResponder = self.presentingViewController as? UnwindResponding where segue.identifier = "unwindSegue" 
        unwindResponder.prepareForUnwindSegue(segue:UISegue, fromViewController:self,info:info)
    



在中间视图控制器中,您可以执行以下操作:

extension IntermediaryViewController : UnwindResponding 
    prepareForUnwindSegue(segue:UISegue , fromViewController:UIViewController, withImportantInfo info:[String,AnyObject]) 
        if let unwindResponder =  self.presentingViewController 
            unwindResponder.prepareForUnwindSegue(segue,fromViewController:fromViewController, info:info)
        
        unwindSegue = true
    

当然,如果您只是想检测 unwind segues,您不会想这样做。也许你会,你永远不知道未来会发生什么。保持代码清洁永远不会受到伤害。

【讨论】:

我不相信viewControllerForUnwindSegueAction会在Push->Push的情况下在FirstViewController中被调用。文档说明在开始展开的控制器上调用此方法,对于推送的控制器,这将返回作为导航控制器的父级。然后canPerformUnwindSegueAction 被调用它的孩子。唯一可靠的机制是使用canPerformUnwindSegueAction 来检测所有控制器中的展开。看我的回答。 好的。是的。但是你为什么要在导航控制器系统中做一个 unwind segue 呢?对我来说似乎有点矫枉过正。 重要的一点是,如果您只是弹出到一个控制器,则很难将信息传回,您必须完成所有代码。 Unwind segues 为您提供了一种机制,可以在跳转发生之前将信息传递到您跳回的点,这一切都由 IB 完成。在许多示例中,您可能希望选择需要多次按下控制器才能到达的项目: 选择后,您希望跳回开始选择的控制器。另一个优点是它也适用于推送和模态序列,而无需您编写代码。 我更喜欢编写大量代码,特别是因为使用给定的方法,控制器之间存在紧密耦合。我宁愿委托根/后代之间的明确关系。但话又说回来,这种方法适用于简单的情况。 +1,你的答案。 :) 我认为这个想法是将紧密耦合简化为使用适当的展开目标方法:例如prepareForUnwindAsColorSelected、prepareForUnwindAsNameSelected 等...所以我实际上非常喜欢将编码简化为设置展开激活和适当展开到点之间的合同,而无需向中间控制器添加任何代码。因此,我认为只要您使用合适的命名方案,这种关系就是明确的:就像手动完成所有操作一样。感谢 +1 8^)。【参考方案4】:

在父视图控制器中添加方法

@IBAction func unwindToParent(unwindSegue: UIStoryboardSegue) 
    if let childViewController = unwindSegue.sourceViewController as? ChildViewController 
        println("unwinding from child")
    

例如,如果展开转场与按钮相关,则在情节提要中将您的按钮链接到它的视图控制器出口

它会建议链接到unwindToParent方法

然后每次执行unwind segue,都会调用unwindToParent方法

【讨论】:

【参考方案5】:

这是 UIViewController 上的一个简单类别,您可以使用它来跟踪您呈现的视图控制器是否处于展开转场中。我想它可能会被冲掉更多,但我相信这对你的情况很有效。

要使用它,您需要在目标视图控制器上从您的 unwind 操作方法注册 unwind segue:

- (IBAction) prepareForUnwind:(UIStoryboardSegue *)segue

    [self ts_registerUnwindSegue: segue];

就是这样。从您的中间视图控制器,您可以测试您是否处于展开转场中:

- (void) viewDidAppear:(BOOL)animated

    [super viewDidAppear: animated];

    BOOL unwinding = [self ts_isUnwinding];

    NSLog( @"%@:%@, unwinding: %@", self.title, NSStringFromSelector(_cmd), unwinding ? @"YES" : @"NO" );

没有必要清理任何东西; segue 结束时会自行注销。

这是完整的类别:

@interface UIViewController (unwinding)

- (void) ts_registerUnwindSegue: (UIStoryboardSegue*) segue;
- (BOOL) ts_isUnwinding;

@end


static NSMapTable* g_viewControllerSegues;

@implementation UIViewController (unwinding)

- (void) ts_registerUnwindSegue: (UIStoryboardSegue*) segue

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^

        g_viewControllerSegues = [NSMapTable weakToWeakObjectsMapTable];
    );

    for ( UIViewController* vc = segue.sourceViewController ; vc != nil ; vc = vc.presentingViewController )
    
        [g_viewControllerSegues setObject: segue forKey: vc];
    


- (BOOL) ts_isUnwinding

    return [g_viewControllerSegues objectForKey: [self ts_topMostParentViewController]] != nil;


- (UIViewController *)ts_topMostParentViewController 
    UIViewController *viewController = self;
    while (viewController.parentViewController) 
        viewController = viewController.parentViewController;
    
    return viewController;


@end

【讨论】:

vc.presentingViewController 将为 nil,但模态演示或某些祖先以模态形式呈现时除外。例如,这不适用于从 C 展开到 A 的 NavRoot->A->PushB->PushC,在 B 处检测,因为没有 presentingViewControllerts_registerUnwindSegue 需要使用展开 segue 搜索代码用于定位目标控制器的相同算法,其中包括搜索源控制器导航控制器的子级、导航父级、模态和每个的组合。 @RoryMcKinnel - IMO 这个问题与呈现的(模态)控制器有关,而不是推送到 UINavigationController 堆栈上的控制器。为什么?因为从导航堆栈展开不会调用中间控制器的 viewDidAppear 方法。 再看一遍,你的假设看起来是正确的。 这是一个很好的解决方法,谢谢。但是我对其进行了测试,但imo的实现不正确。由于给定presentingViewController 的所有子级也将调用viewDidAppear 作为其父级对其自身调用的副作用,因此该类别不会找到presentingViewController 的任何子级控制器。我会建议对您的问题进行编辑以修复它,因为它不适合评论,而且我认为不值得回答,因为逻辑都是您的。【参考方案6】:

您可以覆盖函数unwindForSegue:towardsViewController:,当 ViewController 位于展开 segue 的路径上时会调用该函数。它旨在用于重新配置 ViewController。

Swift 示例:

override func unwind(for unwindSegue: UIStoryboardSegue, towardsViewController subsequentVC: UIViewController) 

【讨论】:

不幸的是,我没有看到在展开路径中的任何视图控制器上调用 unwindForSegue:towardsViewController: (iOS 12.1)。

以上是关于在我的 viewDidAppear 中,我怎么知道它何时被孩子解开?的主要内容,如果未能解决你的问题,请参考以下文章

应用程序进入前台后 viewDidAppear 不再触发

在 viewDidAppear 中无限调用 presentViewController

viewDidAppear:在某些条件下不触发?

Viewwillappear 已调用但 viewdidappear 未调用

使用 viewDidAppear 的自动登录不利用导航控制器

ViewDidLoad 中 UITextField 的圆角只影响左角