Question

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.

Was it helpful?

Solution

- (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;

}

OTHER TIPS

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;
}

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top