多行 UILabel:使用 boundingRect() 向标签的特定部分添加自定义下划线

Multiline UILabel: Adding custom underlines to specific parts of the label using boundingRect()

我希望能够为多行标签的某些 parts/words 添加自定义下划线。

我尝试将 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) 是我发现的一种方法,它预期 return 包含来自作为参数传递的范围的子字符串的 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])
        }
    }

}

这是结果的屏幕截图:

虽然这在第一行时工作正常,但在尝试为另一行加下划线时我不再工作了。

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

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

这是我得到的:

frame: (24.0, 68.0, 366.0, 0.0) and rect: (34.0576171875, 0.0, 30.3369140625, 11.93359375)

frame: (24.0, 100.0, 366.0, 0.0) and rect: (176.35009765625, 0.0, 40.375, 20.287109375)

frame: (24.0, 124.0, 366.0, 0.0) and rect: (572.20458984375, 0.0, 71.14013671875, 17.900390625)

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

感谢您的帮助。

你的整个做法都是错误的。使用文本视图,而不是标签,这样您就可以访问完整的文本工具包堆栈(NSLayoutManager、NSTextStorage、NSTextContainer)。使用自定义布局管理器子类构造堆栈并覆盖 drawBackground(forGlyphRange).