【问题标题】:layoutIfNeeded behavior change iOS8 to iOS9layoutIfNeeded 行为将 iOS8 更改为 iOS9
【发布时间】:2016-02-15 08:37:32
【问题描述】:

对于某些使用 AutoLayout 的情况,我需要知道我的视图(嵌套最多的子视图)在其父视图中的宽度。使用 iOS 8 中的 AutoLayout,我能够依靠 layoutIfNeeded 让布局系统在进行此计算之前对框架进行布局并获得适当的宽度。

一个例子是这样的:

- (CGSize)intrinsicContentSize {
    [self layoutIfNeeded];
    CGSize size = [self roundedSizeAccountingLeftRightInsets:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)];
    size.height += self.insets.top + self.insets.bottom;
    return size;
}

这不再适用于 iOS 9。我确信所有能够计算宽度的约束都已设置(通常只是绑定到超级视图的前导、尾随约束)。

我在 iOS 9 的发行说明中注意到了这一点,但我无法真正理解它。

在 iOS 9 中,当 layoutIfNeeded 被发送到视图并且满足以下所有条件(这并不常见)时,我们应用拟合尺寸约束(UILayoutPriorityFittingSizeLevel 处的宽度/高度 = 0)而不是所需的尺寸约束(匹配当前大小所需的宽度/高度):

  1. 接收器尚未位于托管布局引擎的视图的子树中,例如窗口、视图控制器视图(除非您在该视图上将 translatesAutoresizingMaskIntoConstraints 设置为 NO,或者创建了在其子树中有一个项目的约束并且外的一项)、表格视图单元格内容视图等。
  2. 接收器的最终祖先(即顶级视图)已将 translatesAutoresizingMaskIntoConstraints 设置为 NO。
  3. 顶级视图有一个子视图,它不是 UIViewController 拥有的布局指南,它也将 translatesAutoresizingMaskIntoConstraints 设置为 NO。

在条件 1 下,我们从顶层视图创建一个临时布局引擎,并将子树中的所有约束添加到它。问题是我们需要添加一些约束,使顶级视图的大小在布局引擎中明确。旧的行为(在 iOS 9 之前)是我们会添加约束以将顶级视图的大小限制在条件 1 下的任何情况下的当前范围内。当您添加条件 2 和 3 时,这真的没有意义并可能导致不可满足的约束记录和破坏的布局。

所以在 iOS 9 中,仅针对这种特殊情况,我们使用拟合大小约束。

这意味着如果您在 iOS 9 中的这些条件下向视图发送 layoutIfNeeded,您必须确保您有足够的约束来为顶级视图建立大小(通常,尽管并非总是如此)接收者),或者您必须在发送 layoutIfNeeded 之前将临时大小约束添加到您想要的布局大小的顶级视图,然后将其删除。

有没有其他人遇到过这个问题,或者熟悉如何解决?

编辑:更多示例

当我需要明确知道父视图的布局宽度是多少时,我通常会这样做,因为子视图的约束依赖于该值并且不能用preferredMaxLayoutWidth 表示。

第一个示例是带有标签数组的自定义视图。更新约束时,我需要知道宽度,以便知道这些标签是继续在同一行还是向下移动到下一行。

- (void)updateConstraints {
    [self layoutIfNeeded];
    CGFloat width = self.view.bounds.size.width;
    for (UILabel *label in self.labels) {
    CGSize labelSize = [label sizeThatFits:CGSizeZero];
    CGFloat minLabelWidth = MAX(12, labelSize.width);
    labelSize.width = minLabelWidth;
    lineWidth += labelSize.width + 10;
    if (lineWidth >= width) {
        // update some variables to where I will actually be applying constraints 
    }
    [label mas_updateConstraints:^(MASConstraintMaker *make) {
          // constraint magic
    }];  
    [super updateConstraints];
}

还有一个:

在此示例中,有时会有一个基于条件显示的文本标签。如果需要显示它,我将它扩展到它的适当高度,限制为它的超级视图的宽度(它只有它的前导和尾随超级视图的插图)。如果不需要显示,我会折叠标签。

- (void)updateConstraints {
    // Need layout pass to get the proper width.
    [self layoutIfNeeded];
    CGFloat textHeight = [self.label sizeThatFits:CGSizeMake(self.bounds.size.width - 32, CGFLOAT_MAX)].height;
    [self.label mas_remakeConstraints:^(MASConstraintMaker *make) {
        // update other constraints
        make.height.equalTo( showThisText ? @(textHeight) : @0 );
    }];
    [super updateConstraints];
}

还有一种情况是我需要显示一个 textField 而不是被沿 x 轴的其他元素推离屏幕,所以我必须通过约束给它一个固定的宽度,但我需要知道最大宽度在我这样做之前

- (void)updateConstraints {
    [self layoutIfNeeded];
    CGFloat textFieldWidth = self.bounds.size.width - someVariable;
    [self.textField mas_remakeConstraints:^(MASConstraintMaker *make) {
      make.width.equalTo(@(textFieldWidth));
    }];
    [super updateConstraints];
}

【问题讨论】:

  • 真正的问题是你的代码没有意义。如果您的视图有intrinsicContentSize,则根本不应该调用layoutIfNeeded。它应该知道,基于关于其内容的事实,它自己的大小应该是多少。在任何情况下,您都不会仅仅因为您想知道某些东西的 实际 大小而覆盖 intrinsicContentSize。我建议您透露更多有关您真正想要做什么的信息,以便我们可以帮助您做到那个
  • @matt 在这个特定的示例中,它是一个 UILabel,它需要知道应该将什么高度限制为特定宽度。然后它添加了一些插图。在不知道它的超级视图中的宽度限制的情况下,您还能如何计算正确的尺寸?
  • 不,我不关注你。插入标签的方法不是覆盖它的intrinsicContentSize,而是覆盖它的textRectForBoundsdrawTextInRect。决定高度与宽度行为的方法是设置preferredMaxLayoutWidth。所以我仍然看不到你的代码应该做什么。如果你按照我的建议去做,那将真的很有帮助:拉出你的整个问题并用你的实际目标的描述替换它。
  • @matt 我又添加了几个例子。让我知道这是否更有帮助。总而言之,当我需要在继续之前知道视图的宽度时,我会事先执行布局传递。
  • 看来你在updateConstraints做的工作应该在viewDidLayoutSubviews做。如果您观看 WWDC 2015 视频 2019,他们会上下跳动并警告您不要覆盖 updateConstraints,除非您正在创建/删除约束,并且他们特别警告我们已经在布局中间 此时。我认为在这种情况下调用layoutIfNeeded 完全不正确。

标签: ios ios8 autolayout ios9 nslayoutconstraint


【解决方案1】:

我最终覆盖了 layoutSubviews,因为这是一个 UIView 子类,它现在似乎正在使用此代码

- (void)layoutSubviews {
    [super layoutSubviews];

    static CGSize viewBounds = { 0, 0 };
    static CGSize previousViewBounds = { 0, 0 };
    [self setNeedsUpdateConstraints];
    viewBounds = CGSizeMake(self.bounds.size.width, self.bounds.size.height);
    if (!CGSizeEqualToSize(viewBounds, previousViewBounds)) [self setNeedsUpdateConstraints];
    previousViewBounds = viewBounds;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-27
    • 2016-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多