ラベル NSString の投稿を表示しています。 すべての投稿を表示
ラベル NSString の投稿を表示しています。 すべての投稿を表示

2015年2月2日月曜日

[iOS][Objective-C]文字列の各種変換


どうも。

文字列の各種変換。
全角→半角とか、
ひらがな→カタカナとか、
ローマ字→ひらがなとか。

普段あんまり必要としない機能というか、
ついぞ最近になってやりたいシーンが出てきて、
調べてみたら...。

NSStringには該当するメソッドはありませんが、
CFMutableStringに行き着きました。
で、CFStringTransform関数というのを使えばいいみたい。
定義を覗いてみると。

[CFStringTransform関数]

CF_EXPORT
Boolean CFStringTransform(CFMutableStringRef string, CFRange *range, CFStringRef transform, Boolean reverse);

[transform]

/* Transform identifiers for CFStringTransform()
*/
CF_EXPORT const CFStringRef kCFStringTransformStripCombiningMarks;
CF_EXPORT const CFStringRef kCFStringTransformToLatin;
CF_EXPORT const CFStringRef kCFStringTransformFullwidthHalfwidth;
CF_EXPORT const CFStringRef kCFStringTransformLatinKatakana;
CF_EXPORT const CFStringRef kCFStringTransformLatinHiragana;
CF_EXPORT const CFStringRef kCFStringTransformHiraganaKatakana;
CF_EXPORT const CFStringRef kCFStringTransformMandarinLatin;
CF_EXPORT const CFStringRef kCFStringTransformLatinHangul;
CF_EXPORT const CFStringRef kCFStringTransformLatinArabic;
CF_EXPORT const CFStringRef kCFStringTransformLatinHebrew;
CF_EXPORT const CFStringRef kCFStringTransformLatinThai;
CF_EXPORT const CFStringRef kCFStringTransformLatinCyrillic;
CF_EXPORT const CFStringRef kCFStringTransformLatinGreek;
CF_EXPORT const CFStringRef kCFStringTransformToXMLHex;
CF_EXPORT const CFStringRef kCFStringTransformToUnicodeName;
CF_EXPORT const CFStringRef kCFStringTransformStripDiacritics CF_AVAILABLE(10_5, 2_0);

CFStringTransform関数に対して、
定義されたtransformを指定してやると、
そのように変換してくれる。

再利用性と、
CFのコードをなるべく書かなくていいように、
必要そうな分をピックアップしてNSStringのカテゴリにしてみた。

[NSString+ConvertLetters.h]

@interface NSString (ConvertLetters)

- (NSString *)convertToFullwidth;
- (NSString *)convertToHalfwidth;
- (NSString *)convertKatakanaToHiragana;
- (NSString *)convertHiraganaToKatakana;
- (NSString *)convertHiraganaToRoman;
- (NSString *)convertRomanToHiragana;
- (NSString *)convertKatakanaToRoman;
- (NSString *)convertRomanToKatakana;

@end

[NSString+ConvertLetters.m]

- (NSString *)transformWith:(CFStringRef)transform reverse:(Boolean)reverse {
 
    NSMutableString* retStr = [[NSMutableString alloc] initWithString:self];
    CFStringTransform((__bridge CFMutableStringRef)retStr, NULL, transform, reverse);
 
    return retStr;
}

- (NSString *)convertToFullwidth {
 
    return [self transformWith:kCFStringTransformFullwidthHalfwidth
                       reverse:true];
}







みたいな感じ。


それでは。
ちゃお☆


まこぴー。

2014年10月16日木曜日

[iOS][Objective-C]NSStringからの変換とチェック


どうも。

ひさびさにiOSというか、
Objective-C...。

Swiftの覚え書きはまだもうちょっと先になりそう。
まだまだ仕事ではObjective-Cが現役なんでw

今回は、NSStringです。
NSStringからintやBOOLに変換できます。
plistに設定値を書いといて、
読み出してプログラム内で使うときなんかに重宝します。

NSString *aStr = @"123";
int a = [aStr intValue];
とすると、
aには123が代入されます。

NSString *bStr = @"YES";
BOOL b = [bStr boolValue];
とすると、
bにはYESが代入されます。

まぁ、
当たり前の話ですw
当たり前に使っているが、
実は仕組みを詳しく知らなかったりする。

まずはint変換から。
数値でない文字列の場合どうなる?
Javaで同じようなことをするときは、
NumberFormatExceptionで受けたりしますが、
そういう作法が存在しない以上、
何かしらよしなに値を返してくれるはずです。

NSString *aStr = @"aaa";
int a = [aStr intValue];
とすると、
aには0が代入されます。

@"aaa" → 0
@"123aaa" → 123
@"aaa123" →  0
@"123aa4" → 123

なるほど、
数値文字で始まっていれば、
それが続く限りは数値として認識してくれるみたい。
@"123aaa"を許容しないような場合には、
自力でチェックが必要になります。

チェックについては、
ConvertCheckというカテゴリにして実装してみた。

[NSString+ConvertCheck.m]

- (BOOL)canBeConvertedToInt {
 
    return [self isEqualToString:
            [NSString stringWithFormat:@"%d", [self intValue]]];
}

他にもNSScannerとかNSCharacterSetや正規表現も使えますが、
先に文字列チェックしてしまうとintの範囲を超えたりを気にしたりしなかったり、
コードもちょっと複雑になるんで...。

あと、
これだと@"0000"や@"000009"みたいなのは変換不可になってしまいます。
今回書いたコードの用途上はそれを弾いちゃっていいので構いませんが、
その辺は仕様や実情に合わせたコードを書く必要があろうかと思います。

さて、
今度はBOOL変換。

試してみる前に、
NSString.hを覗いてみると、
こんなコメントがあります。
/*
Skips initial space characters (whitespaceSet), or optional -/+ sign followed by zeroes.
Returns YES on encountering one of "Y", "y", "T", "t", or a digit 1-9. 
It ignores any trailing characters.
*/

つまり、
@"Yeah"でもYESですw
まぁ、
BOOLに関しては、
厳密なチェックが必要なケースってほとんどないと思うんで、
チェックするメソッドも実装しません。
なるほどねーっていうだけですw


それでは。
ちゃお☆


まこぴー。

2014年2月27日木曜日

[iOS][Objective-C]NSString+Addition


どうも。

コードスニペットとしては、
utilのようなクラスに書く前提の方が楽ですが、
実際の使用感としては、
カテゴリとして定義した方が直感的だし、
メソッド名としてもスッキリさせることができる。

ということで、
以前にスニペット的に覚え書きしておいた、
下記のトピックをカテゴリに定義しておく。

http://synonym-of-raspberry.blogspot.jp/2014/02/iosobjective-cnsstringlength.html
http://synonym-of-raspberry.blogspot.jp/2014/02/iosobjective-c_16.html

カテゴリ名は"Addition"とでもしておきます。
(まぁ、そんなネーミングにしたら手広そうってくらいのノリw)

[NSString+Addition.h]

//
//  NSString+Addition.h
//


#import <Foundation/Foundation.h>

@interface NSString (Addition)

- (NSUInteger)realLength;
- (BOOL)isWhiteSpaceOnly;


@end

[NSString+Addition.m]

//
//  NSString+Addition.m
//


#import "NSString+Addition.h"

@implementation NSString (Addition)


- (NSUInteger)realLength {
    
    NSUInteger count = 0;
    
    if (self == nil) {
        
        return count;
    }
    
    int length = [self length];
    
    for (int i = 0; i < length; i++) {
        
        unichar uchar = [self characterAtIndex:i];
        
        if (CFStringIsSurrogateHighCharacter(uchar)) {
            
            // サロゲートペア:上位サロゲート
            i++;
            ++count;
            
        } else if (CFStringIsSurrogateLowCharacter(uchar)) {
            
            // サロゲートペア:下位サロゲート
            // 無視(何もしない)
            
        } else {
            
            ++count;
        }
    }
    
    return count;
}


- (BOOL)isWhiteSpaceOnly {
    
    BOOL ret = NO;
    NSString *newText = @"";
    
    if (self == nil || [self isEqualToString:@""]) {
        
        ret = YES;
        
    } else {
        
        newText = [self stringByTrimmingCharactersInSet:
                   [NSCharacterSet whitespaceAndNewlineCharacterSet]];
        
        if ([newText length] == 0) {
            
            ret = YES;
        }
    }
    
    return ret;
}


@end

上記例の内容だと、
ソースファイルを取り込んで、
ヘッダをimportしてやって、
色んなプロジェクトでそのまま流用できます。
util的なノリです。

プロジェクト独自ではあるが、
そのプロジェクト内では色んなトコで使うようなものは、
また別のカテゴリにまとめるようにしておく。
色んなところで使えそうなものは、
NSString+Additionに追記していく。
という整理をすれば、
NSString+Additionを育てながら、
色んなプロジェクトで使い回していけるワケです。


それでは。
ちゃお☆


まこぴー。

2014年2月16日日曜日

[iOS][Objective-C]文字列が空白文字のみかどうかの判定


どうも。

ユーザが何かしらの入力をした時に、
空白や改行のみの文字列を許容したくない。

というようなことも、
時々あるので、
やはりutilのようなクラスに持っておきたい。

+ (BOOL)isTextWhiteSpaceOnly:(NSString *)text {
 
    BOOL ret = NO;
 
    if (text == nil || [text isEqualToString:@""]) {
     
        ret = YES;
     
    } else {
     
        NSString *newText = [text stringByTrimmingCharactersInSet:
                   [NSCharacterSet whitespaceAndNewlineCharacterSet]];
     
        if ([newText length] == 0) {
         
            ret = YES;
        }
    }
 
    return ret;
}

仕組みは単純で、
空白文字をトリミングしていって、
文字列長が0なら、
空白しかないという判断です。


それでは。
ちゃお☆


まこぴー。

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)



それでは。
ちゃお☆


まこぴー。