NavigationControllerのボタンをレイアウトしたかった..

NavigationController上のボタンをInterfaceBuilderで編集したい!!
→Xcode4.2?諦めろ。

ていう感じなんですかね。色々調べてみましたがかなり意味不明でした...。
時間がもったいないのでコードで書く事にします。
べ、別にコード書けない訳じゃないんだからねっ!極限まで楽して引き継ぎも楽な方法がInterfaceBuilderだったっていうだけなんだからね!勘違いしないでよね!(//

クソッ!クソッ!


参考

XCode4.2のInterfaceBuilder上でMainWindow.xibことwindowを復活させる方法
http://www.trappers.tk/site/2011/06/16/mainwindow-xib/

[iPhone]ナビゲーションバーの弄り方
http://kozy.heteml.jp/l4l/2011/02/iphone.html

Xcode4.2: Empty ApplicationからNavigationを作る
http://ameblo.jp/welx/entry-11057603347.html

Zeemote JS1 Hの使い方についてまとめ

DevFestX Sapporo 2012で頂いたZeemote JS Hの使い方について自分用にまとめ。
公式取り扱い説明書は下記です。
http://www.aplix.co.jp/zeemote/jp/js1h/Zeemote-JS1H-manual-JP.pdf

電池→スティック側のケツがマイナス、下のケツがプラス。
(よく見るとプラスチック側面に書いてある)
電源ボタン→ジョイスティックの隣。3秒で起動。


ジョイスティックモード(Cを押しながら電源を入れる:お買い上げ時)

 操作方法は謎


ポインターモード(Aを押しながら電源を入れる)

 Aボタン:左マウスボタン
 Bボタン:右マウスボタン
 Cボタン:中央マウスボタン
 ジョイスティック:マウスポインタの移動


キーパッドモード(Bを押しながら電源を入れる)

 Aボタン:ENTERキー
 Bボタン:(割当なし)
 Cボタン:ESCキー
 Dボタン:Backspaceキー
 Bボタン+左上:F1キー
 Bボタン+右上:F2キー
 Bボタン+左下:F3キー
 Bボタン+右下:F4キー
 ジョイスティック:上下左右の方向キー
 ※「Bボタン+左上」はBボタンを押しながらジョイスティックを左上に動かすことをあらわす。


Andoroid携帯によってはキーの割当が異なるらしいw

DevFestX Sapporo 2012 1日目

DevFestX Sapporo 2012 1日目に行ってきました。
ちょっとあれな感じの参加で手ぶらで行ってしまったので少し迷惑だったかもしれません(特にハンズオンなんかは交流目的メインだと思いますし申し訳ないです)が、全体を通して、未来を見つめるという意味でもとてもいい経験になりました。

Googleさんが主催ということでGoogleから提供されている各サービスやHTML5の最新の話などを聞く事ができたのですが、今まで知らなかったけれど意外に使えそうなサービスとかが結構ありました。

Google Docs
Google Data API
Google Apps Script
Google App Engine

この辺りは普通に使い方を知っておいてまず損はないと思います。

今回、僕個人としては講演を聞きながらこの先の将来の事とGoogleさんのスタンスをずっと考えていました。Googleさんは地味に個人情報の収集とかしていて、結構あぶないんじゃないかなー、地球を制覇しようとしてるのかなーなんて事もこれまで思っていたのですが、「少なくとも今はそうではないようだ」というのが結論です。

今日色んなサービスを見せてもらいましたが、彼らの提供するサービスはどれもデザインが残念というか、使う人の事をあまり考えていない、そこにコストを割いていないように見受けられました。(デザインについては僕も素人なので人のこといえませんがw)今はデザインにお金をかけず、とにかく機能だけを最優先で充実させて、PC環境の改善をモットーにしているという感じですね。地球を制覇するというよりは「地球最強のサポーター」を目指しているのかなあと思います。

あともう一つGoogleについて感じたことは、今や世界一のGoogleですが、彼らの目指す領域からするとまだまだ山の麓なんだなあという所ですね。会社ぐるみでGoogleと勝負をしたくはないですが、目指すところというのが何となく感じ取れたのでとても良い経験になりました。

とてつもなく奇妙な参加体験記になってしまい申し訳ない限りですが、参加された皆さん、スピーカーの皆さん本当にお疲れさまでした。
明日も参加される方は滑って歯を折られないよう足下とガムにお気をつけ下さい。

InterfaceBuilderとViewControllerを使用したシーン管理の両立について

ちょうど2週間ほど前から職業iOSプログラマーとして働かせてもらって、色々思い出すところから始めたりで大変でしたが、なんとか今日一つの形になるものが出来上がった感じです。
ここらで題名にあるXcode4.2におけるInterfaceBuilderとシーン管理の両立について書こうと思います。

・なぜシーン管理が必要なのか

画面A → 画面B → 画面C

上記のように遷移する場合、Viewを切り替えていくのは一つの方法としてありだと思いますが、Apple公式のガイドやNavigationController、TabBarControllerの設計思想を見るにひとつの画面をViewControllerで定義するのが最も自然なやりかになるかと思います。

ViewControllerA  → ViewControllerB → ViewControllerC

こうなりますね。それでは実際の遷移はどのようにすれば良いでしょうか。
通常、空のプロジェクトを作成するとAppDelegateがUIWindow windowを持っておりこれが全てのrootになっていますので、この切り替えはViewControllerA〜C自身で行うレベルではない。という事が分かると思います。

そこで私はViewControllerA〜CにAppDelegateのdelegateを持たせる事にしました。delegateとはC言語でいうオブジェクトポインタで、ViewControllerA〜CからAppDelegateのメソッドを呼び出す事ができます。

ViewControllerA「ViewControllerBに移動したいよAppDelegateさん」
ViewControllerB「ViewControllerCに移動したいよAppDelegateさん」

シーン管理をしない場合、AppDelegateには上記の台詞分のメソッドが必要になります。

AppDelegate「ViewControllerAからViewControllerBに移動するためのメソッドを準備。」
AppDelegate「ViewControllerBからViewControllerCに移動するためのメソッドを準備。」

画面が増えれば増えるほどただ画面遷移させるだけのメソッドがどんどん量産されていく事になりますし、やっぱりViewControllerBをViewControllerDにしようといったときにもこの手間が発生してしまいます。
protocolの定義、クラスの定義、メソッドの本体の3箇所を編集するのは正直面倒くさいですよね。



・シーン管理を使おう

そこでNavigationControllerを使用した上で、画面遷移時のメソッドをまとめて管理することを考えます。
NavigationControllerの使い方についてはAppDelegate自体にNavigationControllerを持たせて

[window addSubview:[navigationController view] ];

とした上でNavigationControllerに付属している

pushViewController:animated:
popViewControllerAnimated:
popToRootViewControllerAnimated:
popToViewController:animated:

を使ってViewController自体を入れたり出したりします。
肝心なシーン(ViewController)の管理については

NSArray sceneArray = [NSarray arrayWithObjects:ViewControllerA, ViewControllerB, ViewControllerC, nil];

とまずひとつの配列に全てのViewControllerを格納してしまった上で、

#define APPSCENE_A 0
#define APPSCENE_B 1
#define APPSCENE_B 2

@protocol AppDelegate
(void)pushScene;
(void)popScene;
@end

と定義し、

[AppDelegate pushScene:APPSCENE_A];
[AppDelegate popScene];

という感じで呼び出せるように実装します。
これで一つのメソッドでさまざまなシーンを呼び出せるようになりましたね。



・シーン管理の拡張

上記のようなシーン管理をすることで、ViewControllerの切り替わり時に必要な処理も定義してしまうことができます。
例えば次のようなprotocolを用意してみます。

@protocol AppScene
(void)initScene;
(void)endScene;
(void)sleepScene;
(void)resotreScene;
@end

これを

ViewControllerA : UIViewController
ViewControllerB : UIViewController
ViewControllerC : UIViewController

という風に管理する全てのViewControllerに適用してあげることで、pushSceneやpopSceneの中から遷移時に必要な処理を実行させることができます。
ただしinitSceneについてはViewControllerのViewDidAppearで十分ということもありますし、sleepSceneやresotreSceneを使う機会があまりない場合には、ViewControllerBの中でViewControllerCを呼び出したフラグ等を用意して管理するのが楽かもしれません。(もともとViewControllerBの反応がトリガーになりますしね。)
またrestoreSceneなんかはNavigationContrller自体のViewControllerのスタック管理と並行して、AppDelegate側でもシーンをスタック管理することが必要になったりします。



・フェードインとフェードアウト

最後にちょうど今日会社で実装してみたフェードインとフェードアウトについて考えます。
シーン管理の方式をとるとViewController同士の接続が途中で分断されているため、フェードインとフェードアウトの実装が雑多な状況になるかと思います。

ViewControllerA:「よし、フェードアウトして真っ白になったらpushSceneするぞ」
ViewControllerB:「Aがフェードから来るから俺はフェードインするぞ」

これの一番の問題点はやはり拡張性が非常に低いということで、やっぱり黒フェードに変更しようとか、画面の接続を変えようという場合に絶望します。私自身もソースが本当に汚い状態になっていましたので、次のようにwindowにフェード用のUIViewを追加でaddSubviewをする仕組みを考えました。

[window addSubview:[navigationController view] ];
[window addSubview:fadeManagerView];

上記の後、fadeManagerViewに白マット画像や黒マット画像を読み込み、アルファ値を調整してフェードを実現します。
ここで次の新しいpush, popのprotocolを定義すると今までのシーン管理の苦労が報われることになります。

@protocol AppDelegate
(void) pushScene:(NSUInteger)sceneID
 FadeType:(FMFadeType)
 duration:(NSTimeInterval)duration
 selector:(SEL)selector;
@end

実際の実装自体はクラスを分けたりしていますが、上記メソッドでフェードアニメーションを開始、中央値(画面が一色)のときにpushScene、フェードアニメーション終了時点で遷移先のメソッドを呼び出すという形で実装することができ、コード全体の2割ほどが不要となりました。
※遷移先のメソッドを呼び出すには先述のNavigationController内のスタック管理と並行した独自のスタック管理が必要です。


・まとめ

web上ではViewControllerの管理に関する文献も少なかったので、今回はちょっと独自の内容を書いてみました。
シーン管理をすることで最終的には色々な手間を省く事ができますし、根本はコピペで使い回すことができるので生産性もとても良いと思います。私自身も2週間とまだまだ経験が浅いですが何か問題やひらめいたことがあれば、このブログで追記していければと思います。

MPMediaItemCollectionによる再生リストの保存

iPod Libraryとの連携においてはApple公式にあるAddMusicというサンプルが非常に秀逸で、これとiOS Referenceさえ見ておけば大体の事は解決しそうな感じで初心者の方には超おすすめなんですが、今回persistentIDまわりでちょっと危うい箇所があったのでブログに書き残しておきます。

AddMusic https://developer.apple.com/library/ios/#samplecode/AddMusic/

iPod Libraryとの連携で再生リストを作る場合って相当偏っている人でいなければMPMediaItemCollectionで管理すると思うんですが、内部的にunsigned long long で扱われているpersistentIDをNSNumberのままNSUserDefaultsで保存すると、読み込みに失敗するよっていう話です。

NSUserDefaultsの保存と読み込みにおいてはNSUserDefaults自身が型を判定している訳ではなくて、オブジェクトに対して「保存するから専用形式に変換した値を投げてくれよ」と言うんですが、persistentIDをNSNumberのまま保存するとNSNumberは自身をNSStringに変換して渡すようです。

さて、渡すまでは全然問題はなくて、問題は読み込みの際です。
persistentIDは内部的にはunsigned long longという型で保持されており、persistntIDからNSNumberの変換時には数値から数値の変換でうまくいくようですが、保存されたデータ読み込み時のNSStringからNSNumberに変換する際にunsigned long long と判定されないようです。

この問題については調べてる途中で下記の文献を見つけていたのでなんとか回避できました。非常にありがたいです。

MPMediaItemPropertyPersistentID検索でハマったこと

こちらのブログにあったコードを参考にしつつ保存と読み込み用関数を書いてみましたが、ここで重大発表。
アドバイスを頂いた@sawat1203さんの話によるとpersistentIDは一意ではなく、iTunesとの同期時に値が変化してしまうため再生リストの保存には向いていないようです...。
とりあえずpropertyとMPMediaItemCollectionの連携という点において大まかな機構は変わらないと思いますので、保存と読み込に関して私が書いたコード全文を下記に記載しておきます。さて、どのpropertyが一番信用できるのでしょうか、考えてみます。


// NSString から NSNumber(unsigned long long)への変換時に使用
unsigned long long myfanc(const char *p)
{
unsigned long long n = 0;
/* 数値の取得 */
while (isdigit(*p)) {
n = n * 10 + *p - '0';
p++;
}
/* 結果を返す */
return n;
}

// UserMediaItemCollectionの内容をUserDefaultsに保存する関数
(void)saveUserMediaItemCollection
{
NSLog(@"saveUserMediaItemCollection called.");

if([userMediaItemCollection count]){

NSMutableArray *persistentArray = [NSMutableArray array];

// userMediaItemのpersitentIDをNSMutableArray->NSStringに格納する
for(NSUInteger i = 0; i < [userMediaItemCollection count]; i++){
MPMediaItem *mediaItem = [[userMediaItemCollection items] objectAtIndex:i];
NSString *persistentID = [[NSString alloc] initWithFormat:@"%@",
[mediaItem valueForProperty: MPMediaItemPropertyPersistentID]];
[persistentArray addObject:persistentID];

//test code
NSLog(@"save persistentID = %@", persistentID);
}

// NSMutableArrayを保存する
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:persistentArray forKey:USER_MEDIA_ITEMS_KEY];
[userDefaults synchronize];

}
}

// UserMediaItemCollectionの内容をUserDefaultsから読み込む関数
(void)loadUserMediaItemCollection
{
NSLog(@"loadUserMediaItemCollection called.");
NSArray *loadMediaItems = [[NSUserDefaults standardUserDefaults] objectForKey:USER_MEDIA_ITEMS_KEY];

if(loadMediaItems){
NSLog(@"loadMediaItems is not nil");

// 取得した配列の全要素を復元する
for(NSUInteger i = 0; i < [loadMediaItems count]; i++){

// NSStringからNSNumberへ復元する
NSString *persistentString = [loadMediaItems objectAtIndex:i];
const char *pS= [persistentString UTF8String]; //C言語で使える文字列に変換する
unsigned long long pid_num = myfanc(pS); //変換する
NSNumber *persistentID = [NSNumber numberWithUnsignedLongLong:pid_num]; //Number型に直す

// for debug
NSLog(@"persistentID(NSNumber) = %@", persistentID);

// persistentIDをもとにqueryを生成しHitしたMPMediaItemをNSArrayに格納する
MPMediaQuery *query = [[MPMediaQuery alloc] init]; // songsQuery];
MPMediaPropertyPredicate * pred;
pred = [MPMediaPropertyPredicate predicateWithValue:persistentID
forProperty:MPMediaItemPropertyPersistentID
comparisonType:MPMediaPredicateComparisonEqualTo];
[query addFilterPredicate:pred];
NSArray *addMediaItems = [query items];

// HitしたMPMediaItemをuserMediaItemCollectionに格納する
if(addMediaItems){
if(userMediaItemCollection){
// queryから生成したアイテムと既存アイテムを結合して保存する
NSMutableArray *combinedMediaItems = [[userMediaItemCollection items] mutableCopy];
NSArray *addMediaItems = [query items];
[combinedMediaItems addObjectsFromArray: addMediaItems];
[self setUserMediaItemCollection: [MPMediaItemCollection collectionWithItems: (NSArray *) combinedMediaItems]];
}else{
// NSArray(MPMediaItem)からMPMediaItemCollectionを生成する
userMediaItemCollection = [[MPMediaItemCollection alloc] initWithItems:addMediaItems];
}
}

}

}
}

音階素材を自作してみた

ドレミファソラシドの音階ごとのサウンドファイルが欲しかったんですが、簡単に見つからなかったので心が折れる前に自作してしまいました。
作成中のアプリの都合上、基本かなり短めの音でとっていますがテスト用としては問題ないと思います。
wave形式とcaf形式の両方を用意したので必要な方はご自由にお使いください。

https://github.com/letsspeak/MagicSound