如何使自定义视图的帧与其内在内容大小相匹配

时间:2016-04-02 08:43:12

标签: ios swift autolayout uilabel intrinsic-content-size

根据其内容调整大小的自定义标签

我正在从头开始做一个标签。 (我的最终目的是为蒙古语脚本制作一个垂直文本标签,但是现在我只是将一个普通的水平文本标签作为练习。)

只要没有长度和宽度限制,我希望自定义标签的框架根据其内在内容大小调整大小。

它适用于IB

在IB中,当我测试UILabel,我的自定义标签(UIView子类)和按钮时,一切似乎都运行正常。

我为每个视图添加了顶部和前导约束,但没有设置任何高度或宽度约束。

enter image description here

如果我在故事板上调整它们的大小(但仍然没有添加任何限制)...

enter image description here

然后为视图控制器中的所有视图选择更新框架,普通标签和我的自定义标签都会正确调整其内在内容大小。

enter image description here

它在正在运行的应用程序上不起作用

但是,当我在运行时更改标签文本时,我的自定义标签的框架没有调整大小。 (我暂时在文本图层中添加了深蓝色边框,以帮助将其与自定义标签的框架区分开来,这是浅蓝色背景颜色。)

enter image description here

点击“更改文字”会显示

enter image description here

如您所见,文本图层框架已更改,但自定义视图框架未更改。

代码

这是我的自定义标签类:

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))
    }
}

这是视图控制器类:

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
}

我还需要做什么?

1 个答案:

答案 0 :(得分:3)

我错过了一行代码:

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()

    // ...
}

documentation

  

当自定义视图中的某些内容发生更改而无效时,请调用此方法   其内在的内容大小。这允许基于约束的布局   系统将在下一个内容中考虑新的内在内容大小   布局通行证。

我找到了这个解决方案: