Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSString boundingRectWithSize cutting height short with CoreText Framesetter ? iOS

I have a custom UIView which is drawing an NSString via CoreText :

- (NSMutableAttributedString *)getAttributedString : (NSString *)displayText {
string = [[NSMutableAttributedString alloc]
                                     initWithString:displayText];

helvetica = CTFontCreateWithName(CFSTR("Helvetica"), 20.0, NULL);

[string addAttribute:(id)kCTFontAttributeName
               value:(__bridge id)helvetica
               range:NSMakeRange(0, [string length])];

return string;
}

- (void)drawRect:(CGRect)rect
{

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
                                                                       (__bridge CFAttributedStringRef)string);
// left column form
leftColumnPath = CGPathCreateMutable();
CGPathAddRect(leftColumnPath, NULL,
              CGRectMake(0, 0,
                         self.bounds.size.width,
                         self.bounds.size.height));

// left column frame
textleftFrame = CTFramesetterCreateFrame(framesetter,
                                     CFRangeMake(0, 0),
                                     leftColumnPath, NULL);

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    // right column form
    rightColumnPath = CGPathCreateMutable();
    CGPathAddRect(rightColumnPath, NULL,
                  CGRectMake(self.bounds.size.width/2.0, 0,
                             self.bounds.size.width/2.0,
                             self.bounds.size.height));

    NSInteger rightColumStart = CTFrameGetVisibleStringRange(textleftFrame).length;

    // right column frame
    textrightFrame = CTFramesetterCreateFrame(framesetter,
                                          CFRangeMake(rightColumStart, 0),
                                          rightColumnPath,
                                          NULL);
}

// flip the coordinate system
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

// draw
CTFrameDraw(textleftFrame, context);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    CTFrameDraw(textrightFrame, context);
}

// cleanup
CFRelease(textleftFrame);
CGPathRelease(leftColumnPath);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    CFRelease(textrightFrame);
    CGPathRelease(rightColumnPath);
}
CFRelease(framesetter);
CFRelease(helvetica);
CFRelease(helveticaBold);
}

In another class I am then trying to use boundingRectWithSize to calculate how long the view will be to display the text (I then later set a UIScrollView to match this) :

NSMutableAttributedString * attributedString = [textView getAttributedString:text];

    // Code here for iOS 7.0 - sizeWithFont is deprecated.
    CGRect textBoxSize = [attributedString boundingRectWithSize:CGSizeMake(315.f, CGFLOAT_MAX) options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];

    textView.frame = CGRectMake(textView.frame.origin.x, pictureSpace, textBoxSize.size.width, textBoxSize.size.height);

The getAttributedString method is above. The problem is that textView is slightly too short in height and therefore cuts off the last line of so of text. Can anyone suggest what is wrong ?

Also, on a side note, why does the size in boundingRectWithSize have to be 315 (i.e slightly shorter than the screen width) rather than 320 in order to work ? At 320 the textView ends up slightly too wide for the screen.

Edit - this only seems to happen with certain fonts - e.g Verdana works fine. Does someone more knowledgable know if this something to do with glyphs ?

Thanks !

like image 885
GuybrushThreepwood Avatar asked Oct 22 '22 00:10

GuybrushThreepwood


1 Answers

In the MMProgressHud project, the different sizes of the different components are computed using boundingRectWithSize:options:context:method and I had some issue using the HelveticaNeue-Light font but no one using the Verdana font.

I created a new category on NSString to make my own measurement method using Core-Text to have a regular behaviour. This is inspired by this post and this post.

#import "NSString+CustomMetrics.h"
#import<CoreText/CoreText.h>
@implementation NSString (CustomMetrics)

- (CGSize) boundingRectWithSize:(CGSize) bounds andFont:(UIFont*) uiFont
{

    CTFontRef ctFont = CTFontCreateWithName((CFStringRef) uiFont.fontName,uiFont.pointSize, NULL);

    CGFloat ascent = CTFontGetAscent(ctFont);
    CGFloat descent = CTFontGetDescent(ctFont);
    CGFloat leading = CTFontGetLeading(ctFont);

    if (leading < 0)
        leading = 0;

    leading = floor (leading + 0.5);

    CGFloat lineHeight = floor (ascent + 0.5) + floor (descent + 0.5) + leading;
    CGFloat  ascenderDelta = 0;
    if (leading > 0)
        ascenderDelta = 0;
    else
        ascenderDelta = floor (0.2 * lineHeight + 0.5);

    CGFloat defaultLineHeight = lineHeight + ascenderDelta;

    CTParagraphStyleSetting paragraphSettings[1] = { {kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &defaultLineHeight} };

    CTParagraphStyleRef  paragraphStyle = CTParagraphStyleCreate(paragraphSettings, 1);
    CFRange textRange = CFRangeMake(0, self.length);

    //  Create an empty mutable string big enough to hold our test
    CFMutableAttributedStringRef string = CFAttributedStringCreateMutable(kCFAllocatorDefault, self.length);

    //  Inject our text into it
    CFAttributedStringReplaceString(string, CFRangeMake(0, 0), (CFStringRef) self);

    //  Apply our font and line spacing attributes over the span
    CFAttributedStringSetAttribute(string, textRange, kCTFontAttributeName, ctFont);
    CFAttributedStringSetAttribute(string, textRange, kCTParagraphStyleAttributeName, paragraphStyle);

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(string);
    CFRange fitRange;

    CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, NULL, bounds, &fitRange);

    CFRelease(framesetter);
    CFRelease(string);
    CFRelease(ctFont);

    return frameSize;

}
@end
like image 100
yageek Avatar answered Oct 24 '22 10:10

yageek