1

I am working with an UILabel that is constrained to a fixed width, and no more than a certain height (fixed to, say, half the screen size)

To allow our app to work across iOS devices, I set adjustsfontsizetofitwidth to be true on the UILabel. This leads to a resulting adjustment of the font size of the text if the screen size is small.

However, it appears that if the font is actually resized, the UILabel now has extra top and bottom padding to the text. This is marked as part of its intrinsic content size.

This extra vertical padding is problematic, because I have a button that needs to sit right below the baseline of the last line of text.

I need a method to now readjust my frame to tightly fit the newly adjusted text size.

I tried using sizeToFit() as well as invalidateIntrinsicContentSize in layoutSubviews() for my view class, but to no avail.

It seems similar to this and this, neither of which have answers that work for me.

class ProblemExample: UIViewController {
     private let instructionsLabel: UILabel = {
            let label = UILabel().disableAutoresizingMask()
            label.numberOfLines = 0
            label.text = "Lorem ipsum dolor sit amet, ut adhuc argumentum vix, vix eruditi appetere corrumpit in. Quod cibo mnesarchum ex sea. Ad qui case assum delicata, ei laudem prodesset democritum per, alia admodum efficiantur has id. Semper integre ei est, quo dolor causae definitionem ei. Malis impedit vim at, vero quas sit ea. Quaeque expetenda an est."
            label.font = Theme.headingFont // BIG FONT
            label.setLineSpacing(multiple: 1.15)
            label.textColor = .black
            label.lineBreakMode = .byTruncatingTail
            label.adjustsFontSizeToFitWidth = true;
            label.minimumScaleFactor = 0.5

            return label
        }()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.addSubview(instructionsLabel)
        // Using https://github.com/roberthein/TinyConstraints for syntactical lightness
        instructionsLabel.widthToSuperview(multiplier:0.5)
        instructionsLabel.heightToSuperview(multiplier:0.5, relation:.equalOrLess)
        instructionsLabel.centerYToSuperview()

        button = UIButton()
        self.addSubView()
        button.bottom(to: instructionsLabel)
    }
}

Winston Du
  • 340
  • 2
  • 9
  • 1
    if you need dynamic height then dont use this constraint heightToSuperview..... just use left, right, bottom , top constraint you may use width constraint but not height. – Abu Ul Hassan Aug 29 '19 at 04:40
  • @Winston try "label.lineBreakMode = .wordWrap" then it'll increase the height. Considering you've given all the proper constraints (top, leading, training, bottom) and keeping in mind that top and bottom constraints are not of fix value. – Mohit G. Aug 29 '19 at 06:27
  • My method to calculate the height of UILabel, https://stackoverflow.com/a/45182097/419348 – AechoLiu Aug 29 '19 at 07:43
  • @AbuUlHassan I meant heightToSuperview as an less than or equal to constraint. – Winston Du Aug 29 '19 at 23:48

2 Answers2

0

You can profit from the built-in behavior when you need either to fit text into the frame, or stretch frame according to the text size. It seems to me, you need both. Hence you have to do it yourself.

The approximate flow would be the following:

  • get the text that you are going to assign to the label
  • calculate the text's height knowing its width (see this)
  • if text height is more than max label height, set label to max height, otherwise set it to text height
  • assign text to label
Yevgeniy Leychenko
  • 1,287
  • 11
  • 25
  • Thank you for addressing my initial question. I'm still having trouble understanding the implementation of this behavior: at what stage will I know the width if it is calculated by auto layout? – Winston Du Aug 29 '19 at 23:55
  • 1
    After `viewDidLayoutSubviews()` has been called, you can calculate the width (e.g. superview.width - label.leading - label.trailing, or whatever your formula is). – Yevgeniy Leychenko Aug 30 '19 at 04:46
  • So you mean in the definition for overriding `viewDidLayoutSubviews()`? – Winston Du Aug 30 '19 at 05:02
  • You can do these steps in viewDidLayoutSubviews() or at any time after it's called. You also need to have the text, don't you? (I don't know if it comes from backend or somewhere else...) – Yevgeniy Leychenko Aug 30 '19 at 05:04
  • it seems strange to be manually inputting the formula calculating the width, when I should just get it from auto layout. To clarify, I tried your solution, but even at the time `viewDidLayoutSubviews()`, I still don't have the real width. I don't understand how the property `adjustsFontSizeToFitWidth` does this rescaling under the hood. If I had that code, I should be able to solve this problem. – Winston Du Aug 31 '19 at 19:13
  • If you don’t have the width in viewDidLayoutSubviews, most probably something affects it later. – Yevgeniy Leychenko Aug 31 '19 at 20:08
  • I do have the width of my UILabel in viewDidLayoutSubviews. The problem is that in my case I don't have it on the initial call of viewDidLayoutSubviews. This is because the method is called every time a subview is added to the view. (See: https://stackoverflow.com/questions/728372/when-is-layoutsubviews-called). Thus, imagine in my example my UILabel had its width also constrained by another object in the hierarchy before all of my subviews even exist to calculate the actual width constraint on my UILabel, `viewDidLayoutSubviews()` gets called. – Winston Du Aug 31 '19 at 22:25
  • Ok, but then you can calculate the width yourself. Just do it. – Yevgeniy Leychenko Sep 01 '19 at 04:00
0

Try by using following code, always add runtime rendering in viewDidLayoutSubViews or after viewDidLayoutSubViews

 override func viewDidLoad() {
        super.viewDidLoad()

    }

 override func viewDidLayoutSubViews(){
        super.viewDidLayoutSubViews()
        self.addSubview(instructionsLabel)
        // Using https://github.com/roberthein/TinyConstraints for syntactical lightness
        instructionsLabel.widthToSuperview(multiplier:0.5)
        instructionsLabel.heightToSuperview(multiplier:0.5, relation:.equalOrLess)
        instructionsLabel.centerYToSuperview()
        if button == nil {
            button = UIButton()
            self.addSubView()
            button.bottom(to: instructionsLabel)
        }
}
Abu Ul Hassan
  • 1,340
  • 11
  • 28
  • This is overly hacky. There is only one additional layer of dependency expression. What if you are doing dynamic constraints on dynamic constraints? – Winston Du Aug 31 '19 at 19:15
  • to handle this we used nil to it will not enter in if conditions, after first initialization . and for label it is already lazy. – Abu Ul Hassan Sep 01 '19 at 07:30