layoutIfNeeded 行为将 iOS8 更改为 iOS9

layoutIfNeeded behavior change iOS8 to iOS9

对于某些使用 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. 我确定所有能够计算宽度的约束都已设置(通常只是绑定到 superview 的前导、尾随约束)。

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

In iOS 9, when layoutIfNeeded is sent to a view and all of the following conditions are satisfied (which is not common), we apply fitting-size constraints (width/height = 0 at UILayoutPriorityFittingSizeLevel) instead of required size constraints (width/height required to match current size):

  1. The receiver is not yet in the subtree of a view that hosts a layout engine, such as window, view controller view (unless you have set translatesAutoresizingMaskIntoConstraints to NO on that view—or created constraints that have one item in its subtree and one item outside it), table view cell content view, and so on.
  2. The final ancestor (that is, top-level view) of the receiver has translatesAutoresizingMaskIntoConstraints set to NO.
  3. The top-level view has a subview that is not a UIViewController-owned layout guide that also has translatesAutoresizingMaskIntoConstraints set to NO.

Under condition 1, we create a temporary layout engine from the top-level view and add all the constraints from the subtree to it. The problem is that we need to add some constraints that make the size of the top-level view unambiguous in the layout engine. The old behavior (prior to iOS 9) was that we would add constraints to restrict the size of the top-level view to its current bounds for any situation under condition 1. This really doesn’t make sense when you add conditions 2 and 3 and can result in unsatisfiable-constraints logging and broken layout.

So in iOS 9, for this special case only, we use fitting-size constraints instead.

This means that if you are sending layoutIfNeeded to a view under these conditions in iOS 9, you must be sure that either you have sufficient constraints to establish a size for the top-level view (which usually, though not always, is the receiver) or you must add temporary size constraints to the top-level view of layout size you desire before sending layoutIfNeeded, and remove them afterward.

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

编辑:添加更多示例

当我需要明确知道父视图的布局宽度是多少时,我通常会这样做,因为子视图的约束取决于此值,无法用 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];
}

我最终覆盖了 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;
}