どうも。
おとしあな。
NSStringのlengthメソッドを使うと、
絵文字を含む文字列が正確にカウント出来ません(´・ω・`)
NSStringは内部的にはUTF-16で扱われているが、
lengthでカウントする時にサロゲートペアを考慮していないので、
3バイトや4バイトの文字は2文字でカウントされてしまう。
ということで、
正しく文字数をカウント出来るように、
自前で作成してみる。
上記を考慮するなら、
サロゲートペアを考慮すればいいワケで、
それには、
// 上位サロゲートであるかどうか
Boolean CFStringIsSurrogateHighCharacter(UniChar character);
// 下位サロゲートであるかどうか
Boolean CFStringIsSurrogateLowCharacter(UniChar character);
を使えばいいみたい。
まぁ、
いろんな方法があるが、
例によってutilクラスにクラスメソッドとして定義するとして。
(後述するが、不完全なコード)
+ (NSUInteger)realNSStringLength:(NSString *)text {
NSUInteger count = 0;
if (text == nil) {
return count;
}
int length = [text length];
for (int i = 0; i < length; i++) {
unichar uchar = [text characterAtIndex:i];
if (CFStringIsSurrogateHighCharacter(uchar)) {
// サロゲートペア:上位サロゲート
i++;
++count;
} else if (CFStringIsSurrogateLowCharacter(uchar)) {
// サロゲートペア:下位サロゲート
// 無視(何もしない)
} else {
++count;
}
}
return count;
}
NSUInteger count = 0;
if (text == nil) {
return count;
}
int length = [text length];
for (int i = 0; i < length; i++) {
unichar uchar = [text characterAtIndex:i];
if (CFStringIsSurrogateHighCharacter(uchar)) {
// サロゲートペア:上位サロゲート
i++;
++count;
} else if (CFStringIsSurrogateLowCharacter(uchar)) {
// サロゲートペア:下位サロゲート
// 無視(何もしない)
} else {
++count;
}
}
return count;
}
で、
上記を試してみると、
確かに絵文字が1文字としてカウントされるようになった。
しかし、
2文字としてカウントされる絵文字もある。
上記だけでは対応としては不完全なのか…。
んで、
リサーチする中で分かったことは、
絵文字の中でも、
・囲み数字
・国旗
については、
特別な判定をしなければならないこと。
それ以外にも、
いくつか2文字でカウントされてしまうものもある。
ここまでくると、
絵文字の正確な文字コードのマッピングから、
規則性や例外を見つけて、
条件に組み込んでやるしかない。
ただ、
個人的にそれがいいコードとは思えない。
(やる気もないw)
標準のTwitterアプリ(iOS7版)で、
同じような検証を試みたところ、
同等のロジックで文字数を算出しているらしく、
対応出来ない絵文字も同等のよう。
Twitterと同等だからよい、
という判断もよくないが、
それなりの数の絵文字に対応出来ているし、
多少は許容せざるを得ないのかな…。
ちなみに、
実際に今仕事で開発しているアプリでも、
問題が起きている。
文字数がMAX値を超えている場合には、
超えた分をカットしてやる処理があって。
lengthでカウントが正しくできない為、
ケースによってはカット後の末尾が文字化けすることがある。
で、化けるだけならまだしも、
それをC++で作っているミドルウェアに渡した後、
その文字列をC++のstringにUTF-8変換してbrigdeするところで、
クラッシュ((((;゚Д゚)))))))
どう解決するかは、
いくつか案があって、
・きちんと文字数をカウント出来るようにする
・プログラム的にカットするのではなく、
ユーザが自分でBackSpaceするようにUI的に仕向ける。
カウントを上記のロジックである程度正確にしておいて、
文字数を「xxx / xxx」みたくリアルタイム表示し、
超えた時には特定の操作ができなくする。
というのが最も良い方法な気がします。
(これって、結局はTwitterアプリと一緒かw)
それでは。
ちゃお☆
まこぴー。