2014年12月6日土曜日

[iOS][Objective-C]エラー内容のローカライズ表示(NSError)


どうも。

frameworkなどを使って機能を実装する際、
結果をデリゲートによる通知で受けることが多々あります。
成功したら成功した旨のデリゲート、
失敗したら失敗した旨のデリゲート、
といった具合に。

例えば、
失敗したときにエラーのAlertViewを表示してユーザに通知するとする。
他言語対応のアプリだと、Localizable.stringsに項目を増やさなければなりません。
何カ国語も対応している場合には大変です...。

実例として、
日・英・韓・中(繁体字/簡体字)
と対応しているアプリを開発しています。
文言が新たに追加されると、
その分依頼を出さないとどうにもならない状況です。
開発側としても翻訳側としてもその手間をどう省くか?

例えば、失敗のデリゲートでNSErrorが連携される場合、
無条件にエラー内容を出せばよいのなら、
下記の様にすることで、
ローカライズされたエラー内容を取得することが出来る。

NSString *alertMsg = [error localizedDescription];

さらに細かな条件によりメッセージを出し分けたり、
デリゲートで受け取るNSErrorの文言ではフィットしない等あれば、
自力でローカライズするしかないです。

指定5カ国語以外は英語、
みたいな取り決めがある等の場合にも、
自力でローカライズするしかない。

でもこの場合にも、
framework側が出す確認のAlertや部品って、
ユーザ言語に合わせて出てくるから、
frameworkの流れの中では合わせちゃってもいいかな、
と個人的には思います。


それでは。
ちゃお☆


まこぴー。

2014年10月22日水曜日

[iOS][Objective-C]NSDateの比較


どうも。

NSDate同士の比較。
これまで機会がありそうでなかったので、
どうやるんだろうかと...。

もうObjective-Cに関わって何年にもなって、
Swiftも出てきたというのに、
こういうことがちょいちょいありますw

ググれば即出てきますが、
こんなカンジです。

 
    NSDate *date1;
    NSDate *date2;

         (中略)

    NSComparisonResult result = [date compare:date2];
 
    switch (result) {
        case NSOrderedSame:
            // date1 == date2
            break;
        case NSOrderedAscending:
            // date1 < date2
            break;
        case NSOrderedDescending:
            // date1 > date2
            break;
    }


NSComparisonResultはNSNumberの比較でも使いますね。
NSNumberの比較というのも、
個人的にあまりしたことがありませんが...。

NSDateはNSDateFormatter使って、
文字列から生成することもあります。
元になる文字列が、
・フォーマット違反
・日付として無効な数値
になっているなどの場合はnilが返ってきます。
まぁ、ここまでは合点がいきます。

問題なのは、
その後のcompareです。
nilとのcompareは、
NSOrderedSameが返ってきてしまう...。

文字列が信用できるならいいんですが、
今回は文字列が人の手による入力で、
しかも事前にはチェックすらできない状況だったりします。

仕方ないので、
ある程度泥くさいチェックを書かなきゃいけませんね...。

それでは。
ちゃお☆

まこぴー。

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年9月28日日曜日

[Android]Androidアプリのエントリポイント


どうも。

いろいろと忙しく、
久々の覚え書き。

Androidも関わる機会が多くなってきたので、
今回はAndroidに関するトピック。

自分でゼロから作ったり、
最初から全容を把握しているアプリならいいんですが、
もう出来上がったものに対して、
改修や不具合対応などをせねばならない場合もある。
で、
大体の場合ドキュメントはない...。
諸般の事情で、
内部の詳しいつくりを把握している人もいない...。

こんなときには、
与えられたソースコード一式の解析からスタートせねばならない。

で、自分の場合はAndroidアプリに関しては、
大した実戦経験がないワケで...。

しょんぼりしていても仕方ないし、
Androidアプリに限らず、
こんな状態のスタートはよくあることだったりする。

MainActivityとかTopActivityなどと、
クラス名などからアタリがつく場合もあれば、
必ずしもそうでない場合や、
規模が大きい場合など、
まずは困ったときには(アプリの)エントリポイントを探るのが手っ取り早い。

ということで、
Androidアプリのエントリポイントはというと、

AndroidManifest.xmlのintent-filter要素に、
android.intent.action.MAINが設定されているもの

という探し方ができるようだ。
ただし、
android.intent.action.MAIN
は複数のアクティビティに設定でき、
そんな場合には、
android.intent.category.LAUNCHER
が設定されているもの
ということらしい。

すなわち、

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

となっているようなActivityを見つければよいことになる。


それでは。
ちゃお☆


まこぴー。

2014年7月18日金曜日

[iOS][Android]iOSアプリ開発からのAndroidアプリ開発


どうも。

ご無沙汰しています。

仕事ではObjective-CでiOSアプリ開発して、
業務外では少しずつSwiftも弄ったりして、
もう一度原点回帰でC++辺りも学び直したいな、
などと、
平和に過ごしていたのですが、
いきなり仕事でAndroidやることに…。
突然なのはラブストーリーだけでじゅうぶんです。

このAndroidやる経緯を書き出すと、
文句だらけになってしまうのでw

とにかく、
望むか望まざるかに関わらず、
俺はAndroidをやらなければならなくなった。
ほとんど実践経験なしです。

iOSとAndroidで同時に開発してたプロジェクトのときに、
画面を含めた仕様を共通で自分が握ってたのと、
何かの折にコードレビューした程度。

iOSアプリの開発してると、
Android開発やってた人が、
突如iOSのエンジニアに仕立てられるというのをよく見てきたが…。
まさか自分がその逆をやらされるとはw

とにかく、
iOSアプリのエンジニアが、
最短でAndroidのエンジニアになる為にどうしたらいいか?

優秀なエンジニアは何でもすぐにできますが、
自分は並以下なんで…。

Androidは基本的にはJavaで書くことになる。
数年前に1年ちょっとくらいJavaの案件に携わる機会があったくらい。
(ほとんど上流工程しかやってないから、実践的な実装経験はかなり乏しい)
とりあえず、Javaがどんなもんだったか思い出す為に、
「AndroidエンジニアのためのモダンJava」
という本を買ってみた。
http://www.amazon.co.jp/Android%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A2%E3%83%80%E3%83%B3Java-%E5%B1%B1%E7%94%B0-%E7%A5%A5%E5%AF%9B/dp/477415878X

あっさりした内容だが、
今回の用途には適している気がする。
分かりきってるところはチラ見もしくはぶっ飛ばしてw
(半分以上が不要なんだけどね…w)
個人的にはコレクションについてとマルチスレッドについては、
多少しっかりと。

ネット上で目的に適ったページを探してみた。
事細かく説明してくれるワケではないが、
下記のページが非常に参考になる。

http://developer.smartnews.be/blog/2013/09/06/ios-android/

http://akisute.com/2014/01/ios-android.html

ここまでで、
Hello, world!
くらいのレベルはちゃきちゃきクリア。

ちなみに、
iOSとAndroidを組み合わせた検索をすると、
クロスプラットフォーム的なトピックばかりで、
なかなか今回の用途にフィットした情報は見つからない。
(根気強さと検索テクニックに欠けるのかも…)

後は、
アプリ開発的に重要と思われる事項を概念レベルで確認。
・アクティビティ
・サービス
・インテント
・UI部品(どんなのが標準であるか?のレベル)

ここまでサラリとやっておくと、
何となくキャッチアップできた(気がする)

あとは、
Javaでのシングルトンの実現方法と、
グローバル変数の扱い方の確認。
こいつらは乱用あるいは使用事態を原則的に避けねばならないが、
いざというときに強力な助けになる!

グローバル変数はJavaには概念上存在しないが、
抜け道はあります。
一般にバッドノウハウとして嫌われがちで、
Javaの世界では恐らくその傾向はより顕著でしょうが、
数画面程度のアプリでは使った方が、
解決としてシンプルな場合もあります。

と、
限られた時間(合計して数時間)で、
上記のような整理をして、
なんとかかんとか、
とりあえずは仕事としては進めてる感じですw

あとはやりながら身について、
ノウハウが蓄積されていくと思います…。
もちろん、
継続的に学習は必要かと思いますが。

ということで、
Androidに関しても、
覚え書きしておきたいことは、
整理も含めてここに書く。
と思います。


それでは。
ちゃお☆


まこぴー。

2014年7月5日土曜日

[iOS][Objective-C]NSLogをファイルに出力する


どうも。

NSLogによる出力をコンソールで確認。
デバッグ時によくやることです。

常にMacと端末をつなげて、
1台もしくは2台程度でデバッグ実行する場合にはいいのですが、
台数が多くなってきたり、
持って歩いて動作確認といった状況が発生した時に、
ログ出力が確認出来ない…。

NSLogの出力先は、
通常は標準エラー出力(stderr)です。
それをファイルにリダイレクトすればいい。


    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask,
                                                         YES);
    NSString *docDir = [paths objectAtIndex:0];
    NSString *path = [docDir stringByAppendingPathComponent:@"log.txt"];

freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);


上記コードでは、
(App)/Documents/log.txt
にコンソールの内容が出力される。

これを、
AppDelegatedidFinishLaunchingWithOptions
辺りに書いておけばよいかと。

ファイルの内容やファイル自体の破棄など、
ログファイルのライフサイクルは、
freopen()のオプションで変えるとか、
ファイルを破棄する処理を組み込むとか、
実情に応じた形で。


それでは。
ちゃお☆


まこぴー。

2014年6月24日火曜日

[iOS][Objective-C]NSMutableArrayの要素をイテレートしながら削除する


どうも。

ちょっと考えれば分かることなんですが、
あまりこういうシーンが今までなかったので…。

「NSMutableArrayから、
何かしらの条件に合致する要素を削除したい。」

となったときに、
・条件判定→ループ
・要素の削除→removeObject

で、

for (int i = 0; i < [aList count]; i++) {
    ・
    ・
    ・
}

と書き始めて、
「これじゃアカン!」
と…。

removeすると削除して詰めちゃうから、
インデックスがおかしくなる…。

と、
ちょっと考えてみて、
「index降順でループすればいいじゃん!」
という結論。

    int aListCnt = (int)[aList count];

    for (int i = aListCnt - 1; i >= 0; i--) {
 
   MyClass *myClass = [aList objectAtIndex:i];

   if ([myClass shouldBeRemovedFromList]) {
 
        [aList removeObjectAtIndex:i];
       }
    }

まぁ、
これまでこんなシーンがなかったんだなぁと、
なぜかしみじみw

んで、
int aListCnt = (int)[aList count];
というまどろっこしいのがあるんですが、
警告対策です。

countメソッドはNSUIntegerが返ってくるので、
for (int i = [aList count] - 1; …………
と書くと警告が出ます。

では、
for (NSUInteger i = [aList count] - 1; i >= 0; …………
と書いたらどうか?

これはこれで別な警告が出ます。
「unsignedなんだから、i>=0って常に真でしょ?」
みたいな。

まぁ、
for (int i = (int)[aList count] …………
と書けばいいんですが、
ループの条件が長いのも鬱陶しいので、
こんな整理にしてみました。


それでは。
ちゃお☆

まこぴー。

2014年6月11日水曜日

[iOS][Objective-C]バックグラウンド動作


どうも。

アプリがバックグラウンドに遷移しても、
処理を継続したい。
というのは、
要件としてありえる話です。

しかしながら、
iOS側の思想としては、
「アクティブでないのなら可能な限り速やかにサスペンドする」
というような感じです。
これは、バッテリー消費等の観点だと思われます。

通常、
バックグラウンド遷移後、
RunLoopやワーカスレッドの停止により、
サスペンド状態になり、
アプリ動作は停止する。

じゃあ、
本気で即時サスペンドかというとそうでもなく、
AppDelegateの
- (void)applicationDidEnterBackground:(UIApplication *)application;
の中で、
然るべきデータの保存等が可能なように、
5sec以内の処理が許可されている。

ある程度の間処理が継続出来ればいい、
というのであれば、
下記のような方法により、
サスペンドまでの時間を延長する事ができる。
注意が必要なのは、
その延長時間は、
iOS6では600sec、iOS7では180secとなっていること。

[AppDelegate.m]


UIBackgroundTaskIdentifier bgTask = 0;

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    UIApplication *app = [UIApplication sharedApplication];

  bgTask = [app beginBackgroundTaskWithExpirationHandler:^{

  [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

上記はアプリ全般に言える事ですが、
特定の位置づけのアプリであれば、
上記の制約を飛び越える事ができます。

Audio再生や位置情報の利用など。
<AppName>-Info.plistに、
Required background modesとして定義する事が出来る。

だから、
iPodみたくAudioの再生をバックグラウンドでも止めたくない、
とか、
ナビゲーション機能はバックグラウンドだからといって止まってもらっては困る、
というのは、
Info.plistに定義の上、
無制限で処理の継続が許可される。

iOS7からは、
Background fetchとか、
Remote notificationsといった仕組みが追加になっているので、
SNSやWebサービス連携であれば、
そうした仕組みを積極利用すべきと考えられます。
これらは何かしらのトリガにより、
一時的にサスペンドから復帰して処理を実行後、
またサスペンド状態に戻る。
というような仕組みに見受けられる。
(使った事ないから詳しくは分からないw)

で、
裏技というか…。
バッテリー消費量だったり、
(iOS開発観点での)倫理的な問題から、
個人的にはやらない方がいいと思うんですが…。

やろうと思えば、
Info.plistに定義しといて、
空のAudioを再生し続けるとか、
必要なくても位置情報を更新し続けるとかすれば、
バックグラウンド動作が継続出来るワケですw

完全なバッドノウハウだと思います。
Rejectされない保証や、
後々取り下げられない保証はありません。
こういうのは危険と隣り合わせ、
と思っておくくらいがちょうどいいと思うし、
Storeに出したいのなら、
裏技は使わない方がいいかなと思います。

個人で無料で出すようなアプリならまだしも、
会社名義で出すようなアプリでは、
バッドノウハウは排除すべきだと思います。
しかしながら、
そうした警告をしても、
全く意に介さない人もいるので、
痛い目に遭わなきゃいいですがね…、
と見守るしかないですw


それでは。
ちゃお☆


まこぴー。

2014年6月3日火曜日

[iOS][Swift]新しいプログラミング言語「Swift」


どうも。

またまた、
ずいぶんとご無沙汰してしまいました…。

WWDC 2014があり、
iOS8の発表やら、
なんとまぁ、
新プログラミング言語の「Swift」の発表があったり…。

で、
そのSwift。
リファレンスを読むどころか、
早くもXcode6でSwiftをお試し実装している人たちもいる訳ですw
(まぁ、エンジニアたる者、自分もそうでなきゃいけないんですが…)

ちょいとすぐにはそんな時間が取れないので、
移動時間にリファレンスをチラ見して、
先取りしている人たちのコードなんかもチラ見して。
既にググれば、
サワリとしてはそこそこな情報があります。

で、
受けた印象としては、
コードの見た目的には、
JavascriptとかPythonとかに似ているかな。
まぁ、モダンな言語のいいとこ取りみたいな感じなんでしょうか?

Objective-Cに比べれば、
コード量や可読性は一気に上がりそうです。

iOS8以降のみをターゲットにして、
新たに書くならSwiftの方がいいかも知れない。
共存しながらのコンバートを経て、
最終的にはSwiftに完全に置き換わるのか、
果たして…。

ともかく、
実情のお仕事ベースで言うと、
これまで、そして、
いまいまはObjective-Cで書いているワケで、
当面はObjective-Cとのお付き合いは続きそうです。
また、ゆくゆくも、
レガシーなiOSアプリのコードとして付き合わざるを得ないシーンはあるかと。

忘れてはいけないことは、
iOS開発であることには変わりない。
ということです。
クセのあるUIとか画面遷移だとかマルチタスキングだとか、
気を使わなければならないポイントは同じ。

このブログも、
iOSアプリをObjective-Cで開発する、
という前提のトピックばかりでやってますが、
当面はその内容は変わらないと思います。
(まぁ、あまり更新もできていなかったりもしますが…w)

もともと自分が開発をする上で困らないように覚え書きしているので、
Objective-Cの開発が続くのであれば、
Objective-Cの覚え書きをするし、
違う言語になればその覚え書きをするまでです。


それでは。
ちゃお☆


まこぴー。

2014年4月17日木曜日

[iOS][Objective-C]StackとQueue(簡易実装)


どうも。

ずいぶんとご無沙汰しました…。
ごぶさたした理由はいろいろありますが、
書くと長いのでw

で、
今回のお題は、
StackとQueueについて。

Javaには、
java.util.Stackとかjava.util.Queueとかあって、
普通に使えるワケですが、
Objective-Cにはそんなのない…。

StackやQueueというのはデータ構造であって、
その仕組みが分かっていれば、
NSMutableArrayに対して、
相応の処理をしてやればいいだけ。

とは言え、
popとかpushと書いて、
イメージ通りに動く方が見た目にもスッキリなので、
予め実装しておく。

NSMutableArrayを使うのであれば、
カテゴリにしてしまうのが楽ですね☆

カテゴリ名は、
VirtualStack, VirtualQueueとでもしておきます。
Virtualなんてつけるほどすごいことでもないけどw

[NSMutableArray+VirtualStack.m]

#import "NSMutableArray+VirtualStack.h"

@implementation NSMutableArray (VirtualStack)

- (id)pop {

    id lastObj = [self lastObject];
 
    if (lastObj != nil) {
     
        [self removeLastObject];
    }
 
    return lastObj;
}

- (void)push:(id)obj {
 
    [self addObject:obj];
}

@end

[NSMutableArray+VirtualQueue.m]

#import "NSMutableArray+VirtualQueue.h"

@implementation NSMutableArray (VirtualQueue)

- (id)dequeue {
 
    id headObj = [self objectAtIndex:0];
 
    if (headObj != nil) {
     
        [self removeObjectAtIndex:0];
    }
 
    return headObj;
}

- (void)enqueue:(id)obj {
 
    [self addObject:obj];
}

@end

と、
ここまで書いておいて…。
これって、
StackもQueueも両方あったら、
Stackとしてpush-popしてきたものに対して、
いきなりdequeueとか出来ちゃったりするw

それぞれ、
NSMutableArrayをメンバーに持つ、
別なクラスに切り出した方がいいのかもしれない。
複数人開発や、
マルチスレッドでのアクセスがありうるとか、
そういうリスク管理の観点では、
クラスとしてきちんと区別した方がいい。

あくまで、
簡易的に実現するなら、
という方法ですね、これは。


それでは。
ちゃお☆


まこぴー。

2014年4月3日木曜日

[iOS][Objective-C]UIの表示位置やサイズを変更する


どうも。

実際の業務となると、
UIの表示位置やサイズを変更することが、
ちょいちょいとあります。

aViewというUIView*があって、
Y位置を100, 高さを100にしたいとすると…。

aView.frame.origin.y = 100.0f;
aView.frame.size.height = 100.0f;

としてやれるなら簡単ですが、
上記はできません…。

X, Y, Width, Heightのどれかをひとつだけでも変えたい場合でも、
やれることは、
[aView setFrame:xxxx];
だけである。

従って、
新しいCGRectを作ってやって、
そのCGRectをsetFrameしてやるしかない…。
上記の例だと、
XとWidthが変わらないから、
まずは元の位置をベースにしてCGRectを取得し、
YとHeightを変更。
そのCGRectをsetFrameする。

CCRect newFrm = aView.frame;
newFrm.origin.y = 100.0f;
newFrm.size.height = 100.0f;

[aView setFrame:newFrm];

ひとつの部品が1回動くだけならいいけど、
いくつかの部品を相関関係も気にしながら動かさなきゃいけないことの方が多い。
そうなると、
上記のようなコードをたくさん書かねばならず、
可読性やメンテナンス性が大きく損なわれる。

ので、
UIViewのカテゴリにしてしまう。
UIButtonとかUISwitchとか、
その辺も軒並みUIViewを継承しているので、
全般的に使える。

例のごとく、
カテゴリ名はとりあえずAddition…。
ソースはちょっと分量が多いのでヘッダのみ。
実装は上記のようなコードを書いて、
さらに内部でコールしているに過ぎない。

#import <UIKit/UIKit.h>

@interface UIView (Addition)

#pragma mark - Setters of X, Y, Width, Height
#pragma mark (with one parameter)
- (void)setX:(CGFloat)x;
- (void)setY:(CGFloat)y;
- (void)setW:(CGFloat)w;
- (void)setH:(CGFloat)h;
#pragma mark (with two parameters)
- (void)setX:(CGFloat)x Y:(CGFloat)y;
- (void)setX:(CGFloat)x W:(CGFloat)w;
- (void)setY:(CGFloat)y H:(CGFloat)h;
- (void)setW:(CGFloat)w H:(CGFloat)h;
#pragma mark (with all parameters)
- (void)setX:(CGFloat)x Y:(CGFloat)y W:(CGFloat)w H:(CGFloat)h;
#pragma mark (with CGPoint, CGSize)
- (void)setPoint:(CGPoint)p;
- (void)setSize:(CGSize)s;

#pragma mark - Calculation of X, Y, Width, Height
#pragma mark (of X)
- (void)xPlus:(CGFloat)f;
- (void)xMinus:(CGFloat)f;
#pragma mark (of Y)
- (void)yPlus:(CGFloat)f;
- (void)yMinus:(CGFloat)f;
#pragma mark (of Width)
- (void)wPlus:(CGFloat)f;
- (void)wMinus:(CGFloat)f;
#pragma mark (of Height)
- (void)hPlus:(CGFloat)f;
- (void)hMinus:(CGFloat)f;

@end

経験上、
これだけ実装しておけば、
あまり困らないハズ。
とにかく1ステップで位置/サイズ調整が終わるのは、
ソースコードの可読性に歴然と差が出る。
と思う。

それでは。
ちゃお☆


まこぴー。

2014年4月2日水曜日

[iOS][Objective-C]Storyboardの分割



どうも。

未だにStoryboardっつーのが、
よくわからないw
過去のプロジェクトではxibばかり扱ってきたので…。

で、
今のプロジェクトになってようやく、
Storyboardを使っているワケです。

最終的には担当者が自分ひとりになってしまったけど、
最初は3人いて、
ひとつのStoryboardを3人でいじってマージをするという、
悲惨な状況でしたw

根本的に、
xibにせよStoryboardにせよ、
実態はXMLなので、
XMLベースでマージをすればいいんですが、
まぁ大変です。

しかも、
後から画面を付け加えて、
pushViewControllerとかできなかったりして、
直感的なようで直感的でないし、
よくわからない…。

Storyboardをイメージでしか理解していなかったので、
複数人開発には向かないなぁ…。
なんて思っていました。

結局時間もなくて根本改善ができなかったのですが、
個人的にはひとつの答えに行き着きました。

Storyboardを最初から分割すればいいのでは…。
そして、
Push-Pop型の画面遷移の場合、
RootのViewControllerがいるワケで、
そいつからの流れでどんどんPushしてやれば、
Storyboardの見た目上でつながりを持っていなくてもいいのでは…?

で、
Root-Sub1-Sub2-Sub3
と、
それぞれ別のStoryboardにして、
・順番にPushしていって順番にPopできるか?
・popToRootViewControllerできるか?
換言すると、
NavigationControllerによるスタック管理がきちんと成り立つのか?
を検証してみたところ、
オールOK!
期待通りの結果が得られた。

まぁ、
Segueとかいうのを使ってつなげて、
ひとつのStoryboardで全画面をマッピングできるし、
ソースコード量が減らせる。
というのがStoryboardを使う醍醐味のひとつな気もしますがw



それでは。
ちゃお☆


まこぴー。

2014年4月1日火曜日

[iOS][Objective-C]ネット接続の確認


どうも。

Webサービスと連携していると、
ネット接続していることが必須ですね。
当たり前ですがw

先回りしてネット接続を確認して、
あらかじめアラートを出したり、
そもそも処理をさせないようにするとか。

そんなときは、
Reachability
というのが、
iOS Developer Libraryに転がっているので、
そいつを取り込んで使えばいい。
(Reachability.hとReachability.m)

確認の都度、
ごにょごにょと書くのは面倒なので、
utilみたいなクラスに、
クラスメソッドで定義しておく。

+ (BOOL)isAvailableDataNetwork {
 
    Reachability *internetReachability = [Reachability reachabilityForInternetConnection];
 
    NetworkStatus netStatus = [internetReachability currentReachabilityStatus];
 
    if (netStatus != NotReachable) {
     
        return YES;
     
    } else {
     
        return NO;
    }
}

netStatusは、

typedef enum : NSInteger {
    NotReachable = 0,
ReachableViaWiFi,
ReachableViaWWAN
} NetworkStatus;

となっていて、
ViaWWANが3Gとかの接続です。


それでは。
ちゃお☆


まこぴー。

2014年3月27日木曜日

[iOS][Objective-C]アプリでスクリーンショットを取得する


どうも。

一応の制限事項というか、
未解決の課題のあるコードです。

下記のコードでは、
statusBarが描画できない…。
今のところ解決する方法が見つかってないけど、
まぁステータスバーの内容って別にないならないでいいかな、
なんて思ってしまう自分もいたりw

ということで、
例のごとく、
utilのようなクラスにクラスメソッドとして定義するとして。

+ (UIImage *)getScreenShotImage {

    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
 
    UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // Windowの現在の表示内容を1つずつ描画。
    for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
     
        [window.layer renderInContext:context];
    }
 
    UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
 
    UIGraphicsEndImageContext();
 
    return capturedImage;
}

とりあえず取得したスクリーンショットをUIImage*で返しているが、
この辺りは実情に合わせて欲しい形で返したり、
カメラロールやアプリ内に保存する仕組みとかにしてもいい。

使い途としては、
デバッグ用に何かのトリガーでスクリーンショット取得しておくとか、
SLComposeViewControllerに渡してやってSNS連携とか。
MFMailComposeViewControllerに渡してやってメール送信とか。
例えばゲームの達成度をさくっと画像でシェアする、
なんて用途にもいいかもしれない。


それでは。
ちゃお☆


まこぴー。

2014年3月18日火曜日

[iOS][Objective-C]キーボードからの通知を監視する


どうも。

アプリ上で出現するキーボード。
ユーザの文字入力には必須ですが、
こいつが現れたり隠れたり、
予測変換で大きさが変わったりと、
結構厄介です。

画面の作り方次第なんですが、
標準のメッセージアプリなんかがいい例で、
メッセージ入力部はキーボードの表示とセットになって、
その位置が変わります。

入力部が持ち上がる時には、
その分他のViewも動かしたり、
高さを変更したりしなければならない。

上記を実現するためには、
キーボードの通知を監視し、
通知のコールバックで描画処理をしてやる。

下記のようなメソッドを用意して、
viewDidLoadあるいは、
viewDidLoadで初期化等をしているメソッド内で呼ぶ。
大事なのは通知をaddObserverしておくこと。

- (void)prepareReceiveKeyboardNotification {
 
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];
 
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:nil];
 
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardFrameWillChange:)
                                                 name:UIKeyboardWillChangeFrameNotification
                                               object:nil];
 
}

通知のコールバックメソッドをそれぞれ定義する。

- (void) keyboardWillShow:(NSNotification *)note;
- (void) keyboardWillHide:(NSNotification *)note;
- (void) keyboardFrameWillChange:(NSNotification *)note;

上記以外にもキーボード関連の通知があるハズだが、
上記だけで事足りることがほとんど。

それぞれ、パラメータの(NSNotification *)noteに、
キーボードの情報が入ってくる。
下記の様にしてキーボードのframeを取り出す。

    CGRect keyboardFrame;
    [[note.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardFrame];

frameが取り出せたら、
あとはごにょごにょと各パーツの位置やサイズを調整してやればいい。
で、それらの調整については、
keyboardWillHideとkeyboardFrameWillChangeで行う。
keyboardWillShowは、
必要があれば何か前処理を行う機構として、
とりあえず定義しておく。
(少なくとも自分は画面調整の際に使っていない)


それでは。
ちゃお☆


まこぴー。




2014年3月13日木曜日

[iOS][Objective-C]Viewを意味ある単位にまとめる


どうも。

例えば、
標準のメッセージアプリやLINEなんかが、
分かりやすくいい例ですが。

メッセージのやりとりの履歴が表示されるエリアと、
入力のコントロールをするエリアとに、
大きく分かれている。

で、
それらの画面は静的な配置ではない。
入力を開始すると、
キーボードが下から競り上がってくるし、
入力文字の行数が増えてこれば、
入力のTextViewの高さが変わるようになっている。

そういった制御は、
現状では自動的にやってくれるワケでなく、
自力で実装して実現せねばならない。

入力のコントロールエリアなどは、
現状のメッセージアプリやLINEでは、
TextViewの横にうまく配置出来るだけしかないが、
コントロールが増えてくると、
TextViewの下に配置して二段にする、
といったようなレイアウトもありえる。

非常に極端な話ですが、
少なくとも、
・履歴表示エリア
・入力コントロールエリア
という区別が必要になる。

履歴表示エリアには現状ではUITableViewしか存在しないが、
その最下部に何かしらのエリアを追加するようなこともありえそう。
入力コントロールエリアは、
コントロールの増加に伴って拡張がありそう。

という読みをしたときに、
履歴表示エリアと入力コントロールエリアを、
それぞれ1つのUIViewの中に配置しておく。
すると、
キーボードが下から競り上がってきたとき、
2つのViewの座標やサイズを再設定してやればいい。

入力コントロール部が2段になった。
そうしたら、
入力コントロールエリアのViewの中で、
TextViewを含むエリア(高さが可変)とそうでないエリアで、
Viewを分けて配置する。

◎mainView
    ○ナビゲーションバー
    ○ 履歴表示エリアView
    ○ 入力コントロールエリアView
           ・ (上段)入力エリアView
           ・ (下段)コントロールView

上記例は非常に単純な例であるが、
まずは、
「画面仕様からViewのヒエラルキーを整理して捉える」
というクセが重要。

実感として、
スマホアプリの開発では、
要求や仕様が後から変わってくることが結構ある。
その時の変更インパクトを押さえることも出来る。

あと、
個人的に挙げるメリットとして、
デバッグ時にViewに目立つ色をつけて、
意図した通りに可動できているかどうか?
というチェックもしやすくなるw
初期段階ではframeをログ出しするよりも、
その方がよっぽど分かりやすい。


それでは。
ちゃお☆


まこぴー。

2014年3月7日金曜日

[iOS][Objective-C]プルリフレッシュ - UIRefreshControl


どうも。

SNSのタイムラインなんかではおなじみの、
下に引っ張って情報を更新するUI。
今まで、
これを実現しようとすると、
UITableViewのヘッダにViewを追加して、
スクロールを監視して、
なんならデザイン部品も必要で…。
と、
実装がめんどくさかったような気がします。

今では、
UIRefreshControl
というのがあり、
上記のようなことをしなくても、
実現出来る。

viewDidLoad内で、
    _refreshControl = [[UIRefreshControl alloc] init];

    [_refreshControl addTarget:self
                                  action:@selector(refresh)
                   forControlEvents:UIControlEventValueChanged];

    [_tableView addSubview:_refreshControl];

みたいにすれば、
tableViewにrefreshControlが仕込まれる。

デリゲートメソッドがなく、
自力で更新の開始と終了の処理を書いてやって、
beginRefreshing
endRegreshing
を呼んでやらないといけません。

上記の例だと、
refreshというメソッドを定義してやって、
その中でbeginRefreshingを呼ぶことになる。

endRefreshingは、
tableViewをreloadする辺りで呼んでやることになろうかと。

とまぁ、
このエントリを書いている現在はiOS7ですが、
iOS6からある仕組みらしい…。

業務で必要になるケースがなくても、
定期的にOSのアップデートがあった場合は、
リファレンス確認して、
「こういうことができるようになった」
とか確認しておく必要があるなと、
今更ながらに実感w

今回は、
たまたまリファレンスを色々確認してみる調査が発生して、
こんなのがあったと、発見。

んで、
新規開発の場合には、
そろそろiOS6以上をターゲットにしてもいいかな、
と思ったりもするので、
どんどん使おうかな。

ともあれ、
この仕組みの実装が云々よりも、
プルリフレッシュという仕組み自体を最初に思いついた人はすごいな、
なんていつもいつも思うワケです。


それでは。
ちゃお☆


まこぴー。

2014年3月6日木曜日

[iOS][Objective-C](AppName)-Prefix.pchについて


どうも。

Xcodeでプロジェクトを作成すると、
アプリ名-Prefix.pchという、
グローバルなヘッダが勝手にというか、
付随的に出来てて。

で、
このブログでも、
そこをたびたび話題にしてきました。
↓↓↓↓↓
http://synonym-of-raspberry.blogspot.jp/2014/01/iosobjective-cnslog.html
http://synonym-of-raspberry.blogspot.jp/2014/01/iosobjective-c_30.html

なぜそれらが成り立っているかというと、
アプリのグローバルなヘッダで、
明示せずともどこからでも参照出来るから。

ということは、
アプリ全般で使うutilのようなクラスや、
作ったカテゴリやら、
何かをまとめたヘッダのようなものは、
<AppName>-Prefix.pchにimportしちゃえばいい。

これで、
ソース上ヘッダを#importしている箇所が、
ずいぶんスッキリするハズ!

まぁ、
もっと早く気づけよという話ですがw

ということが分かったとして、
新しいアプリプロジェクトを作成した時点では、
utilのような存在はなくて、
後から切り出しては、
色んな人が色んな画面で#importするという、
抗いがたい流れもあるワケです。
しかも画面もコピーで作ってしまうから、
そのまんま色んな#importも意図せず引き継がれるw

これはソースコードというより、
クラス単位で考えてプロジェクトをどう整理するか?
みたいなことでもあるんだろうな…。

自分ひとりで完結するのなら、
・util
・xxxApp(xxxにはアプリ名の略称とか当てはめる)
みたいなクラスを予め用意しておいて、
utilには本当に汎用的なユーティリティを、
xxxAppにはそのアプリに閉じた世界のユーティリティーを、
という風に整理した方がいいな、
とか思ってたり。

そうやって整理することで、
utilの洗練度というか、
より汎用的に作ろうとする気がします。

ここまで書いてみて、
どうせ整理せずにドカドカと#importしちゃうくらいなら、
全部Prefix.pchに書いちゃえばいいんじゃないかと思ったりもするけど、
どうなんだろう?


それでは。
ちゃお☆


まこぴー。

2014年3月1日土曜日

[iOS][Objective-C]NSLogをマクロで使う(その2)


どうも。

以前に、

NSLogはリリース後にもOrganizer等から見ることができるので、
<AppName>-prefix.pchにマクロを記述しておき、
リリースビルド時には自動的に無効化されるようにしておきましょう。
という記事を書いたのですが。

↓↓↓↓↓
http://synonym-of-raspberry.blogspot.jp/2014/01/iosobjective-cnslog.html

内容自体は変わらないのですが、
各プロジェクトでの使い回しなんかを考えると、
ヘッダファイルに切り出して、
そのヘッダを<AppName>-prefix.pchで#importする方が、
有用な気がした。

ので、
そんなヘッダを作ってみる。

[DebugLog.h]

//
//  DebugLog.h
//


#ifndef __DEBUG_LOG_H__

#define __DEBUG_LOG_H__

// ↓↓↓↓↓ DEBUG用ログ出力定義 ↓↓↓↓↓ //
// ※本番用のアーカイブビルドの際には自動で無効化される
//
#ifdef DEBUG
#define LOG(...) NSLog(__VA_ARGS__)
#define LOG_PRINTF(FORMAT, ...) printf("%s\n", [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#define LOG_METHOD NSLog(@"%s", __func__)
#define LOG_METHOD_AND_ABORT LOG_METHOD; abort()
#else
#define LOG(...)
#define LOG_PRINTF(FORMAT, ...)
#define LOG_METHOD
#define LOG_METHOD_AND_ABORT
#endif

#ifdef DEBUG
#define LOG_POINT(p) NSLog(@"%f, %f", p.x, p.y)
#define LOG_SIZE(p) NSLog(@"%f, %f", p.width, p.height)
#define LOG_RECT(p) NSLog(@"%f, %f - %f, %f", p.origin.x, p.origin.y, p.size.width, p.size.height)
#else
#define LOG_POINT(p)
#define LOG_SIZE(p)
#define LOG_RECT(p)
#endif
//
//
// ↑↑↑↑↑ DEBUG用ログ出力定義 ↑↑↑↑↑ //

#endif  // #ifndef __DEBUG_LOG_H__

こういうのを、
組織で共有できると有意義ですが、
いかんせん会社にはiOSのエンジニアは自分しかいないし、
その自分は外に出っぱなしだし、
どうにもこうにも、にんともかんとも…。

まぁ、
NSString+Additionも含めて、
暇を見て個人でgitにでもアップしとこうかな。


それでは。
ちゃお☆


まこぴー。

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月26日水曜日

[iOS][Objective-C]座標/サイズ構造体(CGRect, CGPoint, CGSize)のログ出力


どうも。

UIを扱う場合なんかには、
座標系の構造体、
CGRect, CGPoint, CGSizeを扱うことが多い。
で、
位置とかサイズが可変になってくると、
ログ出しして確認したいのだが…。

CGRectを例にとってみると。

NSLog(@"xxxView.frame:%f, %f, %f, %f",
          xxxView.frame.origin.x,
          xxxView.frame.origin.y,
          xxxView.frame.size.width,
          xxxView.frame.size.height);

なんてやってたら、
面倒で仕方ない。

そんなとき、
NSStringFromCGRectというマクロがあって、
それを使うと…。

NSLog(@"xxxView.frame:%@", NSStringFromCGRect(xxxView.frame));

非常に簡潔に書ける!
こうすると、
xxxView.frame:{{0, 20}, {320, 548}}
といったログがコンソールに出る。
これは、
{{x, y}, {width, height}}
の形式。

CGPointやCGSizeも同じく、
NSStringFromCGPoint
NSStringFromCGSize
といったマクロが用意されている。

もっと出力を分かりやすくするなら、
CGRectCreateDictionaryRepresentationを使って、

NSLog(@"xxxView.frame:%@", CGRectCreateDictionaryRepresentation(xxxView.frame));

なんて書くと、

xxxView.frame:{
    Height = 548
    Width = 320;
    X = 0;
    Y = 20;
}
と、
ログが出ます。
heightとかWidthとか出てくれるんで一見分かりやすいですが…。
CGRectMakeするときなんかもそうなんですが、
一般にX, Y, Width, Heightの順番で捉えているので、
違和感はありますね。

こちらも同じく、
CGPointやCGSizeには、
CGPointCreateDictionaryRepresentation
CGSizeCreateDictionaryRepresentation
が用意されています。

個人的には、
上記のような座標やサイズのログはデバッグ用の使い捨てなので、
NSStringFromCGRectなんかでサクッとやっちゃうのが楽かと☆
小数できちんと確認したいなら自前で書くしかないですが、
そういうシーンにはほとんど出くわしたことない。

ちなみに、
NSStringFromXXXといったマクロには、

NSStringFromCGAffineTransform
NSStringFromCGPoint
NSStringFromCGRect
NSStringFromCGSize
NSStringFromClass
NSStringFromProtocol
NSStringFromRange
NSStringFromSelector
NSStringFromUIEdgeInsets

が用意されていて、
XXXFromStringで逆変換が出来ます。
使ったこともないですがw

ClassやProtocol, Selectorは、
変換と逆変換を巧みに使うことで、
リフレクションみたいなことが実現出来るかもしれませんが。


それでは。
ちゃお☆


まこぴー。

[iOS][Objective-C]enumの注意点


どうも。

enum自体は、
C言語から脈々と受け継がれている仕組みで、
細かく語る必要もないんですが…。

のハズが、
軽くハマった((((;゚Д゚)))))))

何でハマったかというと、
「値を指定しない場合は直前の列挙からのインクリメントとなる」
という、
至極当たり前のことw

enumというのは、
値を指定せずに、
シーケンシャルに列挙することができるワケで、
その特性を活かしているときは構わないのですが、
defineの集合体のように、
値を定義しまくって使うこともできる。

とある仕組みで、
モードをenumで列挙して定義しておいたんですが。
これは正の数で、
シーケンシャルな定義。
ただし、
0はモードなしという定義、
最終の列挙は終端という定義をする。

モードが、
0 < x < 終端
であれば、
モードに応じた処理を実行する。

という仕掛けにしておいた。

で、
これを参考に、
同じようなもので、
エラー処理をハンドリングするようなものを作ってね。
とお願いしたところ…。

エラー処理なので、
「モードはマイナス値の定義であるべき」
という一般的なポリシーは守ってくれたんですが。

飛び番号のマイナス値をenumで、
列挙が進むにつれて、
値が小さくなっていくように書かれていた。
で、
エラー定義上最小の値の次に、
終端を値の指定なしに列挙した。

そうすると、
エラー定義上最小の値と終端の値がかぶってしまうため、
モードが有効範囲内にあるか?
という判断をした時に、
エラー定義上最小の値は無効なモードとして認識されてしまう。
といったことがあって、
「同じように作ったのに、どうしても意図した通りに動きません」
という報告を受けて、
結局自分が手直しするハメになりました…。

実際には、
enumの列挙的な問題以前に、
モードが有効か無効かの条件文自体が、
まず間違っていましたが(´・ω・`)


それでは。
ちゃお☆


まこぴー。

2014年2月24日月曜日

[iOS][Objective-C]iOS上でのintとlongの最大値は同じ



どうも。

iOS上でのlongは32bitしか表現できないので、
事実上intと同じです。

INT_MAX:      2147483647
LONG_MAX:  2147483647

となります。
一般的に言うlongの範囲を使いたいなら、
long long
を使わなければなりません。

これは、
処理系が32bitだからということだと思いますが、
少なくとも32bitと64bitの処理系が混在している間には、
注意が必要。

サーバ連携なんかすると、
long値も受け取らなければならない可能性があるので、
そんな場合には、
iOSアプリ側ではlong longで受け取らなければならない。

まぁ、
64bitの端末以外なくなれば、
long longではなく、
普通にlongがlongとして使えるかもしれませんが。


それでは。
ちゃお☆


まこぴー。

2014年2月21日金曜日

[iOS][Objective-C]数値を3桁カンマ区切りのフォーマットにする


どうも。

数値をカンマ区切りで出力する。
というのもちょいちょいありそうなこと。

例えば、
123456789を、
123,456,789と表示してやる。

まぁ、
理屈は分かっていて、
後ろから3桁に区切りながらカンマを挿入してやればいいですが。
こういうのは理屈が分かっていても、
実際に実装するのはめんどくさいw

で、
便利なのがあるんですわ。

NSNumber *number = [NSNumber numberWithInt:123456789];

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setPositiveFormat:@",###"];

NSString *numberStr = [formatter stringForObjectValue:number];

NSLog(@"%@", numberStr);

などと書くと、
コンソールログには、
123,456,789と表示される。

こっちでもいい。

NSNumber *number = [NSNumber numberWithInt:123456789];

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];

NSString *numberStr = [formatter stringFromNumber:number];

NSLog(@"%@", numberStr);

やりやすい方を使えばいいけど、
見た目に理解しやすいのは後者かな。

日付表示のNSDateFormatterなんてのもあって、
日付も出力形式がシーンによって違ったりするので、
併せて頭の片隅に置いといて、
数値や日付の表示に関してはFormatterを使えば楽。

どういう設定にしたらどうフォーマットされるか?
といったようなことは、
Appleのリファレンスや書籍やネット上の情報で調べがつきます。

本エントリに限ったことではないが、
このブログでの覚え書きというのは、
自分自身が分かればいい尺度だったり、
自分自身の考え方の整理として書いているので、
懇切丁寧なリファレンスではありませんw

極端に言えば、
調べるためのキーワードやスニペットの集合体でしかありません。


それでは。
ちゃお☆


まこぴー。

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月15日土曜日

[iOS][Objective-C]uncaught exceptionで異常終了時のログを取得する


どうも。

uncaught exceptionで異常終了した際に、
その内容が確認出来ると便利。

まぁ、
Xcodeにつないで実機動作中であれば、
コンソールに多少の情報が出て、
それだけでも解決に至ることも多いですが。

ファイルに出しておけば、
XcodeでRunしていなくても取り出して確認できるし、
NSUserDefaultに保存しておけば、
次回起動時に異常終了があったことも検知することもできる。

実現するには、
NSSetUncaughtExceptionHandler関数にハンドラ関数のアドレスを渡す」

これがちょっと面倒というか、
C言語的なんだけど。

ハンドラ関数を作成して、
そのアドレスを渡してやることで、
例外発生時の動作を定義出来る。

とりあえず、
ファイル保存をするとして。

AppDelegate内に、
下記のような関数を定義しておく。

※関数のプロトタイプも必要です!
(AppDelegateの頭の方にでも書いておけばOK)

// 例外発生時の情報を出力
void uncaughtExceptionHandler(NSException *exception)
{
 
    NSString *exceptionOccur = @"\n***** Uncaught exception occured!*****";
    // 例外の名前
    NSString *exceptionName = [NSString stringWithFormat:@"exception name:%@",
                                                  [exception name]];
    // 例外の理由
    NSString *exceptionReason = [NSString stringWithFormat:@"exception reason:%@",
                                                     [exception reason]];
    // 例外のコールスタック
    NSString *exceptionCallStack = [NSString stringWithFormat:@"exception callStackSymbols:\n%@\n",
                                                       [exception callStackSymbols]];
 
    // ログファイルに書き出す内容
    NSString *exceptionLog = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n",
                              exceptionOccur, exceptionName, exceptionReason, exceptionCallStack];
 
 
    // デバッグコンソールにも出力
    NSLog(@"\n%@", exceptionLog);
 
 
    NSString *exceptionLogDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                                     NSUserDomainMask, YES)
                                 objectAtIndex:0];
 
    NSString *exceptionLogPath = [exceptionLogDir
                                  stringByAppendingPathComponent:@"uncaughtException.log"];
 
    NSError *error = nil;
 
    // ファイルに書き込み
    [exceptionLog writeToFile:exceptionLogPath
                   atomically:NO
                     encoding:NSUTF8StringEncoding
                        error:&error];
 
    if (error) {
     
        LOG(@"Cannot write exception log file!!");
    }

        // もしNSUserDefaultsに保存したいならこんな感じ
        //[[NSUserDefaults standardUserDefaults] setValue:exceptionLog 
        //                                                                    forKey:@"exceptionLog"];
}

んで、
AppDelegate.mの、
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
の中で、

// エラー追跡用の機能を追加する。
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);

と書いておけば、
やりたいことが実現出来る。

ログの内容や形式は、
必要に応じて替えればいいです。


それでは。
ちゃお☆


まこぴー。

[iOS][Objective-C]iOSのバージョン比較


どうも。

iOSのバージョンを知りたいことがある。

そんなときは、

NSString *iOSVer = [[UIDevice currentDevice] systemVersion];

とでもすれば、
NSString*で取得することが出来る。

ただし、
これを表示することなどないワケで、
取得した後、
バージョン比較なんかしたいケースの方が多い。

理想論から言えば、
バージョンに依存しないコードが望ましいワケだが、
例えばiOS6からiOS7のように、
大きな変化があった場合には、
バージョン比較による分岐をある程度は余儀なくされる。

例えばiOS7の判定をする場合に、
Appleが推奨してる的な方法だと、

    if (floor(NSFoundationVersionNumber)
        <= NSFoundationVersionNumber_iOS_6_1) {
 
  // iOS6.1以前の場合
     
    } else {
     
        // iOS7(以降)の場合
    }

というようなやり方だが、
NSFoundationVersionNumberの定義は、
直前のバージョンまでしか対応していない。
つまり、今現在でいうと、
iOS7の定義は存在しないから、
iOS7であることの判定をするために、
iOS6.1以前でないことを確認せざるを得ないし、
条件文自体がいまいち直感的でない気がする。

ということで、
自分で分かりやすい方法に。

バージョン分岐はしたくないが、
バージョン分岐をせざるを得なくなることも考慮に入れるなら、
utilのようなクラスに持っておいてもいい。

で、
過去の経験からいくと、
iOSのバージョン比較をしたい場合、
メジャーバージョンだけ分かればいいことが多い。
というのと、
NSString*では比較がめんどくさいので、
数値で取得出来るようにする。


#define IOS_VER_NOT_AVAILABLE -1

+ (int)iOSMajorVersion {
 
    NSString *iOSVer = [[UIDevice currentDevice] systemVersion];
 
    if (iOSVer == nil || [iOSVer isEqualToString:@""]) {
     
        return IOS_VER_NOT_AVAILABLE;
    }
 
    NSArray *verArray = [iOSVer componentsSeparatedByString:@"."];
 
    return [[verArray objectAtIndex:0] intValue];
}

これで、
6や7みたいな数値と直感的に比較ができる。

マイナー、マイクロまで比較したい場合には、
もっと色んな考慮が必要で、
ひとつのクラスにして、
そのクラスのメンバに比較メソッドを仕込む方が汎用的になる。
地味だけど面倒なので、
必要になったら作ればいいんじゃないかとw

メイントピックと外れますが、

アプリ自体のバージョンを管理したいなら、
info.plistのBundle Versionに設定しておいて、

NSString *appVersion = [[NSBundle mainBundle]
                                         objectForInfoDictionaryKey:@"CFBundleVersion"];

とすれば、
NSString*で取れるので、
あとはどのようにして使いたい形に持っていくか。
まぁ、
自分の経験に照らせば、
アプリのバージョンは情報として表示することの方が多いので、
NSString*で取れればそれで構わない。


それでは。
ちゃお☆


まこぴー。

2014年2月11日火曜日

[iOS][Objective-C]NSDictionaryの中身を確認する(description)


どうも。

NSDictionaryに限らずですが、
中身をログ出しして確認したいことは多々ある。

NSDictionary *dic;
というdictionaryがあるとして、
[dic description];
なんて書くと、
文字列で中身が返ってくるので、
NSLog(@"%@", [dic description]);
と書いておけば、
コンソールで確認が可能。
keyとvalueで組み合わせて出してくれる。
わざわざループしなくてもいいから楽。

- (NSString *)descrition;
は、
NSObjectのメソッドなので、
NSArrayなんかでも使える。

arrayがNSString*やNSNumber*なら、
そのまんま格納値が出力されるが、
独自のクラスを使った場合はそうはいかない。

MyClassというクラスがあるとして、
メンバに、
int id;
NSString *name;
とか定義されているとして。

NSArray *array;
というarrayがMyClassのArrayである場合、
[array description];
なんてしても、
idやnameを出してくれるワケではなく、
MyClassクラスのオブジェクトであることと、
恐らくそのアドレスであろうHexの値が確認出来るだけである。
状況によりけりではあるが、
その出力が有用ではない場合の方が多い。

大抵の場合、
独自のクラスもNSObjectを継承しているので、
- (NSString *)description;
もオーバーライドできる。

独自クラスでdescriptionをオーバーライドしておくと、
上記例で、
[array description];
としたときに、
意味ある内容を出力することが出来る。

ただし、
メンバの多いクラスだと、
何でもかんでも出すとごちゃごちゃするので、
descriptionは簡易確認が出来ればいいレベルに抑えておくような精査が必要かもしれない。


それでは。
ちゃお☆


まこぴー。

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)



それでは。
ちゃお☆


まこぴー。

2014年2月4日火曜日

[iOS][Objective-C]NSURLConnectionでファイルDLする場合の注意点


どうも。

NSURLConnectionでファイルをダウンロードする場合、
URLを取得して、requestを発行した後、
いくつかのデリゲートメソッドを使用してハンドリングするが、
その際に注意点が発生する。

// ヘッダが返ってきた
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

// ダウンロード中
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

// ダウンロードエラー
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

// ダウンロード完了
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

注意点(1) -----

レスポンスがOK(200)ではなく、
Forbidden(403)などでも、
ファイルとしてDLしてしまうケースがあるので、
didReceiveResponse内でステータスコードを取得しておいて、
didReceiveData内でステータスコードがOK(200)であるかどうか判断する必要がある。
(デリゲートメソッドをまたぐのが面倒…(´・ω・`))

ステータスコードを取得するには、
ヘッダに、
int resStatus;
などと定義しておき、
didReceiveResponse内で、

resStatus = ((NSHTTPURLResponse *)response).statusCode;

とすればよい。

注意点(2) -----

ファイルがDLできなくても、
connectionDidFinishLoadingが呼ばれてしまうことがある。
(具体的なケース等の詳細は不明…。)
念のため、
目当てのファイルがDLできたかどうか?
の確認をした方が確実。
確認方法は実情に合わせて。



それでは。
ちゃお☆


まこぴー。

2014年2月3日月曜日

[iOS][Objective-C]アプリケーションを終了させる


どうも。

これは基本的に禁じ手で、
Rejectされない保証はないです(´・ω・`)

一応、
「ユーザーに確認を取ってからならOK!」
という説もありますが…。

まぁ、
App Storeに申請するアプリでなければ、
そんなことを気にする必要はないです。

やり方は簡単で、
C言語のexitをコールするだけ。
引数には0でも与えとけばいいです。

ユーザーにアプリを一度終了する旨を、
UIAlertViewで確認を取ってやって、
[OK]ならexitしてしまえばいい。

今現在やってるのはトライアル開発というか、
少なくとも今のもの自体を商用化することはないので、
遠慮なく落としてやっていますがw

ただ、
exitを呼ぶと、
本当にストンと落ちます。

それではあまりに不自然なので、
何かしら終了処理をしているフリをするために、
「アプリケーションを終了しています...」
みたいな通知文言と、
UIActivityIndicatorViewを組み合わせて、
UIAlertViewを表示してやるとかすれば、
画面操作もできないし、
何かしら裏で処理してるっぽいw

上記の終了処理実行中の表示をした2秒後くらいに、
exitが遅延実行されるように仕掛けておけばいい。

で、
exitを使うべきかどうかですが、
App Storeにリリースするのであれば、
やるべきでないと思います。
代替えとしては、
何も操作できない全画面の説明表示かなんかして、
再起動手順を促してやるとか。

exitの件然りですが…。
公開されているAPIでできないことでも、
C言語の仕組みを使うことで実現出来る。
しかしながら、
そのこと自体、
Appleは快く思っていないフシがある。

C言語の仕組みで、
Reject対象になるものもあった気もするし、
今は大丈夫でも今後Reject対象になる可能性は否定出来ない。

なんて言いつつ、
諸般の事情で、
やらざるを得ないことも多々ありますが(´・ω・`)


それでは。
ちゃお☆


まこぴー。

2014年2月2日日曜日

[iOS][Objctive-C]iOS7でステータスバーのレイヤが変更


どうも。

仕事で使っているデバッグ実機ではなく、
自分の私物iPhoneを機種変更して、
iPhone5s+iOS7になりました。
(それまでiPhone4s+iOS5)

で、
私物なんで、
自分の好きなようにアプリを入れていいので、
過去に在籍したプロジェクトのアプリを入れてみたんだが、
見事にステータスバーに画面部品が被っていた…。

これは、
iOS7ではステータスバーはコンテンツとは完全に独立したレイヤとして扱うことになった。
というのが明らかな原因。

さすがにこれはもうどうしようもなくて、
きちんと画面サイズに応じて部品の位置を計算している場合には、
iOSバージョンに合わせて計算方法を変えるしかないし、
Interface Builderでガッチリ作っている場合には、
Storyboardやxibを分岐するしかない…。
どちらにせよ、
バージョンを意識した実装をせざるを得ない。

今のプロジェクトでは、
ためらいなく後者を選んだ。

色々と理由はあるが、
Retina4inchを前提として構わなかった為、
そもそも画面サイズに応じて配置を計算する作り方はしていないこと。
であれば、
コピーしてiOS7用に合わせる方が、
ソースコードに及ぼす影響が少ないし、
結局、
iOS7の標準の「設定」のアプリなんか見てると、
UITableViewCellのLabel表示位置とか、
ポリシーみたいなのが変わっている部分もあって、
そういった細やかなiOS7らしさに対応出来るよう、
nibを分岐させた方がいいと考えた。

ちなみに、
問題の過去アプリについては、
自分自身がそれを引き継いですぐのタスクが、
iPhone5対応(Retina4inch対応)だった。
この時は、
nibを分岐させずに、
異なる画面サイズに対応出来るように、
計算して部品を配置するようにした。

これも結局は常駐仕事で、
アプリにはこれ以上やることもなく、
予算も取れないからと、
契約終了で常駐を終えていて、
当然アフターサポートとかないので、
自分にはもう無関係。


それでは。
ちゃお☆


まこぴー。