NSString.lengthを使おう
CoreText内部ではNSStringが使われている(と思う)ので、CoreTextと文字数を一致させるには、
NSString.lengthを使うとよさそうです。
逆に、表示されてる文字数ベースであれば、String.characters.countで良さそうです。
hiragramさんご指摘ありがとうございました。
試してみよう
以下のコードをplaygroundで実行してみる
func lengthPrint(str: String) {
print("------: 1234567890123456789012345678901234567890")
print("String: \(str)")
print("Length: String(\(str.characters.count)) NSString(\(NSString(string: str).length))")
}
lengthPrint("・゜゚・:.。..。.:・'(*゚▽゚)'・:.。. .。.:・゜゚・*")
lengthPrint("😊")
lengthPrint("あ")
出力結果はこんな感じです。
------: 1234567890123456789012345678901234567890
String: ・゜゚・:.。..。.:・'(*゚▽゚)'・:.。. .。.:・゜゚・*
Length: String(38) NSString(42)
------: 1234567890123456789012345678901234567890
String: 😊
Length: String(1) NSString(2)
------: 1234567890123456789012345678901234567890
String: あ
Length: String(1) NSString(1)
一見String.CharacterView.countの方が正しそうです。
しかし、playgroundのコードを修正するために、Backwordで消そうとすると、NSString.lengthの文字列としてカウントされます。iOS上でも同様。絵文字はサロゲートペアでNSString.length=2と解釈されそうですが、キタワァーの顔文字は。。うーむ。。
顔文字の方は不可視の文字が入っていたためカウントが違っていたようです。wakufactoryさんご指摘ありがとうございました。
不可視文字
見えない文字にまったく考えが至らずトンチンカンな記事を書いてしまいましたので、さらに掘り下げたいと思います。
・゜゚・:.。..。.:・'(*゚▽゚)'・:.。. .。.:・゜゚・*
について、ASCII範囲外はcodePointで出してみました。
\u{200B}\u{FF65}\u{309C}\u{FF9F}\u{FF65}\u{200B}:.\u{FF61}..\u{FF61}.:\u{200B}\u{FF65}\'(*\u{FF9F}\u{25BD}\u{FF9F}\u{200B})\'\u{FF65}:.\u{FF61}. .\u{FF61}.:\u{200B}\u{FF65}\u{309C}\u{FF9F}\u{FF65}*\u{200B}
このU+200B
がZERO WIDTH SPACEで不可視文字です。
そして、こうしてみると、NSStringはUTF-16については、codePointをカウントしてそうです。
エンコーディングの闇が深い。。。