1

I am trying to get the range of visible characters in a UILabel. So for example if I have the text

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

and my UILabel is only set to width and height 100,100 is there a predefined method in Objective C to get the range of the visible characters only?

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ...

user3573256
  • 219
  • 1
  • 2
  • 12
  • This may have been answered here: http://stackoverflow.com/questions/4100421/uilabel-visible-part-of-text And another related solution: http://stackoverflow.com/questions/3520458/how-to-know-the-displayed-text-in-uilabel I hope that helps! – ekscrypto Sep 17 '14 at 17:37

1 Answers1

0

The other answers have pointed out that there are possible approximations for how much text fits, there's no real way to get the actual text rendered by a UILabel because the it doesn't expose any outputs of the rendering process.

If you render your text through something like TTTAttributedLabel, you can get a look inside the rendering process and inject some code to get information about the text that was rendered.

For example, -[TTTAttributedLabel drawFramesetter:attributedString:textRange:inRect:context:] renders the actual text with CoreText by generating a CTFrameRef. CTFrameRef is tasked with laying out text and reporting what text fit and what didn't.

Ignoring truncation:

If you don't care about the fact you're losing characters to truncation, you can call CTFrameGetVisibleStringRange to get the range of characters that are displayed. The idea behind this function is that things like multi-column text layouts need to know where to begin on their next frame:

CFRange actuallyRenderedRange = CTFrameGetVisibleStringRange(frame);
NSString *actuallyRenderedText = [attributedString.string substringWithRange:NSMakeRange(actuallyRenderedRange.location, actuallyRenderedRange.length)];

Considering truncation:

Truncation is complex and a black box with regards to CoreText. For non-truncated lines, the the label can just render the line from CTFrameGetLines. For truncated lines, it must use CTLineCreateTruncatedLine, specifying the truncation line, the truncation point and the truncation token (...). Once you have this line, however, there's no direct way to get what text was chopped off since it could happen at the start, end, or middle of the line. See https://stackoverflow.com/a/5672594/860000.

If you assume end, you can force the truncation to be its own glyph run and get the end position of the second to last text run. If you compare that with the number of characters removed you can get the new string:

  1. Add a dummy attribute to the truncation string:

    NSMutableDictionary *truncationTokenStringAttributes = [self.truncationTokenStringAttributes mutableCopy];
    if (!truncationTokenStringAttributes) {
        truncationTokenStringAttributes = [[attributedString attributesAtIndex:(NSUInteger)truncationAttributePosition effectiveRange:NULL] mutableCopy];
    }
    truncationTokenStringAttributes[@"DummyAttributeToForceNewRun"] = @1;
    
  2. After truncatedLine is created compare the second to last run end position to the normal line length:

    CFArrayRef truncatedLineRuns = CTLineGetGlyphRuns(truncatedLine);
    CFIndex truncatedLineRunCount = CFArrayGetCount(truncatedLineRuns);
    CFIndex numberOfCharactersRemoved;
    CFIndex untruncatedLineLength = CTLineGetStringRange(line).length;
    if (truncatedLineRunCount > 1) {
        CTRunRef lastRealRun = CFArrayGetValueAtIndex(truncatedLineRuns, truncatedLineRunCount - 2);
        CFRange lastRunRange = CTRunGetStringRange(lastRealRun);
        numberOfCharactersRemoved = untruncatedLineLength - (lastRunRange.length + lastRunRange.location);
    } else {
        numberOfCharactersRemoved = untruncatedLineLength; // Everything was removed.
    }
    
    if (numberOfCharactersRemoved > 0) {
        actuallyRenderedText = [actuallyRenderedText substringToIndex:actuallyRenderedText.length - numberOfCharactersRemoved];
    }
    
    actuallyRenderedText = [actuallyRenderedText stringByAppendingString:truncationTokenString];
    

By the time the function completes actuallyRenderedText will contain the actual text that was rendered, including the truncation symbol. I've tried this for a variety of strings and this has come up with a consistent result.

Community
  • 1
  • 1
Brian Nickel
  • 26,890
  • 5
  • 80
  • 110