7

On iOS 11 many of our layouts are breaking due to labels apparently misreporting their intrinsicContentSize.

The bug seems to manifest worst when a UILabel is wrapped in another view that attempts to implement intrinsicContentSize itself. Like so (simplified & contrived example):

class LabelView: UIView {

    let label = UILabel()

    override init(frame: CGRect) {

        super.init(frame: frame)

        self.setup()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setup() {

        self.label.textColor = .black
        self.label.backgroundColor = .green
        self.backgroundColor = .red
        self.label.numberOfLines = 0
        self.addSubview(self.label)

        self.label.translatesAutoresizingMaskIntoConstraints = false
        self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        self.label.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor).isActive = true
        self.label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    }

    override var intrinsicContentSize: CGSize {

        let size = self.label.intrinsicContentSize

        print(size)

        return size
    }
}

The intrinsicContentSize of the UILabel is very distinctive and looks something like: (width: 1073741824.0, height: 20.5). This causes the layout cycle to give far too much space to the view's wrapper.

This only occurs when compiling for iOS 11 from XCode 9. When running on iOS 11 compiled on the iOS 10 SDK (on XCode 8).

On XCode 8 (iOS 10) the view is rendered correctly like so:

enter image description here

on XCode 9 (iOS 11) the view is rendered like this:

XCode 9 rendering

A Gist with full playground code demonstrating this issue is here.

I have filed a radar for this and have at least one solution to the problem (see answer below). I wonder if anyone else has had this problem or has alternative approached you might try.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Sam
  • 3,453
  • 1
  • 33
  • 57

1 Answers1

7

So through experimenting on the playground I was able to come up with a solution that involves testing for the extremely large intrinsic content size.

I noticed that all UILabels that misbehave have numberOfLines==0 and preferredMaxLayoutWidth=0. On subsequent layout passes, UIKit sets preferredMaxLayoutWidth to a non-zero value, presumably to iterate onto the correct height for the label. So the first fix was to try temporarily setting numberOfLines when (self.label.numberOfLines == 0 && self.label.preferredMaxLayoutWidth == 0).

I also noticed that all UILabels that have these two properties as 0 do not necessarily misbehave. (i.e. the inverse isn't true). So this fix worked, but modified the label unnecessarily some of the time. It also has a small bug that when the label's text contains \n newlines, number of lines should be set to the number of lines in the string, not 1.

The final solution I came to is a little more hacky, but specifically looks for UILabel misbehaving and only kick's it then...

override var intrinsicContentSize: CGSize {

    guard super.intrinsicContentSize.width > 1000000000.0 else {

        return super.intrinsicContentSize
    }

    var count = 0

    if let text = self.text {

        text.enumerateLines {(_, _) in
            count += 1
        }

    } else {

        count = 1
    }

    let oldNumberOfLines = self.numberOfLines

    self.numberOfLines = count
    let size = super.intrinsicContentSize

    self.numberOfLines = oldNumberOfLines

    return size
}

You can find this as a Gist here.

Sam
  • 3,453
  • 1
  • 33
  • 57
  • 6
    I found a little less hacky solution for this problem. The fix is setting the `preferredMaxLayoutWidth` of the UILabel. I set it to the screen width like this: `preferredMaxLayoutWidth = UIScreen.main.bounds.width` – Jimmy Jul 30 '18 at 07:54
  • I think that's probably a more hacky solution myself – Sam Jul 30 '18 at 19:59
  • You are just a life saver after 6 hours of struggle. Hacky macky, it just works :thumbsup: @sam – ergunkocak Mar 12 '21 at 21:33
  • This also has same effect : https://stackoverflow.com/a/26181894/956701 – ergunkocak Mar 12 '21 at 22:29
  • Glad it helped @ergunkocak The other solution looks good but I don't know if it would work if the width isn't known ahead of time as in my example above – Sam Mar 15 '21 at 13:25