Emojis messing with obj-c's sizeWithFont math
Asked Answered
S

3

17

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.

Shari answered 23/10, 2013 at 9:1 Comment(3)
Are you sizing a label or the cell its self? If only the label is sizing, I think I will use auto layout to do all the magic. Programmatically calculate the size of label is error-prone.Balder
I've found that this returns good measurements, but I haven't tried emojis. -[UILabel textRectForBounds: limitedToNumberOfLines:]Driggers
as of iOS 7 sizeWithFont:constrainedToSize:lineBreakMode is deprecated and replaced with boundingRectWithSize:options:attributes:context:Swinton
R
13
- (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;

}
Repartee answered 21/3, 2014 at 11:15 Comment(5)
You just made my day, I have been struggling with this for hours and your solution works like a charm... hands down!Segno
Hi @Repartee , you are great :) . I have spend 1 day for geting solution for this library [STTweetLabel ]github.com/SebastienThiebaud/STTweetLabel and your answer is very helpfull to me.. Thank you very much.Minor
Hi @Repartee . I need your help .When in my text so many new line ('\n')character is contains and no emoji in text at that time this function give me wrong height. Any solution for that ???Minor
anyone has swift version?Dissection
Oddly, in iOS 10, I have observed this function producing the wrong height when the attributed string contains emojis.Bufordbug
S
6

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;
}
Salutary answered 23/7, 2014 at 5:56 Comment(0)
P
0

I am use UILabel

sizeThatFits(_ size: CGSize) -> CGSize

It work for me.

my code

let tempLabel = UILabel()
tempLabel.font = font
tempLabel.attributedText = attText
tempLabel.numberOfLines = 0
let size = tempLabel.sizeThatFits(CGSize(width: 200, height:CGFloat.greatestFiniteMagnitude))

as the code, you need to assign three property.

Pinder answered 3/9, 2018 at 4:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.