Counting Lines of Text

This task shows how to count the number of lines in a block of text programmatically. Lines can be defined by hard linebreak characters in the text string, or they can be lines generated by the text layout mechanism when it wraps the text to fit into a text container.

Lines of text defined by hard linebreak characters, such as carriage return and newline, are considered to be paragraphs by the text system. That is, the text layout engine generates line fragments sized to fit into a text container until it reaches a hard linebreak character, so the last fragment is typically shorter than the container width. However, if lines are laid out into a text container wider than the longest glyph run between hard linebreak characters, then each paragraph is a single line.

To count the number of hard linebreak characters in a text string, you can use the NSString methods getLineStart:end:contentsEnd:forRange: and lineRangeForRange:. These methods take a character range as input and return the lines that contain that range. They define lines as character ranges ending in carriage return, newline, carriage return and newline together (in that order, often called CRLF), and the Unicode characters for line separator and paragraph separator. For example, the code in Listing 1 puts into numberOfLines the number of lines in an NSString variable string.

Listing 1  Counting hard line breaks

NSString *string;
unsigned numberOfLines, index, stringLength = [string length];
for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
    index = NSMaxRange([string lineRangeForRange:NSMakeRange(index, 0)]);

This compact code fragment begins by making a range containing only the first character in string. The lineRangeForRange: method returns the line containing that character as a range that includes the hard linebreak character (or characters). The NSMaxRange function returns the index of the first character in the next line, which is the value assigned to index. The numberOfLines variable increments, and the for loop repeats, until index is greater than the length of string, at which point numberOfLines contains the number of lines in string, as defined by hard linebreak characters.

To count the lines generated by the text layout mechanism when it wraps the text to fit into a text container, you can get the information from the layout manager, as shown in Listing 2.

Listing 2  Counting lines of wrapped text

NSLayoutManager *layoutManager = [textView layoutManager];
unsigned numberOfLines, index, numberOfGlyphs =
        [layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
    (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
            effectiveRange:&lineRange];
    index = NSMaxRange(lineRange);
}

This code assumes you have a reference to a text view configured with a layout manager, text storage, and text container. The text view returns a reference to the layout manager, which then returns the number of glyphs for all the characters in its associated text storage, performing glyph generation if necessary. The for loop then begins laying out the text and counting the resulting line fragments. The NSLayoutManager method lineFragmentRectForGlyphAtIndex:effectiveRange: forces layout of the line containing the glyph at the index passed to it. The method returns the rectangle occupied by the line fragment (here ignored) and, by reference, the range of the glyphs in the line after layout. After the method calculates a line, the NSMaxRangefunction returns the index one greater than the maximum value in the range, that is, the index of the first glyph in the next line. The numberOfLines variable increments, and the for loop repeats, until index is greater than the number of glyphs in the text, at which point numberOfLines contains the number of lines resulting from the layout process, as defined by word wrapping.

This strategy causes layout of the entire text contained in the text storage object and calculates the number of lines required to lay it out, regardless of the number of text containers filled or text views required to display it. To obtain the number of lines in a single page (which is modeled by a text container), you would use the NSLayoutManager method glyphRangeForTextContainer: and restrict the line-counting for loop to that range, rather than the {0, numberOfGlyphs} range, which includes all of the text.