Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emojis messing with obj-c's sizeWithFont math

In a UITableView that needs to display a long list of chatlike conversations, often containing emojis, a size calculation error occurs.

My problem is, that if a string is just the right length, and I use sizeWithFont, I on my first measurement using sizewithfont get an incorrect length of the string, causing a "linebreak".

I assume that it is because the string ":-)" is broader than the actual smiley icon.

The evidence can be seen here :

Using <code>SizeWithFont</code>

Now, over at some other stacks, some claim that sizeWithFont will only account for the string, not the Emoji, which for me doesn't make sense, since it gets it right "eventually"...

But they propose using sizeToFit instead, so I decided to give it a go.

Using SizeToFit

Bam, same result.

Does anyone know how to counter this ? Is there a boolean to check if "Label is done being emoji-processed" so i can skip that call ?

Running the same line twice does nothing, it seems the view needs to be drawn, before sizeWithFont realises its mistake.

The shown column is run in a - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath segment, on a custom cell. I can replicate the error on a perfectly regular UITableViewCell as well, so that doesn't seem to be it.

like image 760
Nils Munch Avatar asked Oct 23 '13 09:10

Nils Munch


2 Answers

- (CGFloat)heightStringWithEmojis:(NSString*)str fontType:(UIFont *)uiFont ForWidth:(CGFloat)width {

// Get text
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), (CFStringRef) str );
CFIndex stringLength = CFStringGetLength((CFStringRef) attrString);

// Change font
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef) uiFont.fontName, uiFont.pointSize, NULL);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, stringLength), kCTFontAttributeName, ctFont);

// Calc the size
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CFRange fitRange;
CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(width, CGFLOAT_MAX), &fitRange);

CFRelease(ctFont);
CFRelease(framesetter);
CFRelease(attrString);

return frameSize.height + 10;

}
like image 62
SergiSolanellas Avatar answered Oct 27 '22 20:10

SergiSolanellas


Thank you @SergiSolanellas! Here's a version that takes an attributedString, shortening the method because the text and font are already set.

//
// Given an attributed string that may have emoji characters and the width of 
// the display area, return the required display height.
//
- (CGFloat)heightForAttributedStringWithEmojis:(NSAttributedString *)attributedString forWidth:(CGFloat)width {
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
    CFRange fitRange;
    CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(width, CGFLOAT_MAX), &fitRange);

    CFRelease(framesetter);

    return frameSize.height;
}
like image 30
Troy Avatar answered Oct 27 '22 19:10

Troy