Search code examples
iosautolayoutuilabel

How can I create a view has intrinsicContentSize just like UILabel?


I want to create a custom view that self-size with content.

For example, I setup horizontal constraints for a UILabel, then it will line break automatically.

I wonder how the system knows the label's width before layout?

I print updateConstraints , layoutSubviews and intrinsicContentSize method to try to know how is everything going.

first, I set numberOfLine = 1, so the logs just like this:

TagLabel will updateConstraints
TagLabel intrinsicContentSize 447.5 20.5
TagLabel did updateConstraints
TableViewCell will updateConstraints
TableViewCell did updateConstraints
TableViewCell will layoutSubviews
TableViewCell did layoutSubviews
TagLabel will layoutSubviews
TagLabel did layoutSubviews
TagLabel will layoutSubviews
TagLabel did layoutSubviews

then I set numberOfLine = 0, logs change like this:

TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TableViewCell will updateConstraints
TableViewCell did updateConstraints

// extra calls
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel intrinsicContentSize 371.0 41.0

TableViewCell will layoutSubviews
TableViewCell did layoutSubviews

// extra calls
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 371.0 41.0
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 371.0 41.0
TagLabel did updateConstraints

TagLabel will layoutSubviews
TagLabel did layoutSubviews
TagLabel will layoutSubviews
TagLabel did layoutSubviews

I found that system calculate label's height correctly before layoutSubviews.

So how the system knows the label's width so that it can calculate the height before layoutSubviews

appreciate.


Solution

  • To start with you need to be clear if you want to create a new UIView or subclass UILabel.

    You can use a lot of template code without understanding how the system handles calculations. I assume further that you want to make a custom view from groud up.

    For this explanation we will use UILabel as a example and guideline for our new custom view.

    What you need to understand is that with setting numberOfLines property you change the way the calculation inside UILabel is handled. You can test this by clicking the NOL buttons in the test project.

    When creating your custom view you would have several methods for calculating the size of your custom view located in you custom view subclass. To check this go to EXCustomLabelV and view the:

    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
    

    method output in the console. You will see the first constraint change. The value will become 566.667 while the height still stays 24. The result are the “…” added with text truncation. However, if you set your numberOfLines property to 0 then the constrains change to 195.333 width and 71.6667 height.

    Something important is happening here. The system will always use your constant constrains to calculate your custom View size/position. But your will will have to make adjustments when calculating YOUR subviews (of your custom view) based on the “outer” constraints and your “inner” setup.

    For example, the EXCustomLabelV will always have the width of 200. No matter what. However, the inner textRect won’t have that size. It will sometimes have around 200, sometimes more than 200 and sometimes less. These calculations are affected by the numberOfLines. I am telling you this because you might have a image inside your custom view, or two text fields with surname and last name.

    How does the system know the labels width before layout? It calculates it. It asks “around” first by checking the last constraint (if there is one) for width. It asks it subviews (your custom class) for its constrains and then makes the calculations. The constraints are rules. AutoLayout will be check these rules when calculating.

    How does the system know the labels height? It calculates it. It checks the UILabel numberOfLines first.

    In your custom view you should have custom methods for calculation. In the test project you can see that :

    override func draw(_ rect: CGRect)
    

    is always called last. After all calculations are made you will have the final size of your view. You will always see that there your view width will be equal to the last width constraint.

    Feel free to use my test project for your own calculations and to understand UILabel handless view size calculations.

    When creating your custom view you will have to make these calculations on your own and update the sizes and constrains manually. The system should then be able to update your view easily. I would recommend that you try to calculate the frame sizes for a view on your own. When you go through that “pain” you will understand how much AutoLayout does for you.

    Test project repo link