2014年2月6日木曜日

[iOS][Objective-C]NSStringのlengthは絵文字を正確にカウント出来ない


どうも。

おとしあな。

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;
}

で、
上記を試してみると、
確かに絵文字が1文字としてカウントされるようになった。

しかし、
2文字としてカウントされる絵文字もある。
上記だけでは対応としては不完全なのか…。

んで、
リサーチする中で分かったことは、
絵文字の中でも、
・囲み数字
・国旗
については、
特別な判定をしなければならないこと。

それ以外にも、
いくつか2文字でカウントされてしまうものもある。

ここまでくると、
絵文字の正確な文字コードのマッピングから、
規則性や例外を見つけて、
条件に組み込んでやるしかない。

ただ、
個人的にそれがいいコードとは思えない。
(やる気もないw)

標準のTwitterアプリ(iOS7版)で、
同じような検証を試みたところ、
同等のロジックで文字数を算出しているらしく、
対応出来ない絵文字も同等のよう。

Twitterと同等だからよい、
という判断もよくないが、
それなりの数の絵文字に対応出来ているし、
多少は許容せざるを得ないのかな…。

ちなみに、
実際に今仕事で開発しているアプリでも、
問題が起きている。

文字数がMAX値を超えている場合には、
超えた分をカットしてやる処理があって。
lengthでカウントが正しくできない為、
ケースによってはカット後の末尾が文字化けすることがある。
で、化けるだけならまだしも、
それをC++で作っているミドルウェアに渡した後、
その文字列をC++のstringにUTF-8変換してbrigdeするところで、
クラッシュ((((;゚Д゚)))))))

どう解決するかは、
いくつか案があって、
・きちんと文字数をカウント出来るようにする
・プログラム的にカットするのではなく、
   ユーザが自分でBackSpaceするようにUI的に仕向ける。

カウントを上記のロジックである程度正確にしておいて、
文字数を「xxx / xxx」みたくリアルタイム表示し、
超えた時には特定の操作ができなくする。
というのが最も良い方法な気がします。
(これって、結局はTwitterアプリと一緒かw)



それでは。
ちゃお☆


まこぴー。