ioscore-textnslayoutmanagernstextcontainer

Computing text size with NSLayoutManager


I'm working on a view which uses TextKit framework to typeset text in columns like this: Column Text Layout

I use my UIView's bounds with edge insets (black rectangle) to compute 10 CGRects which I then transform into NSTextContainers (red rectangles). In drawRect: I pass those to the NSLayoutManager which typesets and draws the glyphs for me.

My question is: How can I compute the number of columns required? I can draw a constant number of columns but with varying text lengths, I need to adjust the number of columns programmatically.

I found the method [NSLayoutManager glyphRangeForTextContainer:] which returns range of length 0 when the text container is to be left empty. Therefore, I could loop to create text containers and use this method to determine if more containers are needed. However, this method is said to be inefficient as it triggers the layout computation and I'm not happy running it in a loop perhaps hundreds of times over.

There has to be a better way!

Thanks for your answers, Pete.


Solution

  • Well, after some digging through the TextKit framework I've finally found the answer.

    My code works in a loop like this:

    while ([self needsMoreColumns]) {
        [self addColumn];
    }
    
    ...
    
    - (BOOL)needsMoreColumns {
        // Always create at least one column
        if (self.layoutManager.textContainers.count == 0)
            return YES;
    
        // Find out the glyph range of the last column
        NSRange range = [self.layoutManager glyphRangeForTextContainer:[self.layoutManager.textContainers lastObject]];
        NSUInteger glyphs = [self.layoutManager numberOfGlyphs];
    
        // Compare it with the number of glyphs
        return range.location + range.length < glyphs;
    }
    

    I didn't include the method [self addColumn] as it's a no brainer. It simply uses the geometry of my layout and position of the last column (if any) to compute the CGRect of the next one. Then, it creates NSTextContainer with respective size and stores the origin property of the rectangle in a dedicated array for drawing purposes.

    I've also discovered methods [NSLayoutManager firstUnlaidCharacterIndex] and [NSLayoutManager firstUnlaidGlyphIndex] but they don't seem to work as expected. After laying out three columns worth of text in only one column, they returned the length of the entire string and not the position of the first character which didn't fit into the first column. That's why I rather used the range-based approach.

    That's all folks, be safe! Pete.