0

I'm using NSMutableAttributedString to setup text style. When I set .minimumLineHeight, it centre text in line to the bottom of line. I would like to customise somehow this alignment to centre text in line vertically.

 let paragraphStyle = NSMutableParagraphStyle()
 paragraphStyle.minimumLineHeight = fontLineHeight // 24 for example
 attributedText.addAttribute(.paragraphStyle, value: paragraphStyle, range: fullRange)

enter image description here

I want to get such result: enter image description here

Konstantin.Efimenko
  • 1,242
  • 1
  • 14
  • 38
  • Just curious. Why manually set line height? You can achieve the same behaviour with proper constraints and `sizeToFit` – Lokesh SN Jan 09 '23 at 11:31
  • I have height of text 14 and height of line 24, it makes space between lines of text bigger. – Konstantin.Efimenko Jan 09 '23 at 11:39
  • I've posted an answer with some sample code that can automatically do that for you. If I've somehow misunderstood your problem, feel free to get back – Lokesh SN Jan 09 '23 at 12:59

1 Answers1

1

The problem with providing a lineHeight is that you override the default height-calculation-behaviour with a hardcoded value. Even if you've provided the font's line height as the value, it can vary dynamically based on provided content and it's best to leave this to auto layout (Refer https://stackoverflow.com/a/33278748/9293498)

Auto-layout has a solution for these issues most of the time. All you need is a proper constraint setup for your label (and lineSpacing in your scenario) which can enable it to scale automatically based on provided text with just-enough space required. Both NSAttributedString and String values should work

Here's a code sample I've written trying to simulate your requirement:

Views:

private let termsAndConditionsContainer: UIStackView = {
    let container = UIStackView()
    container.backgroundColor = .clear
    container.spacing = 16
    container.axis = .vertical
    container.alignment = .leading
    container.distribution = .fill
    container.translatesAutoresizingMaskIntoConstraints = false
    return container
}()

private let dataAgreementButton: UIButton = {
    let button = UIButton(type: .custom)
    button.setImage(UIImage(), for: .normal)
    button.layer.borderColor = UIColor.gray.cgColor
    button.layer.borderWidth = 0.5
    button.layer.cornerRadius = 16
    return button
}()

private let dataAgreementLabel: UILabel = {
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 17, weight: .medium)
    label.numberOfLines = 0
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

private let tosAgreementButton: UIButton = {
    let button = UIButton(type: .custom)
    button.setImage(UIImage(), for: .normal)
    button.layer.borderColor = UIColor.gray.cgColor
    button.layer.borderWidth = 0.5
    button.layer.cornerRadius = 16
    return button
}()

private let tosAgreementLabel: UILabel = {
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 17, weight: .medium)
    label.numberOfLines = 0
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

Functions:

private func attributedString(with text: String) -> NSAttributedString {
    //Leave it to auto-layout to determine the line height
    let attributedString = NSMutableAttributedString(string: text)
    let style = NSMutableParagraphStyle()
    style.lineSpacing = 8 // I've just declared the spacing between lines
    attributedString.addAttribute(.paragraphStyle, value: style, range: NSRange(location: 0, length: attributedString.length))
    return attributedString
}

private func generateRowContainer() -> UIStackView {
    let container = UIStackView()
    container.backgroundColor = .clear
    container.spacing = 16
    container.axis = .horizontal
    container.alignment = .center
    container.distribution = .fill
    container.layer.borderWidth = 0.5
    container.layer.borderColor = UIColor.green.cgColor
    container.translatesAutoresizingMaskIntoConstraints = false
    return container
}

And here's my constraint setup as I add the above views in viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    
    let dataAgreementContainer = generateRowContainer()
    dataAgreementContainer.addArrangedSubview(dataAgreementButton)
    dataAgreementLabel.attributedText = attributedString(with: "I agree with the Information note regarding personal data processing")
    dataAgreementContainer.addArrangedSubview(dataAgreementLabel)
    termsAndConditionsContainer.addArrangedSubview(dataAgreementContainer)
    
    let tosAgreementContainer = generateRowContainer()
    tosAgreementContainer.addArrangedSubview(tosAgreementButton)
    tosAgreementLabel.attributedText = attributedString(with: "I agree with terms and conditions")
    tosAgreementContainer.addArrangedSubview(tosAgreementLabel)
    termsAndConditionsContainer.addArrangedSubview(tosAgreementContainer)
    
    view.addSubview(termsAndConditionsContainer)
    
    NSLayoutConstraint.activate([
        
        dataAgreementButton.widthAnchor.constraint(equalToConstant: 32),
        dataAgreementButton.heightAnchor.constraint(equalToConstant: 32),
        
        tosAgreementButton.widthAnchor.constraint(equalToConstant: 32),
        tosAgreementButton.heightAnchor.constraint(equalToConstant: 32),
        
        termsAndConditionsContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32),
        termsAndConditionsContainer.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -32),
        termsAndConditionsContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
}

I got the below output:

Output

In the above example, I've used a horizontal stack view to maintain the alignment of text and radio button, which seems efficient to me for your requirement. But the point is, as long as your label is in a properly constrained environment, you don't have the need to manually specify any form of its height.

Lokesh SN
  • 1,583
  • 7
  • 23