Search code examples
iphoneiosuilabelcore-text

CoreText manual line breaking: How to position the text, vertical alignment?



I'm trying to create a custom label subclass that draws rich text and in which I can set different parameters. One of the most huge request I have is about line breaking. In UILabel is fixed and sometimes it doesn't fit the graphics requirements.
Thus, helping myself with a little snippet on Apple site, I've started writing my own classes, and it works (somehow), but I'm having one problems:

  1. If the line is only one, the text is not aligned vertically, it always starts a the top left of the screen

Here is the code I've written so far:

- (void)drawRect:(CGRect)rect
{
    if (_text) {

        if (!_attribstring) {
            [self createAttributedString];
        }
        if (self.lineBreakValue == 0) {
            self.lineBreakValue = 9;
        }

        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
        CGContextScaleCTM(ctx, 1.0, -1.0); 
        CGContextSetShouldSmoothFonts(ctx, YES);
        CGContextSetShouldAntialias(ctx, YES);
        CGRect textRect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);

        //Manual Line braking
        BOOL shouldDrawAnotherLine = YES;
        double width = textRect.size.width;
        CGPoint textPosition = CGPointMake(textRect.origin.x, textRect.origin.y+textRect.size.height-self.lineBreakValue);
        ;
        // Initialize those variables.

        // Create a typesetter using the attributed string.
        CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((__bridge CFAttributedStringRef )  self.attribstring);

        // Find a break for line from the beginning of the string to the given width.
        CFIndex start = 0;
        while (shouldDrawAnotherLine) {

            CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);

            // Use the returned character count (to the break) to create the line.
            CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));

            // Get the offset needed to center the line.
            float flush = 0.5; // centered
            double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);

            // Move the given text drawing position by the calculated offset and draw the line.
            CGContextSetTextPosition(ctx, textPosition.x + penOffset, textPosition.y);
            CTLineDraw(line, ctx);
            if (line!=NULL) {
                CFRelease(line);
            }
            // Move the index beyond the line break.

            if (start + count >= [self.attribstring.string length]) {
                shouldDrawAnotherLine = NO;
                continue;
            }
            start += count;
            textPosition.y-=self.lineBreakValue;
        }
        if (typesetter!=NULL) {
            CFRelease(typesetter);

        }

    }

}

Can someone point me to the right direction? Regards,
Andrea


Solution

  • For laying out text vertically aligned, you need to know the total height of the lines, and the y position of the first line would be:

    (self.bounds.size.height - (totalLineHeight)) / 2 - font.ascent;
    

    So you need 2 loops in your code, one for calculating the total height of the lines(you can also save char count of each line for later use in another loop for drawing), another certainly for drawing the lines starting from the y position calculated using the above formula.

    Note: Height of each line can be the font size, you can also add line spacing between lines, all you need to ensure is that being consistent in calculating the line height and drawing those lines in regards of the y position.