如何在不重载 CPU 的情况下动态更新 UILabel 宽度
How to update a UILabel width dynamically without overloading the CPU
iOS中的标签是像(1)一样创建的,没有水平边距,一点也不美观。
我想创建一个像(2)中的标签,弯曲的边缘和左右边距
这个标签的内容每秒更新2次,它的宽度必须动态变化。
所以我创造了这个class
@IBDesignable
class BeautifulLabel : UILabel {
// private var internalRect : CGRect? = .zero
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: marginTop,
left: marginLeft,
bottom: marginBottom,
right: marginRight)
super.drawText(in: rect.inset(by: insets))
}
@IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
self.layer.masksToBounds = cornerRadius > 0
}
}
@IBInspectable var marginTop: CGFloat = 0
@IBInspectable var marginBottom: CGFloat = 0
@IBInspectable var marginLeft: CGFloat = 0
@IBInspectable var marginRight: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
var bounds = self.bounds
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
这行得通,但在 layoutSubviews()
中调整 self.bounds
,使该方法再次被调用,导致巨大的循环,CPU 尖峰和内存泄漏。
然后我试了这个:
override var text: String? {
didSet {
let resizingLabel = UILabel(frame: self.bounds)
resizingLabel.text = self.text
var bounds = resizingLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: 500, height: 50), limitedToNumberOfLines: 1)
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
}
这根本行不通。标签没有调整到合适的大小。
标签必须只有一行、固定高度、截尾和固定字体大小(系统 17)。我对它的宽度感兴趣。
有什么想法吗?
视图不应更改其自身的大小。它应该只改变它的 intrinsicContentSize
.
当您将视图添加到视图层次结构时,即指定它是否应遵守固有内容大小(例如,内容拥抱设置、抗压缩性、没有明确的宽度和高度限制等)。如果您这样做,自动布局引擎将为您完成所有工作。
因此,举个例子,极简主义的方法就是覆盖 intrinsicContentSize
:
@IBDesignable
class BeautifulLabel: UILabel {
@IBInspectable var marginX: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
@IBInspectable var marginY: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
@IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius } }
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + marginX * 2, height: size.height + marginY * 2)
}
}
一个更完整的例子可能是一个 UIView
子类,其中标签是一个子视图,插入适当的边距:
@IBDesignable
class BeautifulLabel: UIView {
@IBInspectable var marginTop: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var marginBottom: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var marginLeft: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var marginRight: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var cornerRadius: CGFloat = -1 { didSet { setNeedsLayout() } }
@IBInspectable var text: String? {
get {
label.text
}
set {
label.text = newValue
invalidateIntrinsicContentSize()
}
}
@IBInspectable var font: UIFont? {
get {
label.font
}
set {
label.font = newValue
invalidateIntrinsicContentSize()
}
}
private var topConstraint: NSLayoutConstraint!
private var leftConstraint: NSLayoutConstraint!
private var rightConstraint: NSLayoutConstraint!
private var bottomConstraint: NSLayoutConstraint!
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override var intrinsicContentSize: CGSize {
let size = label.intrinsicContentSize
return CGSize(width: size.width + marginLeft + marginRight,
height: size.height + marginTop + marginBottom)
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let maxCornerRadius = min(bounds.width, bounds.height) / 2
if cornerRadius < 0 || cornerRadius > maxCornerRadius {
layer.cornerRadius = maxCornerRadius
} else {
layer.cornerRadius = cornerRadius
}
}
}
private extension BeautifulLabel {
func configure() {
addSubview(label)
topConstraint = label.topAnchor.constraint(equalTo: topAnchor, constant: marginTop)
leftConstraint = label.leftAnchor.constraint(equalTo: leftAnchor, constant: marginLeft)
rightConstraint = rightAnchor.constraint(equalTo: label.rightAnchor, constant: marginRight)
bottomConstraint = bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: marginBottom)
NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}
func didUpdateInsets() {
topConstraint.constant = marginTop
leftConstraint.constant = marginLeft
rightConstraint.constant = marginRight
bottomConstraint.constant = marginBottom
invalidateIntrinsicContentSize()
}
}
现在在这种情况下,我只公开了 text
和 font
,但是您显然会重复您想要公开的任何其他属性。
但我们不要迷失在上述实现的细节中。最重要的是,视图不应该尝试调整自己的大小,而只是调整自己的 intrinsicContentSize
。它应该在必要时执行 invalidateIntrinsicContentSize
。
iOS中的标签是像(1)一样创建的,没有水平边距,一点也不美观。 我想创建一个像(2)中的标签,弯曲的边缘和左右边距
这个标签的内容每秒更新2次,它的宽度必须动态变化。
所以我创造了这个class
@IBDesignable
class BeautifulLabel : UILabel {
// private var internalRect : CGRect? = .zero
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: marginTop,
left: marginLeft,
bottom: marginBottom,
right: marginRight)
super.drawText(in: rect.inset(by: insets))
}
@IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
self.layer.masksToBounds = cornerRadius > 0
}
}
@IBInspectable var marginTop: CGFloat = 0
@IBInspectable var marginBottom: CGFloat = 0
@IBInspectable var marginLeft: CGFloat = 0
@IBInspectable var marginRight: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
var bounds = self.bounds
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
这行得通,但在 layoutSubviews()
中调整 self.bounds
,使该方法再次被调用,导致巨大的循环,CPU 尖峰和内存泄漏。
然后我试了这个:
override var text: String? {
didSet {
let resizingLabel = UILabel(frame: self.bounds)
resizingLabel.text = self.text
var bounds = resizingLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: 500, height: 50), limitedToNumberOfLines: 1)
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
}
这根本行不通。标签没有调整到合适的大小。
标签必须只有一行、固定高度、截尾和固定字体大小(系统 17)。我对它的宽度感兴趣。
有什么想法吗?
视图不应更改其自身的大小。它应该只改变它的 intrinsicContentSize
.
当您将视图添加到视图层次结构时,即指定它是否应遵守固有内容大小(例如,内容拥抱设置、抗压缩性、没有明确的宽度和高度限制等)。如果您这样做,自动布局引擎将为您完成所有工作。
因此,举个例子,极简主义的方法就是覆盖 intrinsicContentSize
:
@IBDesignable
class BeautifulLabel: UILabel {
@IBInspectable var marginX: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
@IBInspectable var marginY: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
@IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius } }
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + marginX * 2, height: size.height + marginY * 2)
}
}
一个更完整的例子可能是一个 UIView
子类,其中标签是一个子视图,插入适当的边距:
@IBDesignable
class BeautifulLabel: UIView {
@IBInspectable var marginTop: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var marginBottom: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var marginLeft: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var marginRight: CGFloat = 0 { didSet { didUpdateInsets() } }
@IBInspectable var cornerRadius: CGFloat = -1 { didSet { setNeedsLayout() } }
@IBInspectable var text: String? {
get {
label.text
}
set {
label.text = newValue
invalidateIntrinsicContentSize()
}
}
@IBInspectable var font: UIFont? {
get {
label.font
}
set {
label.font = newValue
invalidateIntrinsicContentSize()
}
}
private var topConstraint: NSLayoutConstraint!
private var leftConstraint: NSLayoutConstraint!
private var rightConstraint: NSLayoutConstraint!
private var bottomConstraint: NSLayoutConstraint!
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override var intrinsicContentSize: CGSize {
let size = label.intrinsicContentSize
return CGSize(width: size.width + marginLeft + marginRight,
height: size.height + marginTop + marginBottom)
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let maxCornerRadius = min(bounds.width, bounds.height) / 2
if cornerRadius < 0 || cornerRadius > maxCornerRadius {
layer.cornerRadius = maxCornerRadius
} else {
layer.cornerRadius = cornerRadius
}
}
}
private extension BeautifulLabel {
func configure() {
addSubview(label)
topConstraint = label.topAnchor.constraint(equalTo: topAnchor, constant: marginTop)
leftConstraint = label.leftAnchor.constraint(equalTo: leftAnchor, constant: marginLeft)
rightConstraint = rightAnchor.constraint(equalTo: label.rightAnchor, constant: marginRight)
bottomConstraint = bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: marginBottom)
NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}
func didUpdateInsets() {
topConstraint.constant = marginTop
leftConstraint.constant = marginLeft
rightConstraint.constant = marginRight
bottomConstraint.constant = marginBottom
invalidateIntrinsicContentSize()
}
}
现在在这种情况下,我只公开了 text
和 font
,但是您显然会重复您想要公开的任何其他属性。
但我们不要迷失在上述实现的细节中。最重要的是,视图不应该尝试调整自己的大小,而只是调整自己的 intrinsicContentSize
。它应该在必要时执行 invalidateIntrinsicContentSize
。