如何从UILabel的第n行获取text / String?

时间:2010-12-12 10:42:16

标签: ios swift uilabel core-text

是否有一种简单的方法可以获取(或简单显示)UILabel中给定行的文本?

我的UILabel正确显示我的文字并将其整理出来,但偶尔我需要能够显示某些行,但显然我需要知道UILabel如何定位所有这些。

我知道这可以通过子字符串轻松完成,但我需要知道该行的起点和终点。

或者我可以滚动UILabel,如果UILabel的框架有某种偏移并隐藏了我不想看到的其他内容。

我无法发现任何可以轻松完成此操作的内容。有人有什么好主意吗?

由于

iphaaw

10 个答案:

答案 0 :(得分:23)

我有更好的方法找到它。

您可以在CoreText.framework的帮助下获得此作品。

1.添加CoreText.framework
2.导入 #import <CoreText/CoreText.h>
然后使用以下方法:

- (NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label {
    NSString *text = [label text];
    UIFont   *font = [label font];
    CGRect    rect = [label frame];

    CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];


    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));

    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);

    NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
    NSMutableArray *linesArray = [[NSMutableArray alloc]init];

    for (id line in lines)
    {
        CTLineRef lineRef = (__bridge CTLineRef )line;
        CFRange lineRange = CTLineGetStringRange(lineRef);
        NSRange range = NSMakeRange(lineRange.location, lineRange.length);

        NSString *lineString = [text substringWithRange:range];
        [linesArray addObject:lineString];
    }

    return (NSArray *)linesArray;
}

调用此方法: -

NSArray *linesArray = [self getLinesArrayOfStringInLabel:yourLabel];

现在您可以使用linesArray

SWIFT 4版本

func getLinesArrayOfString(in label: UILabel) -> [String] {

        /// An empty string's array
        var linesArray = [String]()

        guard let text = label.text, let font = label.font else {return linesArray}

        let rect = label.frame

        let myFont: CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value: myFont, range: NSRange(location: 0, length: attStr.length))

        let frameSetter: CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path: CGMutablePath = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: rect.size.width, height: 100000), transform: .identity)

        let frame: CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else {return linesArray}

        for line in lines {
            let lineRef = line as! CTLine
            let lineRange: CFRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            let lineString: String = (text as NSString).substring(with: range)
            linesArray.append(lineString)
        }
        return linesArray
 }

使用:

let lines: [String] = getLinesArrayOfString(in: label)

答案 1 :(得分:8)

Swift 3

func getLinesArrayFromLabel(label:UILabel) -> [String] {

        let text:NSString = label.text! as NSString // TODO: Make safe?
        let font:UIFont = label.font
        let rect:CGRect = label.frame

        let myFont:CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
        let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
        attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSMakeRange(0, attStr.length))
        let frameSetter:CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path:CGMutablePath = CGMutablePath()
        path.addRect(CGRect(x:0, y:0, width:rect.size.width, height:100000))

        let frame:CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        let lines = CTFrameGetLines(frame) as NSArray
        var linesArray = [String]()

        for line in lines {
            let lineRange = CTLineGetStringRange(line as! CTLine)
            let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
            let lineString = text.substring(with: range)
            linesArray.append(lineString as String)
        }
        return linesArray
}

Swift 2(Xcode 7)版本(测试过,并从Swift 1回答中重新编辑)

func getLinesArrayOfStringInLabel(label:UILabel) -> [String] {

let text:NSString = label.text! // TODO: Make safe?
let font:UIFont = label.font
let rect:CGRect = label.frame

let myFont:CTFontRef = CTFontCreateWithName(font.fontName, font.pointSize, nil)
let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSMakeRange(0, attStr.length))
let frameSetter:CTFramesetterRef = CTFramesetterCreateWithAttributedString(attStr as CFAttributedStringRef)
let path:CGMutablePathRef = CGPathCreateMutable()
CGPathAddRect(path, nil, CGRectMake(0, 0, rect.size.width, 100000))
let frame:CTFrameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
let lines = CTFrameGetLines(frame) as NSArray
var linesArray = [String]()

for line in lines {
    let lineRange = CTLineGetStringRange(line as! CTLine)
    let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
    let lineString = text.substringWithRange(range)
    linesArray.append(lineString as String)
}
return linesArray
}

答案 2 :(得分:5)

以正确的版本回答!!!!

-(NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label
{
    NSString *text = [label text];
    UIFont   *font = [label font];
    CGRect    rect = [label frame];

    CTFontRef myFont = CTFontCreateWithName(( CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    [attStr addAttribute:(NSString *)kCTFontAttributeName value:( id)myFont range:NSMakeRange(0, attStr.length)];

    CFRelease(myFont);

    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(( CFAttributedStringRef)attStr);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));

    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);

    NSArray *lines = ( NSArray *)CTFrameGetLines(frame);

    NSMutableArray *linesArray = [[NSMutableArray alloc]init];

    for (id line in lines)
    {
        CTLineRef lineRef = ( CTLineRef )line;
        CFRange lineRange = CTLineGetStringRange(lineRef);
        NSRange range = NSMakeRange(lineRange.location, lineRange.length);

        NSString *lineString = [text substringWithRange:range];

        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithFloat:0.0]));
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithInt:0.0]));

        //NSLog(@"''''''''''''''''''%@",lineString);
        [linesArray addObject:lineString];

    }
    [attStr release];

    CGPathRelease(path);
    CFRelease( frame );
    CFRelease(frameSetter);


    return (NSArray *)linesArray;
}

答案 3 :(得分:4)

我不认为有这样的本地方式(如“takethenline”方法) 我可以找出一个棘手的解决方案,但我不确定是最好的解决方案 您可以将标签拆分为一系列单词 然后你可以循环数组并检查文本高度,直到这个词如下:

NSString *texttocheck;
float old_height = 0;
int linenumber = 0; 

for (x=0; x<[wordarray lenght]; x++) {
    texttocheck = [NSString stringWithFormat:@"%@ %@", texttocheck, [wordarray objectAtIndex:x]];

    float height = [text sizeWithFont:textLabel.font
                    constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
                        lineBreakMode:UILineBreakModeWordWrap].height;

    if (old_height < height) {
        linenumber++;
    }
}

如果高度发生变化,则表示在单词前有换行符 我现在无法检查语法是否正确写入,因此您必须自己检查。

答案 4 :(得分:2)

这是获取标签中所有行的Swift 3版本。 (@fredpi有类似的答案,但它只适用于第一行)

extension UILabel {

    func getArrayOfLinesInLabel() -> [String] {

       let text = NSString(string: self.text ?? "-- -- -- --")
       let font = self.font ?? // Your default font here
       let rect = self.frame

       let myFont = CTFontCreateWithName(font.fontName as CFString?, font.pointSize, nil)
       let attStr = NSMutableAttributedString(string: text as String)
       attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSRange(location: 0, length: attStr.length))
       let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
       let path = CGPath(rect: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height), transform: nil)
       let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
       guard let lines = CTFrameGetLines(frame) as? [CTLine] else {
           return []
       }

       var linesArray = [String]()

       for line in lines {
           let lineRange = CTLineGetStringRange(line)
           let range = NSRange(location: lineRange.location, length: lineRange.length)
           let lineString = text.substring(with: range)
           linesArray.append(lineString as String)
       }

       return linesArray
   }
}

答案 5 :(得分:1)

Swift 3 - Xcode 8.1

我已将先前答案中的代码放在一起,以创建一个Swift 3,兼容Xcode 8.1 extensionUILabel,返回标签的第一行。

import CoreText

extension UILabel {

   /// Returns the String displayed in the first line of the UILabel or "" if text or font is missing
   var firstLineString: String {

    guard let text = self.text else { return "" }
    guard let font = self.font else { return "" }
    let rect = self.frame

    let attStr = NSMutableAttributedString(string: text)
    attStr.addAttribute(String(kCTFontAttributeName), value: CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil), range: NSMakeRange(0, attStr.length))

    let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
    let path = CGMutablePath()
    path.addRect(CGRect(x: 0, y: 0, width: rect.size.width + 7, height: 100))
    let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)

    guard let line = (CTFrameGetLines(frame) as! [CTLine]).first else { return "" }
    let lineString = text[text.startIndex...text.index(text.startIndex, offsetBy: CTLineGetStringRange(line).length-2)]

    return lineString
  }
}

要使用它,请在firstLineString个实例上简单地调用UILabel,如下所示:

let firstLine = myLabel.firstLineString

答案 6 :(得分:1)

关于iOS 11+的非常重要的更改

从iOS 11开始,Apple故意更改了UILabel自动换行功能的行为,这会检测多行{{ 1}}。通过设计,String的自动换行现在可以避免孤立的文本(在新行中包含单个单词),如下所述:word wrapping in iOS 11

因此,如果避免孤立文本的新换行在特定行中生效,则UILabel返回标签中所有行的UILabel数组的方式将不再正确工作。相反,它导致CTFrameGetLines(frame)的某些部分通过新的自动换行设计将属于下一行,而不是最终成为焦点所在的行。

可以在我更改后的@TheTiger's answer版本中找到经过测试的修复程序,该版本利用CTLine计算String的实际内容大小,然后再使用该大小创建用 Swift 4

编写的rect /路径
UILabel

此扩展名sizeThatFits(size:)extension UILabel { /// creates an array containing one entry for each line of text the label has var lines: [String]? { guard let text = text, let font = font else { return nil } let attStr = NSMutableAttributedString(string: text) attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length)) let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString) let path = CGMutablePath() // size needs to be adjusted, because frame might change because of intelligent word wrapping of iOS let size = sizeThatFits(CGSize(width: self.frame.width, height: .greatestFiniteMagnitude)) path.addRect(CGRect(x: 0, y: 0, width: size.width, height: size.height), transform: .identity) let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, nil) guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil } var linesArray: [String] = [] for line in lines { let lineRef = line as! CTLine let lineRange = CTLineGetStringRange(lineRef) let range = NSRange(location: lineRange.location, length: lineRange.length) let lineString = (text as NSString).substring(with: range) linesArray.append(lineString) } return linesArray } } 数组的形式返回标签​​的内容,每行有一个与用户所看到的完全相同的条目。

答案 7 :(得分:0)

如果所有字符都以相同的大小显示,即它们被包含在一个常见大小的框中,您可以利用它。 (例如,日语字符就是这种情况。)

否则,您可以查询显示字体中每个字符的大小,并计算该行必须是什么。

唯一的担心是你的计算可能不同意Apple在幕后所做的事情 - 在这种情况下,我建议你去覆盖文本框架绘图的麻烦。在文档中查找核心文本。

(我可能这样做错了,但我没有发现Apple的方法在文档中给出的非常准确,所以我自己做了别的事。)

答案 8 :(得分:0)

接受的答案非常好。

我重构了两个地方:

  1. 10000 更改为CGFloat.greatestFiniteMagnitude

  2. 已将其添加到extension的{​​{1}}

  3. 我也想提及一下,如果您通过设置框架来创建标签,则效果很好。如果您使用自动版式,请不要忘记拨打电话

    UILabel

获取正确的帧大小。

代码如下:

youLabel.layoutIfNeeded()

答案 9 :(得分:0)

对不起,我的声誉太低,无法发表评论。 这是Philipp Jahoda对https://stackoverflow.com/a/53783203/2439941的评论。

您的代码段完美运行,直到我们在UILabel上启用了动态类型。当我们在iOS设置应用中将文本大小设置为最大值时,它开始丢失返回数组最后一行中的字符。甚至完全缺少大量文本的最后一行。

我们设法通过另一种方式获得frame来解决此问题:

let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: .greatestFiniteMagnitude))
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path.cgPath, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }

现在,它可以在任何动态类型大小下正常工作。

然后是完整功能:

extension UILabel {

    /// creates an array containing one entry for each line of text the label has
    var lines: [String]? {

        guard let text = text, let font = font else { return nil }

        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length))

        let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: .greatestFiniteMagnitude))
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path.cgPath, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }

        var linesArray: [String] = []

        for line in lines {
            let lineRef = line as! CTLine
            let lineRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            let lineString = (text as NSString).substring(with: range)
            linesArray.append(lineString)
        }
        return linesArray
    }
}