Emoji对NSAttributedString属性的支持(字距调整/段落样式)

Dir*_*nry 5 unicode nsattributedstring ios swift

我在上使用字距调整属性,UILabel以使用一些自定义字母间距显示其文本。不幸的是,当我显示用户生成的字符串时,有时会看到以下内容:

/ Users / mick / Desktop / Capture d'écran2018-03-30à16.45.55.png

即有时某些表情符号字符未显示。

如果我注释掉字距调整,但改用某些段落样式,则会得到相同类型的错误渲染。

我在文档中找不到任何内容,明确拒绝了对特殊unicode字符的支持。我是在做错什么还是iOS的错误?

重现此错误的代码可在以下游乐场中获得:https://github.com/Bootstragram/Playgrounds/tree/master/LabelWithEmoji.playground

并复制到这里:

//: 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()
Run Code Online (Sandbox Code Playgroud)

谢谢。

Cod*_*ent 6

TL,DR:

String.count != NSString.length。每次看到时NSRange,都必须将其转换String为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
}
Run Code Online (Sandbox Code Playgroud)

更长的解释

您的问题是在Swift String和ObjC 之间转换的常见问题NSString。a的长度String是扩展的字素簇的数量;在ObjC中,它是编码该字符串所需的UTF-16代码点的数量。

以竖起大拇指的字符为例:

let str = ""
let nsStr = str as NSString

print(str.count)    // 1
print(nsStr.length) // 2
Run Code Online (Sandbox Code Playgroud)

当涉及到标志表情符号时,事情甚至会变得更奇怪:

let str = ""
let nsStr = str as NSString

print(str.count)    // 1
print(nsStr.length) // 4    
Run Code Online (Sandbox Code Playgroud)

即使本文的撰写方式可以追溯到2003年,但今天仍然是一本好书: 每个软件开发人员的绝对最低知识绝对,肯定地必须了解Unicode和字符集。