【问题标题】:Frame calculations in `viewDidLayoutSubviews``viewDidLayoutSubviews` 中的帧计算
【发布时间】:2015-11-26 15:29:42
【问题描述】:

首先,我要提一下,这主要是一个效率问题。

viewWillAppear 太早而viewDidAppear 太晚(视图已经可见)的情况下,关于在哪里进行帧计算的讨论很多。

常见的答案是在viewDidLayoutSubviews 中进行帧计算。问题是,它被多次调用。更糟糕的是,最准确的调用,即所有帧都有其最终大小的调用是最后一个调用。据我所知,没有办法知道哪个呼叫是最后一个呼叫。

我们曾经有一个'framesAreSet'标志(初始化为假)和一个检查,如果帧不为零(类似于self.view.frame.size.width != 0)并且'framesAreSet'为假,它会进入,转动标志并只计算一次。比如:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if (self.view.frame.size.width != 0 && !framesAreSet)
    {
        framesAreSet = true;

        //Calculate frames here
    }
} 

这看起来没问题,但实际上,检查 self.view.frame.size.width != 0 之类的内容保证确实设置了框架。 viewDidLayoutSubviews 将在之后被调用这一事实表明某些帧未设置为其最终状态。

如果有viewCompleteLayoutSubviews 那就太好了。当所有框架都已设置且视图尚不可见时,对于完成“一次性”框架计算的最佳方法有什么想法吗?

(这可能是视图使用 NSConstraints 的问题)

【问题讨论】:

  • 为什么viewDidLayoutSubviews被多次调用是个问题?你面临什么问题?
  • 你有机会更新self.view 的边界吗?如果是这样,那是你的问题。
  • @rmaddy 冗余不是好的编码习惯。假设您有一个复杂的 ViewController,在代码中添加了许多子视图(实际上大部分是在代码中创建的)并为设置框架进行了许多计算。事实上,想象一下“繁重”的计算调用次数与添加子视图的次数一样多。请注意,除了最后一个之外,所有内容都是多余的。有时“它有效”还不够好。
  • 所以您的问题严格来说是效率问题之一。你应该在你的问题中说清楚。我过去做过的一件事是跟踪self.view.frame.size 和内部viewDidLayoutSubviews 我只在上次调用后大小发生变化时才进行计算。
  • @rmaddy 更新了问题。谢谢。

标签: ios ios9


【解决方案1】:

应用程序每次绘制仅获得一个didLayoutSubviews(在此之前,视图状态更改仅使用setNeedsLayout 记录)。从这个意义上说,didLayoutSubviews 是你对didCompleteLayoutOfSubviews 的想法。绘制完成后,如果再次发生视图状态变化,则布局再次不完整。

换句话说,didLayout 调用的数量不取决于子视图添加或框架更改的数量,它取决于绘制的数量(不要与运行循环混淆)。在绘制之前,如果设置了 needsLayout 标志,layoutSubviews 然后 didLayoutSubviews 将被调用一次,无论视图层次结构被重新排列了多少。

【讨论】:

  • 如果我理解正确,didLayoutSubviews 的调用次数取决于(除了最初的)我在代码中的更改(更改框架、添加子视图)。也就是说,它是可预测的,我可以在我最后一次“didLayoutSubviews trigger”之后有一个标志,之后最后一次(对于那个周期)将调用didLayoutSubviews
  • 我觉得你的理解不太对。 didLayout 调用的数量不取决于子视图添加或框架更改的数量,它取决于绘制的数量(不要与运行循环混淆)。在绘制之前,如果设置了 needsLayout 标志,layoutSubviews 然后 didLayoutSubviews 将被调用一次,无论视图层次结构被重新排列了多少。
  • 好答案。如果您可以将最后一条评论添加到您的答案中,那就太好了,我会将其标记为“已接受”。我需要找到在初始“layoutSubviews”之后调用我的视图进行绘制的位置。但这是一个不同的问题。谢谢!
  • @bauerMusic - 希望对您有所帮助。如果需要,请发布生成布局的代码(也可能是布局代码)。也许我们可以改进它。
  • 希望我能做到这一点(公司方面)。其实这个问题不是任何项目的问题,一直在通过各种项目思考。我正在做一些非常相似的事情,创建一个测试用例,我可以在其中查明导致视图绘制调用layoutSubviews(或生命周期中的哪个位置)的原因。到目前为止,我发现添加 UILabel 会触发第二次调用。在 viewDidLoadviewWillAppear 中更改其框架或颜色的 xib 视图引用不会。
【解决方案2】:

您应该始终根据视图当前的维度重新布局。

您设置框架,然后旋转设备。你需要重新计算帧吗?当然可以,边界大小已经改变!你在用你的framesAreSet 做出危险的假设。

如果性能确实是您的关键,您可能希望将计算的帧缓存到某个字典中,以在边界 size 更改时使这些帧无效。

但你肯定不会只执行一次布局。

另一种方法是让视图控制器的view 成为自定义类的实例并覆盖其中的-layoutSubviews

【讨论】:

  • “你在用你的 framesAreSet 做出危险的假设”。在某些时候,它会被设置,比如viewDidAppear。考虑一个不旋转的视图,多次运行相同的帧计算是否有意义?更糟糕的是,如果视图确实旋转,是否应该每次旋转多次运行帧计算,而唯一有用的时间是最后一次?
  • "考虑一个不旋转的视图,多次运行相同的帧计算是否有意义?" - 是的,还有其他因素会导致您的视图改变其大小。
【解决方案3】:

您可以使用 dispatch once 令牌 (GCD) 在 vi​​ewDidLayoutSubviews 方法中计算帧,如果帧被初始化一次,这将不会进入您的 dispatchonce 块。

【讨论】:

  • 好点。但是,出于我的目的,它的行为与标志没有任何不同。此外,还有“捕捉”最后和最新的viewDidLayoutSubviews 呼叫的问题。
  • 但我不认为我们可以在 viewDisLayoutSubview 中获得最后一次调用 ....您可以在 viewWillAppear 中进行帧计算。
  • 正如我所写,在不同的条件下,viewWillAppear 帧没有最终大小。
  • 你能告诉我你到底想做什么,这样我就可以更好地理解,这样我们就可以解决这个问题。
  • dispatch_once 的行为与常规标志不同,它是线程安全的,而标志不是。
猜你喜欢
  • 1970-01-01
  • 2014-12-25
  • 2018-03-25
  • 2017-01-26
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2011-06-08
  • 2014-06-18
相关资源
最近更新 更多