如何在UILabel中找到文本子字符串的CGRect?

bry*_*ark 69 uilabel ios ios7 textkit

对于给定的NSRange,我想找到一个CGRectUILabel对应的的字形NSRange.例如,我想CGRect在"快速的棕色狐狸跳过懒狗"这句话中找到包含"狗"字样的字样.

问题的视觉描述

诀窍是,UILabel有多行,而文本确实attributedText如此,所以找到字符串的确切位置有点困难.

我想在我的UILabel子类上写的方法看起来像这样:

 - (CGRect)rectForSubstringWithRange:(NSRange)range;
Run Code Online (Sandbox Code Playgroud)

细节,对于有兴趣的人:

我的目标是能够创建一个具有UILabel的确切外观和位置的新UILabel,然后我可以制作动画.我已经把剩下的事情搞清楚了,但特别是这一步让我暂时退缩了.

到目前为止我尝试解决问题的方法是:

  • 我希望在iOS 7中,有一些Text Kit可以解决这个问题,但是我用Text Kit看到的大多数例子都集中在UITextViewUITextField而不是UILabel.
  • 我在这里看到了有关Stack Overflow的另一个问题,它承诺解决问题,但是已接受的答案已经超过两年了,并且代码在属性文本中表现不佳.

我敢打赌,对此的正确答案涉及以下其中一项:

  • 使用标准的Text Kit方法在一行代码中解决此问题.我打赌它会涉及NSLayoutManagertextContainerForGlyphAtIndex:effectiveRange
  • 编写一个复杂的方法,将UILabel分成几行,并在一行中找到一个字形的矩形,可能使用Core Text方法.我目前最好的选择是拆开@mattt优秀的TTTAttributedLabel,它有一个方法可以在一个点上找到一个字形 - 如果我将其反转,并找到一个可能有效的字形点.

更新:这是一个github要点,我已经尝试了迄今为止解决这个问题的三件事:https://gist.github.com/bryanjclark/7036101

Luk*_*ers 88

按照约书亚在代码中的答案,我想出了以下似乎运作良好的:

- (CGRect)boundingRectForCharacterRange:(NSRange)range
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
    textContainer.lineFragmentPadding = 0;
    [layoutManager addTextContainer:textContainer];

    NSRange glyphRange;

    // Convert the range for glyphs.
    [layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];

    return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}
Run Code Online (Sandbox Code Playgroud)

  • 很酷!但是,如果字体已自动缩小以适合当前视图大小,则应该意识到这不能正常工作.您需要相应地调整字体大小. (9认同)
  • 很好的答案.由于UILabel没有左边距,请确保添加textContainer.lineFragmentPadding = 0; (3认同)
  • 很棒,但是解决方案没有考虑到attributionText的对齐方式. (2认同)

Noo*_*ath 16

建立卢克罗杰斯的答案,但写得很快:

斯威夫特2

extension UILabel {
    func boundingRectForCharacterRange(_ range: NSRange) -> CGRect? {

        guard let attributedText = attributedText else { return nil }

        let textStorage = NSTextStorage(attributedString: attributedText)
        let layoutManager = NSLayoutManager()

        textStorage.addLayoutManager(layoutManager)

        let textContainer = NSTextContainer(size: bounds.size)
        textContainer.lineFragmentPadding = 0.0

        layoutManager.addTextContainer(textContainer)

        var glyphRange = NSRange()

        // Convert the range for glyphs.
        layoutManager.characterRangeForGlyphRange(range, actualGlyphRange: &glyphRange)

        return layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)        
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例(Swift 2)

let label = UILabel()
let text = "aa bb cc"
label.attributedText = NSAttributedString(string: text)
let sublayer = CALayer()
sublayer.borderWidth = 1
sublayer.frame = label.boundingRectForCharacterRange(NSRange(text.range(of: "bb")!, in: text))
label.layer.addSublayer(sublayer)
Run Code Online (Sandbox Code Playgroud)

斯威夫特3/4

extension UILabel {
    func boundingRect(forCharacterRange range: NSRange) -> CGRect? {

        guard let attributedText = attributedText else { return nil }

        let textStorage = NSTextStorage(attributedString: attributedText)
        let layoutManager = NSLayoutManager()

        textStorage.addLayoutManager(layoutManager)

        let textContainer = NSTextContainer(size: bounds.size)
        textContainer.lineFragmentPadding = 0.0

        layoutManager.addTextContainer(textContainer)

        var glyphRange = NSRange()

        // Convert the range for glyphs.
        layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)

        return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)       
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例(Swift 3/4)

let label = UILabel()
let text = "aa bb cc"
label.attributedText = NSAttributedString(string: text)
let sublayer = CALayer()
sublayer.borderWidth = 1
sublayer.frame = label.boundingRect(forCharacterRange: NSRange(text.range(of: "bb")!, in: text))
label.layer.addSublayer(sublayer)
Run Code Online (Sandbox Code Playgroud)

  • 重要的是要注意你的UILabel的属性文本必须具有键NSAttributedStringKey.paragraphStyle和NSAttributedStringKey.font设置的属性,以便该函数可以解释该文本对齐和字体 (3认同)
  • @Hemang示例用法:`let t ="aa bb cc"; label.attributedText = NSAttributedString(string:t); let l = CALayer(); l.borderWidth = 1; l.frame = label.boundingRectForCharacterRange(NSRange(t.range(of:"bb")!, in:t)); label.layer.addSublayer(升);` (2认同)

Jos*_*hua 11

我的建议是使用Text Kit.遗憾的是,我们无法访问使用的布局管理器,UILabel但是可以创建它的副本并使用它来获取范围的矩形.

我的建议是创建一个NSTextStorage对象,其中包含与标签中完全相同的属性文本.接下来创建一个NSLayoutManager并将其添加到文本存储对象.最后创建一个NSTextContainer与标签大小相同的文件并将其添加到布局管理器中.

现在文本存储与标签具有相同的文本,文本容器与标签的大小相同,因此我们应该能够询问我们为我们的范围使用的rect创建的布局管理器boundingRectForGlyphRange:inTextContainer:.确保首先使用glyphRangeForCharacterRange:actualCharacterRange:布局管理器对象将字符范围转换为字形范围.

一切顺利,应该给你一个你在标签中指定的范围的边界 CGRect.

我没有对此进行过测试,但这将是我的方法,并通过模仿UILabel自身的工作原理应该有很大的成功机会.


小智 5

swift 4 解决方案,甚至适用于多行字符串,边界被替换为内在内容大小

extension UILabel {
func boundingRectForCharacterRange(range: NSRange) -> 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: range, actualGlyphRange: &glyphRange)

    return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
}
Run Code Online (Sandbox Code Playgroud)