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 !
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With