如何使自定义视图的框架与其固有内容大小相匹配
How to make a custom view's frame match its instrinsic content size
根据内容调整大小的自定义标签
我正在从头开始制作标签。 (我的最终目的是为蒙古文字制作一个垂直文本标签,但目前我只是制作一个普通的水平文本标签作为练习。)
只要没有长度和宽度限制,我希望我的自定义标签的框架根据其固有内容大小调整大小。
在 IB 中有效
在 IB 中,当我测试 UILabel
、我的自定义标签(UIView
subclass)和按钮时,一切似乎都工作正常。
我为每个视图添加了顶部和前导约束,但没有设置任何高度或宽度约束。
如果我在故事板上调整它们的大小(但仍然没有添加任何更多约束)...
然后为视图控制器中的所有视图选择更新帧,普通标签和我的自定义标签都正确调整到它们的固有大小内容大小。
它不适用于 运行 应用程序
但是,当我在运行时更改标签文本时,我的自定义标签的框架没有调整大小。 (我暂时在文本层添加了深蓝色边框,以帮助区分此处自定义标签的框架,后者是浅蓝色背景色。)
点击"Change text"得到
如您所见,文本层框架发生了变化,但自定义视图的框架没有。
代码
这是我的自定义标签class:
import UIKit
@IBDesignable
class UILabelFromScratch: UIView {
private let textLayer = CATextLayer()
@IBInspectable var text: String = "" {
didSet {
updateTextLayerFrame()
}
}
@IBInspectable var fontSize: CGFloat = 17 {
didSet {
updateTextLayerFrame()
}
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
// Text layer
textLayer.borderColor = UIColor.blueColor().CGColor
textLayer.borderWidth = 1
textLayer.contentsScale = UIScreen.mainScreen().scale
layer.addSublayer(textLayer)
}
// MARK: Other methods
override func intrinsicContentSize() -> CGSize {
return textLayer.frame.size
}
func updateTextLayerFrame() {
let myAttribute = [ NSFontAttributeName: UIFont.systemFontOfSize(fontSize) ]
let attrString = NSMutableAttributedString(string: self.text, attributes: myAttribute )
let size = dimensionsForAttributedString(attrString)
textLayer.frame = CGRect(x: self.layer.bounds.origin.x, y: self.layer.bounds.origin.y, width: size.width, height: size.height)
textLayer.string = attrString
}
func dimensionsForAttributedString(attrString: NSAttributedString) -> CGSize {
var ascent: CGFloat = 0
var descent: CGFloat = 0
var width: CGFloat = 0
let line: CTLineRef = CTLineCreateWithAttributedString(attrString)
width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, nil))
// make width an even integer for better graphics rendering
width = ceil(width)
if Int(width)%2 == 1 {
width += 1.0
}
return CGSize(width: width, height: ceil(ascent+descent))
}
}
这里是视图控制器 class:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var normalLabel: UILabel!
@IBOutlet weak var labelFromScratch: UILabelFromScratch!
@IBAction func changeTextButtonTapped(sender: UIButton) {
normalLabel.text = "Hello"
labelFromScratch.text = "Hello"
}
}
问题
UILabel
我的自定义标签中缺少什么?我覆盖了 intrinsicContentSize
:
override func intrinsicContentSize() -> CGSize {
return textLayer.frame.size
}
我还需要做什么?
我遗漏了一行代码:
invalidateIntrinsicContentSize()
我在更新文字层框架后添加的
func updateTextLayerFrame() {
// ...
textLayer.frame = CGRect(x: self.layer.bounds.origin.x, y: self.layer.bounds.origin.y, width: size.width, height: size.height)
invalidateIntrinsicContentSize()
// ...
}
Call this when something changes in your custom view that invalidates
its intrinsic content size. This allows the constraint-based layout
system to take the new intrinsic content size into account in its next
layout pass.
我在哪里找到这个解决方案:
根据内容调整大小的自定义标签
我正在从头开始制作标签。 (我的最终目的是为蒙古文字制作一个垂直文本标签,但目前我只是制作一个普通的水平文本标签作为练习。)
只要没有长度和宽度限制,我希望我的自定义标签的框架根据其固有内容大小调整大小。
在 IB 中有效
在 IB 中,当我测试 UILabel
、我的自定义标签(UIView
subclass)和按钮时,一切似乎都工作正常。
我为每个视图添加了顶部和前导约束,但没有设置任何高度或宽度约束。
如果我在故事板上调整它们的大小(但仍然没有添加任何更多约束)...
然后为视图控制器中的所有视图选择更新帧,普通标签和我的自定义标签都正确调整到它们的固有大小内容大小。
它不适用于 运行 应用程序
但是,当我在运行时更改标签文本时,我的自定义标签的框架没有调整大小。 (我暂时在文本层添加了深蓝色边框,以帮助区分此处自定义标签的框架,后者是浅蓝色背景色。)
点击"Change text"得到
如您所见,文本层框架发生了变化,但自定义视图的框架没有。
代码
这是我的自定义标签class:
import UIKit
@IBDesignable
class UILabelFromScratch: UIView {
private let textLayer = CATextLayer()
@IBInspectable var text: String = "" {
didSet {
updateTextLayerFrame()
}
}
@IBInspectable var fontSize: CGFloat = 17 {
didSet {
updateTextLayerFrame()
}
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
// Text layer
textLayer.borderColor = UIColor.blueColor().CGColor
textLayer.borderWidth = 1
textLayer.contentsScale = UIScreen.mainScreen().scale
layer.addSublayer(textLayer)
}
// MARK: Other methods
override func intrinsicContentSize() -> CGSize {
return textLayer.frame.size
}
func updateTextLayerFrame() {
let myAttribute = [ NSFontAttributeName: UIFont.systemFontOfSize(fontSize) ]
let attrString = NSMutableAttributedString(string: self.text, attributes: myAttribute )
let size = dimensionsForAttributedString(attrString)
textLayer.frame = CGRect(x: self.layer.bounds.origin.x, y: self.layer.bounds.origin.y, width: size.width, height: size.height)
textLayer.string = attrString
}
func dimensionsForAttributedString(attrString: NSAttributedString) -> CGSize {
var ascent: CGFloat = 0
var descent: CGFloat = 0
var width: CGFloat = 0
let line: CTLineRef = CTLineCreateWithAttributedString(attrString)
width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, nil))
// make width an even integer for better graphics rendering
width = ceil(width)
if Int(width)%2 == 1 {
width += 1.0
}
return CGSize(width: width, height: ceil(ascent+descent))
}
}
这里是视图控制器 class:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var normalLabel: UILabel!
@IBOutlet weak var labelFromScratch: UILabelFromScratch!
@IBAction func changeTextButtonTapped(sender: UIButton) {
normalLabel.text = "Hello"
labelFromScratch.text = "Hello"
}
}
问题
UILabel
我的自定义标签中缺少什么?我覆盖了 intrinsicContentSize
:
override func intrinsicContentSize() -> CGSize {
return textLayer.frame.size
}
我还需要做什么?
我遗漏了一行代码:
invalidateIntrinsicContentSize()
我在更新文字层框架后添加的
func updateTextLayerFrame() {
// ...
textLayer.frame = CGRect(x: self.layer.bounds.origin.x, y: self.layer.bounds.origin.y, width: size.width, height: size.height)
invalidateIntrinsicContentSize()
// ...
}
Call this when something changes in your custom view that invalidates its intrinsic content size. This allows the constraint-based layout system to take the new intrinsic content size into account in its next layout pass.
我在哪里找到这个解决方案: