为什么在呈现控制器上呈现模态UIViewController导致setNeedsLayout?

时间:2014-03-24 04:29:37

标签: ios cocoa-touch uiview uiviewcontroller autolayout

看来,呈现和解除视图控制器都会提示呈现视图来布局其子视图和/或更新其约束。在视图层次较深的情况下,这会引入性能问题。再次 - 这是现有的,当前显示的视图。创建和显示的模态非常轻。

无论我是否使用autolayout(如我的示例项目中),都会发生这种情况。

我构建了一个demo project,它近似于我正在处理的应用。有一个主要的父控制器,水平滚动UIScrollView。多个子控制器被添加到父控制器,并且它们的视图被添加到滚动视图并使用NSLayoutConstraint进行排列。每个子视图本身都有一个子视图,一个简单的UIView,也有一个约束。

在导航栏中,有一个用于启动模态的按钮。呈现时,父控制器多次在每个子视图上调用setNeedsLayout。在我的演示项目中,我正在覆盖setNeedsLayout以便在访问时进行记录。关闭模态时也会出现同样的情况。打开并关闭模态几次并观察控制台。

我看不出需要新布局的原因,而且对于更复杂的视图,我发现有数百个这样的调用正在触发,并且会对性能产生明显的影响。

请注意,当省略ChildView的布局代码时,不会调用setNeedsLayout。我鼓励您注释掉约束并查看日志记录的差异。

为什么会这样?如何在呈现和解除模态时阻止不必要的布局传递?

3 个答案:

答案 0 :(得分:3)

首先,您正在记录setNeedsLayout,这只是一种标记机制,并不会真正招致任何工作。多次调用setNeedsLayout可能只会触发单个布局。您应该记录-[UIView layoutSubviews]-[UIViewController viewDidLayoutSubviews],因为这些都是实际繁重发生的地方。

其次,布局相关的方法是在演示期间重复和快速调用的,因为:

  1. 窗口需要旋转其所有子视图以尊重呈现的视图控制器的首选界面方向。
  2. 动画需要知道您的观点的初始和最终状态。
  3. 当出于任何原因在父视图上发生布局时,所有子视图(可能包括视图控制器的视图)当然也需要更新其布局。
  4. 如果您想最大限度地减少布局次数,可以尝试使用presentViewController:animated:放弃,而是使用addChildViewController:并手动设置必要的视图。但即便如此,您仍然可以触发父控制器的布局。

答案 1 :(得分:1)

您正在做一件非常非常奇怪的事情:您正在维护一个带有10个子视图控制器的自定义父视图控制器,所有子视图控制器的所有视图都在接口中同时。视图控制器不是为那种东西而设计的。这就是触发您看到的多个layoutSubviews来电。拥有多个子视图控制器很好,但是它们的视图不应该都在层次结构中 - 特别是在你的情况下,只有一个这样的子视图实际上是可见的。

实际上,您构建的界面 - 分页滚动视图,其中每个页面都是"是由视图控制器管理的视图 - 已经由UIPageViewController为您实现,它更高效,因为它实际上一次只能维护最多三个视图控制器:视图控制器管理可见滚动视图中的视图,以及管理其右侧和左侧视图的视图控制器。它也非常方便,易于使用。

所以:

  • 您应使用 UIPageViewController或

  • 你应该模仿 UIPageViewController做什么,删除视图控制器'视图(甚至可能发布视图控制器)当它们滚动到视线外时 - 正如我们在UIPageViewController存在之前所做的那样 - 请参阅WWDC 2011的高级滚动视图技术视频。我的拉丁文"闪卡&#34 ;应用程序在UIPageViewController出现之前以这种方式工作;它有数千的词汇卡,每个词汇卡由一个视图控制器管理,但在任何一个时刻最多只能存在三个卡视图控制器。

(顺便说一句,您也不应该使用自己的self.childControllers可变数组,因为此列表已经为您self.childViewControllers维护。)

答案 2 :(得分:0)

我认为layoutSubviews正在被调用,因为呈现控制器view会更改超视图,同时在显示视图隐藏后动画出屏幕。

如果你想在框架没有改变的情况下跳过layoutSubviews,只需保存对最后一帧的引用,如果等于返回而不做任何操作。此外,无需在子视图上调用setNeedslayout,因为系统会在您调整大小时自动触发它。

无论如何,你的主要问题是你的方法:

  1. 如果您要使用视图控制器(仅在标签栏控制器内,推送到导航控制器,作为窗口' { {1}},以模态方式呈现,等等。如果要手动添加视图,请不要使用视图控制器,只使用自定义视图!这是一个非常常见的错误,您可以看到更多详细信息here

  2. 懒惰加载视图和对象并重复使用。例如,您应该只加载1~3页内容并仅在用户滚动到它们时加载新内容。加载新视图时,请删除其中一个旧视图,或者更好地重复使用它。


  3. 您不仅可以将逻辑与控制器分开,还可以将自定义视图与逻辑分开。在特定情况下不应使用控制器的一些原因:

    • 当您手动添加视图时,控制器不会被容器控制器或窗口保留。
    • 控制器无法获得方向,记忆,rootController等事件。再次,因为你没有将它们用作正确的视图控制器。

    如果您正确实施了custom container controller(这是很多工作要做得很好),那么您可以使用控制器。否则坚持自定义视图。