viewDidLoad在prepareForSegue完成之前被调用

29
我原本以为viewDidLoad会在prepareForSegue执行完毕后被调用。这甚至是Hegarty教授斯坦福课程的方式(最近一次是在2013年2月)。
然而,今天我第一次注意到viewDidLoad在prepareForSegue完成之前就被调用了。因此,在prepareForSegue中设置的属性在目标视图控制器内的viewDidLoad方法中是不可用的。
这似乎与预期行为相矛盾。
更新
我刚刚发现了问题所在。在我的目标视图控制器中,我有一个自定义setter,每次“模型”更新时都会重新加载tableView:
DestinationViewController    
- (void)setManagedObjectsArray:(NSArray *)managedObjectsArray
    {
        _managedObjectsArray = [managedObjectsArray copy];
        [self.tableView reloadData];
    }

原来,由于destinationViewController是UITableViewController的子类...调用'self.tableView'会强制加载视图。根据Apple的文档,调用视图控制器的view属性可以强制加载视图。UITableViewController的视图是tableView。

http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html

因此,在prepareForSegue方法中,以下代码强制加载目标视图控制器的视图:
vc.managedObjectsArray = <custom method that returns an array>;

为了解决这个问题,我更改了 destinationViewController 的模型的自定义 setter 为:
- (void)setManagedObjectsArray:(NSArray *)managedObjectsArray
    {
        _managedObjectsArray = [managedObjectsArray copy];
        if ([self isViewLoaded]) {
            [self.tableView reloadData];
        }
    }

只有在tableView在屏幕上时,这才会重新加载tableView。因此不会在prepareForSegue期间强制加载视图。

如果有人反对此过程,请分享您的想法。否则,我希望这能防止某人长时间失眠。


1
你如何知道viewDidLoad被调用了?你能分享一些代码吗? - Khaled Barazi
2
我怀疑 prepareForSegue 实际上是调用 viewDidLoad 的函数(直接或间接地)。 - user529758
6个回答

25

过去我也遇到了类似的困惑。 我学到的一般经验规则是:

  1. prepareForSegue在目标VC的viewDidLoad之前被调用。
  2. 仅在加载所有outlet之后才会调用viewDidLoad。
  3. 不要尝试在源VC的prepareForSegue中引用任何目标VC的outlet。

关于prepareForSegue的另一个教训是避免冗余处理。例如,如果您已经通过Storyboard将tableView单元格segue到VC,则如果您尝试同时处理tableView:didSelectRowAtIndexPath和prepareForSegue,则可能会遇到类似的竞争条件。 可以通过使用手动segue或放弃didSelectRowAtIndexPath上的任何处理来避免这种情况。


实际上,如果您在initWithCoder:中访问了outlets,则在调用prepareForSegue:之前会隐式调用viewDidLoad方法。感谢@Hampde123的评论,这帮助了我。 - cdescours
如果遵循这些规则,如何在视图控制器之间传递数据? - galactikuh

3

我想在这里解释一下发生了什么:

prepareForSegue在视图被显示之前调用。

viewDidLoad在第一次访问视图时调用(视图是懒加载的)。

可能发生的情况是,您在prepareForSegue中访问了view,手动触发了视图加载。

通常的流程是:

  1. preformSegue
  2. prepareForSegue
  3. ViewController添加到层次结构中
  4. viewDidLoad

但是在您的情况下可能发生的是:

  1. preformSegue
  2. prepareForSegue
  3. |— 在prepareForSegue中,您访问了view
  4. |— view自动加载 => 调用了viewDidLoad
  5. performSegue返回
  6. ViewController添加到层次结构中,没有viewDidLoad(已经调用过了)

所以通常情况下,在将ViewController添加到层次结构之前,不会访问view属性,但如果访问了,viewDidLoad可能会提前触发。


这很有道理,但如果不是从prepareForSegue传递参数到新视图,那么该如何传递参数呢? - galactikuh
你仍然可以做到,但在访问视图之前需要设置参数。 - Antzi

2

感谢分享,帮助我解决了我的问题。

在我的情况下,destinationViewController 是一个 UITabBarController,修改它的 viewControllers 数组触发了 viewDidLoad 事件:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UITabBarController *tabBarController = segue.destinationViewController;
    tabBarController.viewControllers = ...
    tabBarController.something = something;
}

在viewDidLoad中,我需要设置something属性,所以我不得不将其移到上方:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UITabBarController *tabBarController = segue.destinationViewController;
    tabBarController.something = something;
     tabBarController.viewControllers = ...
}

1
简单的解决方案是将

放置在文本中。
[self.tableView reloadData];

viewWillAppear中:

0
在我的情况下,在prepareSegue中设置目标presentationController的代理会导致viewDidLoad被调用。 我将 segue.destination.presentationController?.delegate = self 移动到prepareSegue的末尾:
    override func prepare(for segue: UIStoryboardSegue, sender: Any?)     {
    // do stuff ....
    segue.destination.presentationController?.delegate = self
     }

0

我遇到了一个类似的问题,我的动态单元格在选择时显示了一个模态。在执行didSelect:atIndexPath之前,prepareForSegue被执行了。帮助我的是,在故事板中重新分配了segue,从控制器开始而不是动态单元格原型。我解决了竞争条件(?),现在一切都完美地工作了!


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