Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong text height when text contains emoji

Tags:

ios

swift

Following the official docs, I created this function to calculate text height.

func calculateTextHeight(myString: String, myWidth: CGFloat, myFont: UIFont) -> CGFloat {
    let textStorage = NSTextStorage(string: myString)
    let textContainer = NSTextContainer(size: CGSize(width: myWidth, height: CGFloat.max))
    let layoutManager = NSLayoutManager()

    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)

    textStorage.addAttribute(NSFontAttributeName, value: myFont, range: NSMakeRange(0, textStorage.length))
    textContainer.lineFragmentPadding = 0
    textContainer.lineBreakMode = .ByWordWrapping

    layoutManager.glyphRangeForTextContainer(textContainer)
    return layoutManager.usedRectForTextContainer(textContainer).size.height
}

But the calculated height is wrong when the text contains an emoji.

var s = "ABCDE 12345"
print(calculateTextHeight(s, myWidth: 500, myFont: UIFont.systemFontOfSize(14)))
// prints 16.7 (correct)

s = "ABCDE 12345 💩"
print(calculateTextHeight(s, myWidth: 500, myFont: UIFont.systemFontOfSize(14)))
// prints 22.9 (should be 16.7)

Is this a bug? How can I fix this?

like image 536
Code Avatar asked Feb 13 '16 08:02

Code


1 Answers

This worked for me when text contains emojis. For NSAttributedString strings:

extension NSAttributedString {
    func sizeOfString(constrainedToWidth width: Double) -> CGSize {
        let framesetter = CTFramesetterCreateWithAttributedString(self)
        return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0, length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
    }
}

For String:

extension String {
    func sizeOfString(constrainedToWidth width: Double, font: UIFont) -> CGSize {
        let attributes = [NSAttributedString.Key.font : font]
        let attString = NSAttributedString(string: self, attributes: attributes)
        let framesetter = CTFramesetterCreateWithAttributedString(attString)
        return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0, length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
    }
}
like image 160
sash Avatar answered Oct 05 '22 02:10

sash