简体   繁体   中英

layoutIfNeeded behavior change iOS8 to iOS9

For certain cases with AutoLayout I need to know the width of my view (most nested subview) within it's superview. With AutoLayout in iOS 8 I was able to rely on layoutIfNeeded for the layout system to layout the frames and get the proper width before I do this calculation.

An example would be something like this:

- (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;
}

This no longer works with iOS 9. I'm sure that all constraints to be able to calculate the width are set (usually just leading, trailing constraints bound to the superview).

I noticed this in the release notes for iOS 9 but I wasn't really able to interpret it.

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.

Has anyone else encountered this issue, or familiar with how to solve?

Edit: Couple More Examples

I usually do this when I need to know explicitly what the layout width will be of the superview because constraints of the subview are dependent on this value and can't be expressed with preferredMaxLayoutWidth.

The first example is a custom view with an array of labels. When constraints are updated I need to know the width so I can know if those labels will continue on the same line or move down to the next line.

- (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];
}

One more:

In this example there will sometimes be a text label that is shown based on a condition. If it needs to be shown I expand it to it's appropriate height constrained to the width of it's superview (it only has insets to it's leading and trailing superview). If it doesn't need to be shown I collapse the label.

- (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];
}

There can also be a case when I need a textField to be shown and not be pushed off the screen by other elements along the x axis so I have to give it a fixed width via constraints but I need to know the max width before I do that

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

I ended up overriding layoutSubviews since this is a UIView subclass and it seems to be working now with this code

- (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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM