0

I made function that set line height and letter spacing in UILabel.

extension UILabel {
    func apply(lineHeight: CGFloat, letterSpacing: CGFloat = 0) {
        let attributedString = NSMutableAttributedString(string: text ?? " ")
        let range = NSMakeRange(0, attributedString.length)
        
        // Line Height
        let style = NSMutableParagraphStyle()
        style.minimumLineHeight = lineHeight
        style.maximumLineHeight = lineHeight
        attributedString.addAttribute(.paragraphStyle, value: style, range: range)
        attributedString.addAttribute(.baselineOffset, value: (lineHeight - font.lineHeight) / 2, range: range)
        
        // Letter Spacing
        attributedString.addAttribute(.kern, value: letterSpacing, range: range)
        
        attributedText = attributedString
    }
}

And I apply that my label.

But something very strange happens.

All of the codes below are the same.

The only difference is when you change the label's text.

I can't understand why this is happening.

What's the problem?

override func viewDidLoad() {
        super.viewDidLoad()
        
        
        let label1 = UILabel()
        label1.font = .systemFont(ofSize: 32, weight: .bold)
        label1.apply(lineHeight: 48)
        label1.text = "This is test1"
        label1.translatesAutoresizingMaskIntoConstraints = false
        
        let label2 = UILabel()
        label2.text = "This is test2"
        label2.font = .systemFont(ofSize: 32, weight: .bold)
        label2.apply(lineHeight: 48)
        label2.translatesAutoresizingMaskIntoConstraints = false
        
        let label3 = UILabel()
        label3.text = "This is test3"
        label3.font = .systemFont(ofSize: 32, weight: .bold)
        label3.apply(lineHeight: 48)
        label3.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(label1)
        view.addSubview(label2)
        view.addSubview(label3)
        
        NSLayoutConstraint.activate([
            label1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label1.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            
            label2.topAnchor.constraint(equalTo: label1.bottomAnchor, constant: 8),
            label2.centerXAnchor.constraint(equalTo: label1.centerXAnchor),
            
            label3.topAnchor.constraint(equalTo: label2.bottomAnchor, constant: 8),
            label3.centerXAnchor.constraint(equalTo: label1.centerXAnchor)
        ])
    }

enter image description here

oddK
  • 261
  • 4
  • 14

1 Answers1

0

This could possibly be considered a "bug" - worth reporting.

If you set the .text property after setting the attributed text, the line height in the paragraph style does not get applied.

You can confirm this by adding this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let v = view.subviews[1] as? UILabel {
        v.text = "This is test2"
    }
}

When you tap the view, your 2nd label will "lose" it's line height setting.

Curiously, the way around this is to reference the label's .attributedText property after setting its .text property:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let v = view.subviews[1] as? UILabel {
        v.text = "This is test2"
        _ = v.attributedText
    }
}

which, apparently, will cause UIKit to "update" the paragraph line height style.

So, for your specific example, adding this at the end of viewDidLoad():

[label1, label2, label3].forEach { v in
    _ = v.attributedText
}

will result in all 3 labels having the same appearance.

DonMag
  • 69,424
  • 5
  • 50
  • 86