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

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







みたいな感じ。


それでは。
ちゃお☆


まこぴー。

2015年1月31日土曜日

[iOS][Objective-C]Xcode6でPCHファイルが...


どうも。
ご無沙汰しています。

というか、
もう1月も終わろうとしていますが、
2015年の初投稿ですw

あけましておめでとうございます!

実は、
というか、
もっと早く気付けという話なんですが...。

Xcode6で新規プロジェクトを作成した場合、
(ProjectName)-Prefix.pchが自動では作成されません...。

仕事では既存アプリへの機能追加や不具合修正、
個人的な取り組みでは、
Objective-Cでは既存コードを使った実験、
Swiftなら新規プロジェクトの作成、
みたいな感じでやっていたので、
いざObjective-Cで新規に作ってみて気づいたワケですw

仕方ないので、
xxx.pchファイルを作成して、
[Build Setting]
[Apple LLVM6 6.0 - Language] - [PrefixHeader]に、
$(SRCROOT)/$(PRODUCT_NAME)/xxx.pch
と設定すれば使えるようになります。


それでは。
ちゃお☆


まこぴー。

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年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年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の列挙的な問題以前に、
モードが有効か無効かの条件文自体が、
まず間違っていましたが(´・ω・`)


それでは。
ちゃお☆


まこぴー。