Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emoji support for NSAttributedString attributes (kerning/paragraph style)

I am using a kerning attribute on a UILabel to display its text with some custom letter spacing. Unfortunately, as I'm displaying user-generated strings, I sometimes see things like the following:

/Users/mick/Desktop/Capture d’écran 2018-03-30 Γ  16.45.55.png

ie sometimes some emoji characters are not being displayed.

If I comment out the kerning but apply some paragraph style instead, I get the same kind of errored rendering.

I couldn't find anything in the documentation explicitely rejecting support for special unicode characters. Am I doing something wrong or is it an iOS bug?

The code to reproduce the bug is available as a playground here: https://github.com/Bootstragram/Playgrounds/tree/master/LabelWithEmoji.playground

and copied here:

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport

extension NSAttributedString {
    static func kernedSpacedText(_ text: String,
                                    letterSpacing: CGFloat = 0.0,
                                    lineHeight: CGFloat? = nil) -> NSAttributedString {
        // TODO add the font attribute

        let attributedString = NSMutableAttributedString(string: text)
        attributedString.addAttribute(NSAttributedStringKey.kern,
                                      value: letterSpacing,
                                      range: NSRange(location: 0, length: text.count))

        if let lineHeight = lineHeight {
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.lineSpacing = lineHeight

            attributedString.addAttribute(NSAttributedStringKey.paragraphStyle,
                                          value: paragraphStyle,
                                          range: NSRange(location: 0, length: text.count))
        }

        return attributedString
    }
}

//for familyName in UIFont.familyNames {
//    for fontName in UIFont.fontNames(forFamilyName: familyName) {
//        print(fontName)
//    }
//}

class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        let myString = "1βš½πŸ“ΊπŸ»βšΎοΈπŸŒ―πŸ„β€β™‚οΈπŸ‘\n2 πŸ˜€πŸ’ΏπŸ’Έ 🍻"

        let label = UILabel()
        label.frame = CGRect(x: 150, y: 200, width: 200, height: 100)
        label.attributedText = NSAttributedString.kernedSpacedText(myString)
        label.numberOfLines = 0
        label.textColor = .black

        view.addSubview(label)
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

Thanks.

like image 278
Dirty Henry Avatar asked Mar 30 '18 14:03

Dirty Henry


People also ask

What are emojis in HTML?

Emojis look like images, or icons, but they are not. They are letters (characters) from the UTF-8 (Unicode) character set. UTF-8 covers almost all of the characters and symbols in the world. To display an HTML page correctly, a web browser must know the character set used in the page.

What is the UTF-8 number for emoji characters?

Emoji Characters. Emojis are also characters from the UTF-8 alphabet: is 128516 is 128525 is 128151

What is the default font for NSAttributedString objects?

The default font for NSAttributedString objects is Helvetica 12-point, which may differ from the default system font for the platform.

What is attribute string in Java?

An NSAttributedString object manages character strings and associated sets of attributes (for example, font and kerning) that apply to individual characters or ranges of characters in the string. An association of characters and their attributes is called an attributed string.


1 Answers

TL, DR:

String.count != NSString.length. Any time you see NSRange, you must convert your String into UTF-16:

static func kernedSpacedText(_ text: String,
                                letterSpacing: CGFloat = 0.0,
                                lineHeight: CGFloat? = nil) -> NSAttributedString {
    // TODO add the font attribute

    let attributedString = NSMutableAttributedString(string: text)
    attributedString.addAttribute(NSAttributedStringKey.kern,
                                  value: letterSpacing,
                                  range: NSRange(location: 0, length: text.utf16.count))

    if let lineHeight = lineHeight {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineHeight

        attributedString.addAttribute(NSAttributedStringKey.paragraphStyle,
                                      value: paragraphStyle,
                                      range: NSRange(location: 0, length: text.utf16.count))
    }

    return attributedString
}

The longer explanation

Yours is a common problem converting between Swift's String and ObjC's NSString. The length of a String is the number of extended grapheme clusters; in ObjC, it's the number of UTF-16 code points needed to encode that string.

Take the thumb-up character for example:

let str = "πŸ‘"
let nsStr = str as NSString

print(str.count)    // 1
print(nsStr.length) // 2

Things can get even weirder when it comes to the flag emojis:

let str = "πŸ‡ΊπŸ‡Έ"
let nsStr = str as NSString

print(str.count)    // 1
print(nsStr.length) // 4    

Even though this article was written all the way back in 2003, it's still a good read today: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets.

like image 143
Code Different Avatar answered Oct 11 '22 12:10

Code Different