ios - 单行 UILabel - 基线位置错误
问题描述
我在自定义 UILabel 子类中有错误的垂直基线位置numberOfLines = 1
(红线显示基线):
但在numberOfLines = 0
或其他正值的情况下它工作正常:
此自定义标签具有:
- 属性文本,
- 被覆盖
textRect(forBounds:limitedToNumberOfLines:)
。
在覆盖的方法中,我补偿了文本剪辑,这是因为行高值用于计算标签渲染矩形,并且对于此目的来说太小了。这是代码:
class CustomLabel: UILabel
{
var lineHeight: CGFloat!
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect
{
func snapToScreenScale(_ length: CGFloat) -> CGFloat
{
let scale = UIScreen.main.scale
return ceil(length * scale) / scale
}
func compensation() -> CGFloat
{
// text inside line area is attached to bottom and may be clipped at top
// so to compensate clipping we should add difference between required and original line heights
let topCompensation = max(
0,
snapToScreenScale(
font.lineHeight - lineHeight
)
)
// lines of text are centered verticaly in label's bounds
// so to compensate top clipping completely we should add equal extra space at top and bottom
let compensation = topCompensation * 2
return compensation
}
var rect = super.textRect(
forBounds: bounds,
limitedToNumberOfLines: numberOfLines
)
rect.size = .init(
width: snapToScreenScale(rect.width),
height: snapToScreenScale(rect.height)
)
rect.size.height += compensation()
return rect
}
}
以及如何使用标签:
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = 32
paragraphStyle.maximumLineHeight = 32
let label = CustomLabel()
label.backgroundColor = .lightGray
label.lineHeight = 32
label.numberOfLines = 1
label.attributedText = NSAttributedString(
string: "Text",
attributes: [
.font: UIFont.systemFont(ofSize: 32),
.paragraphStyle: paragraphStyle
]
)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 16).isActive = true
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true
let baseline = UIView()
baseline.backgroundColor = .red.withAlphaComponent(0.2)
view.addSubview(baseline)
baseline.translatesAutoresizingMaskIntoConstraints = false
baseline.topAnchor.constraint(equalTo: label.firstBaselineAnchor).isActive = true
baseline.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true
baseline.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true
baseline.heightAnchor.constraint(equalToConstant: 1).isActive = true
注意,由于项目要求:
- 我不能增加行高,
- 我不能总是使用
numberOfLines = 0
.
有趣的细节:调用super
intextRect(forBounds:limitedToNumberOfLines:)
有副作用;如果未调用,则基线将附加到标签的顶部:
因此,标签的行为显然是错误的。尝试通过调用标签的方法来修复约束
setNeedsUpdateConstraints()
updateConstraintsIfNeeded()
没有帮助,在不同的时刻尝试(标签初始化后,矩形计算后,布局后)。
在 iOS 12.0 - 14.5 中测试。
将欣赏任何想法。
解决方案
添加了解决方法。想法是设置numberOfLines
为0,因为那时不会发生错误;为了限制行数,我用所需的行数计算文本 rect,因此额外的文本不适合 rect 并被换行。
代码说明了这种方法;请注意,此代码只是一个概念,并不可靠或设计良好等。
class CustomLabel: UILabel
{
var lineHeight: CGFloat!
var requiredNumberOfLines: Int!
override var numberOfLines: Int
{
get
{
return requiredNumberOfLines
}
set
{
requiredNumberOfLines = newValue
super.numberOfLines = 0 // override to prevent bug with incorrect baseline position
}
}
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect
{
func snapToScreenScale(_ length: CGFloat) -> CGFloat
{
let scale = UIScreen.main.scale
return ceil(length * scale) / scale
}
func compensation() -> CGFloat
{
// text inside line area is attached to bottom and may be clipped at top
// so to compensate clipping we should add difference between required and original line heights
let topCompensation = max(
0,
snapToScreenScale(
font.lineHeight - lineHeight
)
)
// lines of text are centered verticaly in label's bounds
// so to compensate top clipping completely we should add equal extra space at top and bottom
let compensation = topCompensation * 2
return compensation
}
var rect = super.textRect(
forBounds: bounds,
limitedToNumberOfLines: numberOfLines
)
rect.size = .init(
width: snapToScreenScale(rect.width),
height: snapToScreenScale(rect.height)
)
rect.size.height += compensation()
return rect
}
}
推荐阅读
- regex - 正则表达式:如何删除字符串后的空格
- java - 无法使用图像按钮交换 2 个图像
- c++ - 如何从对象的数据成员构造 std::set 或 Boost flat_set?
- text - 使文本的特定部分在颤动中可点击
- ios - 你如何在 Xcode 中使用 Flutter Flavors
- kivy - buildozer 调试错误“不支持的 major.minor 版本 52.0”错误
- javascript - 如何重置输入文件?
- html - 我如何使用在 QCheckbox 的文本描述中?
- mysql - 为什么 INSERT INTO 使用引用表中的现有值使外键约束失败?
- numpy - ValueError:无法将字符串转换为浮点数:'2,019,278' - 可能是非法字符或格式?