在NSMutableAttributedString中垂直居中文本。

huangapple go评论86阅读模式
英文:

Center text vertically in line of NSMutableAttributedString

问题

我正在使用NSMutableAttributedString来设置文本样式。当我设置.minimumLineHeight时,它会将文本居中到行的底部。我想要自定义一些方式来垂直居中文本在行内。

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

我想要获得这样的结果:

在NSMutableAttributedString中垂直居中文本。

英文:

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)

在NSMutableAttributedString中垂直居中文本。

I want to get such result: 在NSMutableAttributedString中垂直居中文本。

答案1

得分: 1

提供 lineHeight 的问题在于您会使用硬编码值覆盖默认的高度计算行为。即使您提供了字体的行高作为值,它也可能会根据提供的内容动态变化,最好将其留给自动布局(参考 https://stackoverflow.com/a/33278748/9293498

自动布局在大多数情况下都有解决这些问题的方法。您只需为标签设置适当的约束(以及在您的情况下的 lineSpacing),它就可以根据提供的文本自动缩放,以满足所需的空间。NSAttributedStringString 值都应该适用。

以下是我尝试模拟您的要求时编写的代码示例:

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 {
    // 让自动布局确定行高
    let attributedString = NSMutableAttributedString(string: text)
    let style = NSMutableParagraphStyle()
    style.lineSpacing = 8 // 我只是声明了行之间的间距
    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)
    ])
}

在上面的示例中,我使用了水平的堆栈视图来保持文本和单选按钮的对齐,这对于您的需求似乎是有效的。但关键是,只要您的标签处于适当约束的环境中,您就不需要手动指定任何形式的高度。

英文:

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:

在NSMutableAttributedString中垂直居中文本。

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.

huangapple
  • 本文由 发表于 2023年1月9日 19:00:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/75056318.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定