How to get text / String from nth line of UILabel?

Is there an easy way to get (or simply display) the text from a given line in a UILabel?

My UILabel is correctly displaying my text and laying it out beautifully but occasionally I need to be able to just show certain lines but obviously I need to know how UILabel has positioned everything to do this.

I know this could easily be done with a substring but I'd need to know the start and end point of the line.

Alternatively I could scroll the UILabel if there was some kind of offset to the UILabel's frame and hide the rest of the content I didn't want to see.

I've not been able to uncover anything that shows how this could be done easily. Anyone got any good ideas?

Thanks

iphaaw


Solution 1:

I have better way to find it.

You can get this with the help of CoreText.framework.

1.Add CoreText.framework.
2.Import #import <CoreText/CoreText.h>.
Then use below method:

- (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;
}

Call this method :-

NSArray *linesArray = [self getLinesArrayOfStringInLabel:yourLabel];

Now you can use linesArray.

SWIFT 4 VERSION

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 = CTFontCreateWithFontDescriptor(font.fontDescriptor, 0, 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
 }

Use:

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

Solution 2:

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) version (tested, and re-edited from the Swift 1 answer)

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
}

Solution 3:

Answer with Proper release !!!!

-(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;
}