How to calculate Frame size to draw CoreText by line

core-text, ios, ipad, iphone, objective-c

Solution

For some reasons seems that `(ascent + descent)` isn't the correct height for a line. In fact ascender + 1 + descender is equal to the Point Size.

Simply changing this:

CGContextSetTextPosition(context, 0.0, idx*-(ascent + descent)-ascent);

into this:

CGContextSetTextPosition(context, 0.0, idx*-(ascent - 1 + descent)-ascent);

did the trick. Reference: Cocoanetics article about UIFont. Thanks to jverrijt for the hint!

Problem

I am trying to dynamically create book pages based on a long `NSAttributedString` splitted into pieces. What I am doing now is using this category for `NSAttributedString`: ``` @interface NSAttributedString (Height) - (CGFloat)boundingHeightForWidth:(CGFloat)inWidth; @end @implementation NSAttributedString (Height) - (CGFloat)boundingHeightForWidth:(CGFloat)inWidth { CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)self); CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(inWidth, 10000), NULL); CFRelease(framesetter); return suggestedSize.height ; } @end ``` Since I was drawing the text using a `CTFramesetter`, this worked fine and the correct height for the box was correctly returned. Unfortunately now I need to draw the text line by line: ``` -(void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); // Flip the coordinate system CGContextSetTextMatrix(context, CGAffineTransformIdentity); //CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); NSArray *lines = (__bridge NSArray *)(CTFrameGetLines(ctFrame)); CFIndex lineCount = [lines count]; for(CFIndex idx = 0; idx < lineCount; idx++) { // For each line found from where it starts and it's length CTLineRef line = CFArrayGetValueAtIndex((CFArrayRef)lines, idx); CFRange lineStringRange = CTLineGetStringRange(line); NSRange lineRange = NSMakeRange(lineStringRange.location, lineStringRange.length); // Get the line related string NSString* lineString = [displayedString.string substringWithRange:lineRange]; // Calculate it's range NSRange stringRange = NSMakeRange(lineStringRange.location, lineStringRange.length); static const unichar softHypen = 0x00AD; // Get the last char of the line unichar lastChar = [lineString characterAtIndex:stringRange.length-1]; // Check if it's a soft hyphenation character if(softHypen == lastChar) { NSMutableAttributedString* lineAttrString = [[displayedString attributedSubstringFromRange:stringRange] mutableCopy]; NSRange replaceRange = NSMakeRange(stringRange.length-1, 1); // Replace it with an hard hyphenation character [lineAttrString replaceCharactersInRange:replaceRange withString:@"-"]; CTLineRef hyphenLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)lineAttrString); CTLineRef justifiedLine = CTLineCreateJustifiedLine(hyphenLine, 1.0, self.frame.size.width); CGFloat ascent; CGFloat descent; // Calculate the line height CTLineGetTypographicBounds(justifiedLine, &ascent, &descent, NULL); // Set the correct position for the line CGContextSetTextPosition(context, 0.0, idx*-(ascent + descent)-ascent); CTLineDraw(justifiedLine, context); } else{ CGFloat ascent; CGFloat descent; // Calculate the line height CTLineGetTypographicBounds(line, &ascent, &descent, nil); CGFloat y = idx*-(ascent + descent)-ascent; // Set the correct position for the line CGContextSetTextPosition(context, 0.0, y); CTLineDraw(line, context); } } ``` This is working almost fine but not always. Sometimes happens this: As you can see the last line is cutted out. Has someone any advice to fix this annoying problem?

Original source