多行UILabel:使用boundingRect()

时间:2019-06-13 16:32:03

标签: swift uilabel multiline

我希望仅在多行标签的某些部分/单词上添加自定义下划线。

我尝试将NSAttributedString.Key.underlineStyleNSMutableAttributedString一起使用,但是我无法访问重要的参数,例如文本和下划线之间的间距或下划线条的高度。

因此,我正在考虑在预期文本下方的UILabel中添加一个子层。到目前为止,这是我所做的:

extension UILabel {

    func underline(word: String, withColor color: UIColor) {
        guard let text = self.text else {
            return
        }
        if let range = text.range(of: word) {
            let start = text.distance(from: text.startIndex, to: range.lowerBound)
            let end = text.distance(from: text.startIndex, to: range.upperBound)
            if let rect = self.boundingRectFor(characterRange: start..<end) {
                let c = CALayer()
                c.frame = CGRect(origin: CGPoint(x: rect.origin.x, y: rect.size.height + (self.font.pointSize / 8)), size: CGSize(width: rect.size.width, height: (self.font.pointSize / 4)))
                c.backgroundColor = color.cgColor
                self.layer.addSublayer(c)
            }
        }
    }

    private func boundingRectFor(characterRange: Range<Int>) -> CGRect? {
        guard let attributedText = attributedText else { return nil }
        let textStorage = NSTextStorage(attributedString: attributedText)
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        let textContainer = NSTextContainer(size: intrinsicContentSize)
        textContainer.lineFragmentPadding = 0.0
        layoutManager.addTextContainer(textContainer)
        var glyphRange = NSRange()
        layoutManager.characterRange(forGlyphRange: NSRange(location: characterRange.startIndex, length: characterRange.endIndex - characterRange.startIndex), actualGlyphRange: &glyphRange)
        return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
    }

}

boundingRectFor(characterRange)是我发现的一种方法,有望返回包含作为参数传递的范围内的子字符串的rect。

underline(word: String, withColor color: UIColor)将在标签文本中查找给定单词的第一个匹配项,并在其下划线。

在视图控制器中的简单使用:

class ViewController: UIViewController {

    @IBOutlet var labels: [UILabel]!

    var texts: [String] = ["Lorem ipsum dolor sit amet.", 
                           "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 
                           "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."]

    var textToUnderline = ["ipsum", "amet", "incididunt"]

    var underliningColors: [UIColor] = [.red, .blue, .purple]

    @IBOutlet weak var firstLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.labels.enumerated().forEach { index, label in
            label.text = texts[index]
            label.underline(word: textToUnderline[index], withColor: underliningColors[index])
        }
    }

}

这是结果的屏幕截图:

enter image description here

尽管这对于第一行来说很好用,但是当我试图在另一行下划线时我就不再工作了。

为了更好地理解,我尝试在if let rect(扩展名为UILabel)下打印一些值:

// will print the label frame and the rect to underline
print("frame: \(self.frame) and rect: \(rect)") 

这就是我得到的:

  

帧:(24.0、68.0、366.0、0.0)和矩形:(34.0576171875,0.0,   30.3369140625,11.93359375)

     

帧:(24.0,100.0,366.0,0.0),矩形:(176.35009765625,0.0,40.375,20.287109375)

     

帧:(24.0、124.0、366.0、0.0)和矩形:(572.20458984375、0.0、71.14013671875、17.900390625)

我意识到最后一个标签rect(572.2 ...)的origin.x超过了宽度(366),但是我很难理解为什么会得到这个结果以及如何改善我的功能。

谢谢您的帮助。

1 个答案:

答案 0 :(得分:3)

您的整个方法是错误的。使用文本视图而不是标签,以便您可以访问全文本工具包堆栈(NSLayoutManager,NSTextStorage,NSTextContainer)。使用自定义布局管理器子类构造堆栈,并覆盖 if (idiom == DeviceIdiom.Phone) { var grid = new GridItemsLayout(ItemsLayoutOrientation.Vertical) { Span = 3, }; collectionViewItemsLayout.SetValue(CollectionView.ItemsLayoutProperty, grid); }