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

2009年10月11日日曜日

UIViewControllerのtouchesBeganとかtouchesEndedが上手く機能しなかったと思ったら・・・

ひさっびさに普通のUIKitを使ったiPhoneアプリを作ったりし始めたらかなりの範囲を忘れてしまっていて大ハマりしてます。中でも一番困ったのがこれ。
@implementation AbesiViewController

- (void)viewDidLoad
{
[super viewDidLoad];
self.wantsFullScreenLayout = YES;
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}

- (void)dealloc
{
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesBegan");
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesMoved");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesEnded");
}

@end
とまぁ、なんの変哲もないUIViewControllerにタッチを扱うためのイベントハンドラを搭載してみただけなのですが、コレがまぁ動かない動かない!

こういうときに考えられる原因は、だいたいがInterface BuilderでInteraction Enabledのチェックを入れ忘れているとか、ViewControllerとViewをつなぎ忘れているとかそんなのばっかりなので真っ先に調べてみました。が、やはり問題は見つかりません。

試しにUIViewControllerではなくUIViewにtouchesBeganを載せてみるとコレが動くんです。ああ、OS 3.1.2ぐらいから挙動が変わったのかなとか思っていたら、

http://stackoverflow.com/questions/1025574/uiviewcontroller-not-receiving-touchesbegan-message
OK, I'm a dummy. It works fine. The problem was, I didn't realize I was sending a release message to the UIViewController without having retained it elsewhere first. So that was causing the problem.
あーーー!そうだ!!!UIViewControllerをretainしてない!!!orz

retainしたら解決しました。ほんと腕がなまってる・・・

2009年9月1日火曜日

多少マシになったかな




相変わらずこんなしょぼっちい物を作っていたりします。


■前回からちょっとだけ改善
敵が出るようになりました(キャラの向き滅茶苦茶ですが)。
弾が出るようになりました(タダの自機狙い3Wayですが)。
ちょっとずつデザエモン程度には近づいてきたかなぁと。
あとは自機が動いて弾が出れば一応STGっぽくはなります。

今回作ったところまでの経過をgithubにうpしてみました。
http://github.com/akisute/MyShooting/tree/338e164b4716ec27a6a9b8d384f7714243f53d2e

最新のソースはこちら。
http://github.com/akisute/MyShooting/tree/master


■今回の気づき
弾の発射とか敵の出現タイミングをフレーム単位で計測して実行しているのですが、cocos2dのフレーム時間はかなりばらつくようです。弾が200発程度出ただけでフレーム時間が2倍になったりします。要するに弾の発射間隔が2倍になります。ちょっとこれはいただけない感じ。毎フレームごとに前フレームから何秒経過したかが取得できるので、それを元に時間単位で発射タイミングを測った方がいいかもしれません。

あとはActionがやたら重いです。何が重いってActionの動き自体よりもActionオブジェクトの作成と解放に時間がかかっている感じです。頻繁に起動が変わったり単純直線移動しかしない動きを再現するとき(たとえば高速弾や自機コントロール)は、昔ながらのvx, vyを使った方法のほうが軽くて良さそうです。

2009年8月29日土曜日

どのクラスのインスタンスを作っているかに注意しましょう・・・

突然ですが問題です。以下のソースはコンパイルできますがバグがあります。
http://github.com/akisute/MyShooting/blob/4570825e7384157d3572b689cc23f9bb3acc0b82/Classes/MSTGObjectFactory.m

さて、どこが間違っているでしょうか?




■なんて突然言ってもわからないですが
正解はこちら。
+ (MSTGObject *)cannon
{
// ここが間違い。MSTGObjectではなくてMSTGCannonのインスタンスを生成する必要がある
MSTGObject *object = [MSTGObject spriteWithFile:@"cannon.png"];
return object;
}
で、MSTGObjectはライフがデフォルトゼロなので、生成された瞬間に即死して画面から退場してくださるという悲しいバグが。うーむJavaではこんな情けないバグ作ったりはしないのですが・・・ともかくこれで一歩前進。

イニシャライザとかコンポジットとか考えた結果がこれだよ!

前回の記事から引き続きCocos2Dでちょこちょこアプリを書いてます。
ソースコードも公開してみました。
http://github.com/akisute/MyShooting/tree/master


■で、あれからどうなった
全面作り直しorz

コーディングを進めていくうちに次々と問題点が噴出してきたのですが、その中でもこれはまずいと思ったのが2点。


■問題点その1.オブジェクトリストの管理が面倒
画面への描画を担当してくれるCocosNodeにSpriteを登録するのとは別にオブジェクトを管理するためのマネージャクラスを持っていたため、オブジェクトを追加するたびにこんなコードを書かなくちゃいけなかったんです。
// MSObjectの親ノード(Layer)の中で・・・
MSObject *object = [[[MSBullet alloc] initWithPosition:ccp(100,100)
angle:0.0
speed:200.0] autorelease];
[self addChild:object.node];
[[MSObjectManager sharedInstance] addObject:object
forOwner:kMSObjectOwnerEnemy];
これだけならまだしも、今度は消すのが面倒。
[self removeChild:object.node];
[[MSObjectManager sharedInstance] removeObject:object];
どちらかを消し忘れるとエラーになりますし、ループ中にMSObjectManagerからインスタンスを消すとエラーになったりと結構散々です。この問題点1はまだCocosNodeにaddMSObject:forOwner:とかremoveMSObject:みたいなメソッドを追加すればなんとか対応できたのですが、もっと深刻な問題点2が。


■問題点その2.オブジェクトクラスの中でscheduleが使えない
これで完全に積みました。要するにこんなコードが書けないわけです。
// MSObjectの中で
// 毎フレームごとに呼び出されるメソッドを定義する
- (void)onFrame:(ccTime)dt
{
// 死亡判定してみたり
// 衝突判定してみたり
// ライフを回復したり無敵フレームを設定してみたり
}

- (id)init
{
if(self = [super initWithFile:@"ship.png"])
{
// sceduleメソッドはCocosNodeのメソッドなのでこうやって登録する
// しかしschedule出来るのはCocosNode自身が持っているメソッドのみ
// そのためこの方法でメソッドを登録しても
// 実行時に「spriteにはonFrameなんてメソッドがありません」とエラーになる
[self.sprite schedule:@selector(onFrame:)];
}
return self;
}
これを回避するにはMSObjectにスケジューラを自分で実装する必要が出てきて・・・ってそれもうCocos2dでやる意味なくね?ということで結論。Cocos2dはCocosNodeを継承して使え。


■作り直しの結果
MSObjectをコンポジットではなくSpriteのサブクラスにして解決。ついでに名前をMSTGObjectに改名しました。イニシャライザが漏れてややこしいと言う問題が発生してしまいますが、そこは以下のようにファクトリクラスを使って解決。
MSTGObject *ship = [MSTGObjectFactory ship];
MSTGObject *bullet = [MSTGObjectFactory bullet];

2009年8月8日土曜日

cocos2d細かいところメモ

cocos2dを使っていて適当に気づいたところとかメモしてみます。

■ログ出力
CCLOGというマクロccMacros.hで定義されています。
#ifdef DEBUG
#define CCLOG(...) NSLog(__VA_ARGS__)
#else
#define CCLOG(...) do {} while (0)
#endif
注意点としてDEBUGがdefineされていないと使えません。


■ベクトル演算とか角度変換とか
CGPointを拡張してベクトル演算をするためのメソッドが追加されています。なかなか便利です。たとえばこんな感じ。
    // 二つのベクトルのdot(内積)とlength(ベクトルの長さ)を計算してcosθを求める
float dot = ccpDot(lastAccerelometerVector, convertedVector);
float a = ccpLength(lastAccerelometerVector);
float b = ccpLength(convertedVector);
float cosTheta = dot / (a*b); // 注意:a*bが0だとゼロ除算で死にます。真似しないでね><
// 角度→ラジアン変換用のマクロ。逆ももちろんあります
float threshold = cos(CC_DEGREES_TO_RADIANS(120.0));
Chipmunkにも同様のメソッドがあります。お好きな方をご利用いただけますが、個人的には全部cocos2dでやる方が好きです。唯一の問題は、こんなメソッドを使っているとベクトル内積の計算式を忘れます。


■音を出力する方法
最初からCocoa Touchで用意されているAudio Queue Service, OpenAL, AVAudioPlayer, Audio Unitに加えて、さらにcocos2dについてくる音再生用ライブラリとして
  • CocosDenshionとSimpleAudioEngine
  • sound-engine
これだけたくさんの中から選択できます。今回は一番簡単なSimpleAudioEngineという奴を使ってみました。
        // prefetch sound resources
SimpleAudioEngine *audioEngine = [SimpleAudioEngine sharedEngine];
[audioEngine preloadEffect:@"bell.aif"];
[audioEngine preloadEffect:@"gong.aif"];
[audioEngine playEffect:@"gong.aif"];
たったのこれだけです。playEffectでサウンドエフェクトが、playBGMでBGMが再生できます。同時再生数とか使用可能なファイルタイプとか細かいところは不明ですが、そこそこの量が同時に出せるみたいです。
注意点としてSimpleAudioEngineはCocosDenshionを使用しています。CocosDenshionのライセンスはcocos2dと独立しており、こちらは年間2500ドル以上の利益が出ている場合には500ドルのライセンス料を徴収するとかなんとかそういう条項があります。(http://www.cocos2d-iphone.org/wiki/doku.php/cocosdenshion:licenseを参照)


今はこのcocos2dの上にどのような構造でアプリを組んでやろうかとか考えてます。・・・あー、こんなこと考えてるからいつまで経っても本題のアプリが完成しないんだー ><

2009年8月7日金曜日

cocos2d for iPhone Project Template v0.8 真似して作ってみました



cocos2d導入キット(http://d.hatena.ne.jp/Seasons/20090511/1241990196)としてid:Seasonsさんが公開されているキットを元に、私もcocos2d用のプロジェクトテンプレートを作ってみることにしました。id:Seasonsさん、すばらしいキットをありがとうございます!

今回の作業にあたり参考にしたページはこちら。
http://d.hatena.ne.jp/griffin-stewie/20090315/p1

作成したテンプレートはgitに公開いたしましたので、よろしければ使ってみてください。
http://github.com/akisute/cocos2d-xcode-template/tree/master


■インストール方法
downloadタブから0.8をダウンロードしてきて、解凍したファイルを以下のパスに配置してください。
~/Library/Application Support/Developer/Shared/Xcode/Project Templates
配置後、.gitと.gitignoreファイルを削除してください。削除しないと後からテンプレを使って新しいプロジェクトを作ったときに、これらのファイルがコピーされてしまいgitを使おうとしたときに問題が発生する可能性があります。


■テンプレートの内容
id:Seasonsさんが公開されている0.8beta用のテンプレートを元に、一部自分の気になった箇所を自分好みに修正しました。また、使用するcocos2dのバージョンを0.8betaから0.8リリース版に変更しました。一応ビルドして画面が出るところまでは確認しています。ただし、全機能をテストしたわけではないので、一部不具合があるかもしれません!
また注意点として、簡素化のため元のcocos2dや外部ライブラリに付随していたREADMEやドキュメント、LICENSEファイルなどをすべて削除してしまっています。おそらくは大丈夫だと思いますが、ライセンス的に問題が発生するかもしれません。大変申し訳ありませんが、このテンプレートを利用した際に生じる一切の不具合について当方では責任を負いきれませんのでご了承ください。


■id:Seasonsさんのテンプレートから変更したところ一覧
  1. 使用するcocos2dのバージョンを0.8リリース版にした。
  2. プロジェクトのグループ構成およびディレクトリ構成を変更した。この変更により、本来システムが利用する/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application以下ではなく、ユーザーのライブラリである~/Library/Application Support/Developer/Shared/Xcode/Project Templates以下にテンプレートを配置しても動作するようになった。
  3. MenuSceneとGameSceneが最初のテンプレとして用意されていたのを統廃合し、MainSceneひとつだけにした。
  4. ビルドの設定を必要に応じて変更し、またGCC_PREPROCESSOR_DEFINITIONSにDEBUGを追加した。これにより最初からデバッグビルド時にCCLOGマクロが正しく機能する。
  5. ソースコードのインデントがまちまちになっていたのを、すべてApple式のインデントに修正。
  6. ソースコード中から日本語コメントをすべて除去し英語コメントに置換。
  7. 一部使用されていたdepreciatedメソッドを除去。[[Director sharedDirector ] setDeviceOrientation:CCDeviceOrientationLandscapeRight];を使うようにした。


■思わぬ収穫
この作業によってXcodeの仕組みとテンプレートの作り方、ビルドの仕方などにかなり詳しくなることができました。たとえば・・・
  • Xcodeのテンプレートファイルは単なる普通のXcodeプロジェクトと何ら変わらない。そのままビルドして実行することもできる。
    唯一異なる点が、xcodeprojバンドルの中に、TemplateIcon.icnsとTemplateInfo.plistというファイルが存在し、これらを変更することでテンプレートとしての設定を変更することが出来る点。
  • libhogehoge.a(静的ライブラリ?)の作り方。ターゲットに新規ターゲットを追加して、必要なソースをぽいぽい投げ込むだけ。antでbuild.xmlを書くより簡単。
  • ___PROJECTNAMEASIDENTIFIER___.pchの配置パスを変更したときは、ターゲットのビルド設定のPrefix Headerの値を書き換える必要がある
  • 同様に、___PROJECTNAMEASIDENTIFIER___-Info.plistのパスを変更したときも、ターゲットのビルド設定のInfo.plistファイルの値を書き換える必要がある
  • main.mは何処に置いておいても大丈夫らしい

ということで、Xcodeプロジェクトのテンプレートを作成するのは凄く勉強になります。皆さんも一度自分好みのテンプレートを作成してみてはいかがでしょうか?

2009年8月4日火曜日

cocos2d for iPhoneをためしてみました



YourTurnが一段落したので、今度は前々から一度やってみたかったゲームにチャレンジしてみようと思い、cocos2d for iPhone(http://code.google.com/p/cocos2d-iphone/)を使い始めました。cocos2d for iPhoneとは、iPhone上で簡単に2Dのゲームを作成するためのフレームワークです。FPS管理、アニメーション、パーティクル、シーンの管理およびアニメーション付きの切り替え、メニュー、タッチおよび加速度イベントの管理など、ゲームに必要な要素をほぼすべて網羅してくれています。さらにオプションとしてBGMやサウンドエフェクトの管理と、2D物理エンジンも用意されており、まさに至れり尽くせりといった感じです。

まずは手始めにチュートリアルをやってから、Tic Tac Toe(いわゆるタダのマルバツゲーム)を作ってみました。
ソース全体 http://github.com/akisute/Cocos2DTest/tree/master
ヘッダ部分のみ http://gist.github.com/160244
実装部分のみ http://gist.github.com/160246

この程度の内容のソースでパーティクルも作れるし音も鳴らせるしで、本当に簡単です。ここまで簡単だとRPGツクールより簡単かもしれませんw
今までゲームプログラミングの経験が全然無い人のとっかかり用としては最適ではないでしょうか?

2009年7月19日日曜日

YourTurn 1.1 サブミットしました

YourTurn 1.1をApp Storeにサブミットしました。現在レビュー待ちです。
http://wiki.github.com/akisute/YourTurn


■そもそもこのアプリ何?
学会タイマーアプリといえばわかりやすいと思います。要するに参加者ごとに時間を決めて割り振り、時間が来たらタイマー音がなる、それだけのアプリです。





2分程度のビデオデモを作ってみましたので、よろしければご覧ください(無音ですけど・・・)。


順調にレビューが進めば2週間後の8月上旬にはストアに並ぶと思います。無料、というかソースも全部公開してます。気が向いたらプレゼンしたりするときにでも使ってやってください。

iPhone OS 3.0のTableViewでscrollEnabledをNOにするとtableView:didSelectRowAtIndexPath:が機能しない

http://forums.macrumors.com/showthread.php?t=470266

OS2.2のころはscrollEnabledをNOにしても正常にdidSelectRowAtIndexPathが動いていたので、これは正直結構困ります。仕方がないのでscrollEnabledを使うのを回避して、スクロールするように戻しました。OS 3.1では直っているとよいのですが。

iPhone付属のPhotos(写真)アプリのような、回転可能な全画面表示ビューを作る方法



Photos(写真)アプリに使われている、全画面ビューを真似して作ってみました。具体的には以下のような仕様になります。
  • ステータスバーの後ろも含め、ビューの内容が全画面(320x480)で表示される
  • 画面をタップするとステータスバーとナビゲーションバーが消える
  • もう一度タップすると再度表示される
  • iPhoneを傾けると画面が回転する
参考にしたページは以下のとおり。
http://life.ponpoko.tv/?p=533

また、サンプルソースも公開してます。こちらのソースの、- (void)fullScreenMode:(BOOL)mode animated:(BOOL)animatedがフルスクリーン表示を実現しているメソッドになります。


■注意
iPhone OS 3.0以上専用です。2.2以下では別の方法を取る必要があります。


■まずはStatus BarとNavigation Barを消す
http://life.ponpoko.tv/?p=533 こちらのページに書いてある内容を100%そのままパク参考にさせていただければ何も問題ありません。iPhone OS 2.2までは記載されている通りのworkaroundが必要になりますが、3.0以降ではバグが修正されているため、単にStatus BarとNavigation Barを消せばOKです。

ただし一点、[self.navigationController setNavigationBarHidden:YES animated:YES];を使うと、ナビゲーションバーが上方向に対してぴょこんと引っ込むようなアニメーションになります。ステータスバーはその場でフェードアウトして消えるので、なんだか不自然な感じに見えます。Photos.app(写真アプリ)など、iPhone標準のアプリと同様にナビゲーションバーをフェードアウトさせて消したい場合には、UIViewのアニメーション機能とalpha値を利用して以下のようにします。
    if (animated)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
}
self.navigationController.navigationBar.alpha = mode ? 0 : 1;
if (animated)
{
[UIView commitAnimations];
}
Three20プロジェクトのソースから拝借してきました><


■Viewを全画面配置する(Status BarとNavigation Barのあった場所にも描画させたい)
さんざん悩んだあげく、実はUIViewControllerにそのためのプロパティがあったと知り、ものすごく簡単に解決しました。
- (void)viewWillAppear:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;
// このUIViewController.wantsFullScreenLayoutをYESにするとフルスクリーン表示になる
self.wantsFullScreenLayout = YES;
}
ついでにステータスバーとナビゲーションバーを透明にして裏が透過するようにしてみました。


■ビューの回転に対応したい
何も考えずにshouldAutorotateToInterfaceOrientationすればいいだろう常考
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
とおもったら思わぬ落とし穴が。確かに全画面表示にした状態でビューを回転させたり、逆にナビゲーションバーが出ている状態でビューを回転させたりする分にはこれだけで全く問題ないのですが、
  1. ナビゲーションバーを消す(全画面表示にする)
  2. iPhoneを傾けてビューを回転させる
  3. 再度ナビゲーションバーを表示する
という手順を踏むと、再度表示したときにナビゲーションバーが崩れる問題が発生。結局ナビゲーションバーを消したり再表示したりする際に、以下のようなworkaroundをするハメに。
    // Force set the frame of the navigation bar
CGRect frame = self.navigationController.navigationBar.frame;
frame.origin.y = 20.0;
self.navigationController.navigationBar.frame = frame;


■全画面表示をしている画面から前の画面に戻るとバグる問題
これで完成かと思えば、最後にもう一つ落とし穴が。
  1. iPhoneを横向きに傾けて、Landscape表示にする
  2. ナビゲーションバーを表示する
  3. そのまま横向きになっている状態で、ナビゲーションバーの戻るボタンを押して、前の画面に戻る
  4. 前の画面に戻る際に、wantsFullScreenLayoutをNOにして通常画面モードにし、ステータスバーとナビゲーションバーのスタイルもDefaultに戻す
という風な操作を行うと、なぜかナビゲーションバーのスタイルだけがDefaultに戻らず、表示も崩れるという問題が発生しました。たぶんOS 3.0のバグだと思います。
いろいろ試してみた結果、全画面表示をしている画面ではなく、前の画面のviewWillAppearのタイミングで以下のコードを呼びだせば問題が解決しました。
- (void)viewWillAppear:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
self.navigationController.navigationBar.barStyle = UIBarStyleDefault;
}

CGGradientを用いてUITableViewCellを描画し、テーブルをカッコよく見せる方法

デフォルトのUITableViewCellの背景が白くてのっぺりでいまいち味気ないと思い、背景にグラデーションを付けてかっこよく見せる方法を調べてみました。単純に別途用意した背景画像をbackgroundViewに表示してもよいのですが、Cocoa Touchの2Dグラフィックスライブラリにはグラデーションを描画するためのCGGradientというクラスが最初から用意されています。さっそく私もパクってインスパイアされてやってみました。

参考にしたページはこちら。
http://developer.apple.com/documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html#//apple_ref/doc/uid/TP30001066-CH207-TPXREF101


■どこで描画するか
  • UITableViewCellのdrawRectで直接描画。
    少しでも高速に描画したい場合にはこの方法
  • 新規にUIViewを継承した背景用Viewを作成しセルのbackgroundViewに設定。そのViewのdrawRectで描画
    複数のUITableViewCellで同じ背景を適用したいときはこの方法が便利

■まずは実際に描画してみる
drawRectの中でCGContextを作成して、続いてCGGradientを生成。CGGradientを作るためにはCGColorSpaceとか色を指定する配列とかが必要になるのでそれらも生成。最後に生成したCGGradientオブジェクトを描画するという流れになります。ということでソースを見てみましょう。
- (void)drawRect:(CGRect)rect
{
// CGContextを用意する
CGContextRef context = UIGraphicsGetCurrentContext();

// CGGradientを生成する
// 生成するためにCGColorSpaceと色データの配列が必要になるので
// 適当に用意する
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
0.79, 0.79, 0.79, 1.0 }; // End color
colorSpace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorSpace, components,
locations, num_locations);

// 生成したCGGradientを描画する
// 始点と終点を指定してやると、その間に直線的なグラデーションが描画される。
// (横幅は無限大)
CGPoint startPoint = CGPointMake(self.frame.size.width/2, 0.0);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
}
このコードを実行すると・・・


はい!もうグラデーションができました。嘘みたいに簡単です。

2010/05/24追記:注意点として、CGGradientとCGColorRefのオブジェクトは手動でリリースしないとメモリリークが発生します!


■UIColorを元にグラデーションを作る
先ほどの例では配列にRGBA要素を渡してグラデーションを作りましたが、UIColorが使えるともっとお手軽で、しかもRGBAだけではなくてHSBAで色が指定できて何かと便利です。ということで、次はUIColorからグラデーションを作ってみます。

UIColorから直接CGGradientを作ることは出来ないので、途中でCGColorとCFArrayRefを作り、それを元にCGGradientを生成してみます。コードはこちら。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *currentColor = [UIColor colorWithHue:currentBackgroundColorHSBA[0]
saturation:currentBackgroundColorHSBA[1]
brightness:currentBackgroundColorHSBA[2]
alpha:currentBackgroundColorHSBA[3]];
// gradient background
CGGradientRef grad;
CGColorSpaceRef colorSpace;

// UIColorからCGColorを取り出すのはとっても簡単
CGColorRef currentColorRef = [currentColor CGColor];
CGColorRef voidColorRef = [[UIColor colorWithHue:0.0
saturation:0.0
brightness:0.17
alpha:1.0] CGColor];
CGColorRef colorArray[2] = {voidColorRef, currentColorRef};
CGFloat locations[2] = { 0.0, 1.0 };

// CFArrayRefを作る。
// もっと簡単に作りたければ、NSArrayを作ってからCFArrayRefにキャストするだけでもよい。(未確認)
CFArrayRef colors = CFArrayCreate(kCFAllocatorDefault, (const void **)colorArray, 2, &kCFTypeArrayCallBacks);

colorSpace = CGColorSpaceCreateDeviceRGB();
// Gradientを生成する
grad = CGGradientCreateWithColors(colorSpace, colors, locations);

// 描画する
CGFloat progress = currentTime / allottedTime;
CGFloat height = self.frame.size.height + HEIGHT_GRADIENT;
CGPoint startPoint = CGPointMake(self.frame.size.width/2, progress * height - HEIGHT_GRADIENT);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, progress * height);
CGContextDrawLinearGradient(context,
grad,
startPoint,
endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(grad);
}
CGContextDrawLinearGradientの第4引数にkCGGradientDrawsBeforeStartLocationフラグとkCGGradientDrawsAfterEndLocationフラグを設定しています。これらのフラグを指定すると、startPoint以前、およびendPoint以降をグラデーションの開始色と終了色でべた塗りしてくれます。実行結果は以下の通り。


こんな感じで、画面中央から下が全部べた塗りになっているのが分かると思います。

他にも、CGGradientを作る際にlocationsを増やせば2色だけではなくて多色のグラデーションを作成することが出来たりします。このあたり、Appleが公開しているドキュメントだけでも相当詳しく紹介されているので、そちらを見ればほぼ間違いないかと。


■サンプルソース
今回のサンプルはgithubですべてソースを公開して居ますので、より詳しく学びたい方はそちらも併せてご参照ください。
http://github.com/akisute/YourTurn/tree/master
これとかこれを見るとよいかと思います。

2009年7月12日日曜日

EditControlとAccessoryViewの背景は透明



  • EditControlの背景はデフォルトで透明
  • AccessoryViewの背景もデフォルトで透明
  • UILabelの背景はデフォルトで白塗りつぶし(透明にはならない)

OS 2.2.1での実験結果なので、3.0では違うかも(たぶん同じ)

それだけです。お粗末様でした・・・

2009年7月7日火曜日

YourTurnをApp Storeにサブミットしてみた



2ヶ月ぐらい前からちょこちょこ作っていた物をレビューに出してみました。まぁ、どうなるかは分かりません。現在一週間経過、特に何も変化無し。今回の目的であるApp Store童貞を捨てることは見事達成できそうです><


公開されるのを気長に待ちながら、現在1.1アップデートを作成中。セルがかっこよくなりました。

2009年7月5日日曜日

iMacとiPhoneでBluetoothのヘッドセットを使ってみた

以前からずっと気になっていたBluetoothヘッドセットをこのたび購入したので、iPhone 3GやiMacとつないでみました。
使用感などをレポートしてみたいと思います。


■購入したヘッドセット
某所で紹介されていた、「Bluetoothステレオヘッドセット ELANVITAL EVSH-2882」という奴です。ネックバンド型でA2DPに対応していて通話ももちろん出来ちゃう、その上8000円未満となかなかお買い得。(ソニーとかモトローラのやつは1万6000円とかしますからね><)


■iMacで使う
まずは「環境設定」から「Bluetooth」を開いてヘッドセットとペアリングさせます。ペアリングさせると、「ヘッドセットを有効にする」と「ヘッドフォンを有効にする」の二つが選択できる様になります。

こんな感じ。

実はここに落とし穴があって、
  • ヘッドセットを有効にすると、HSPを用いてiMacとヘッドフォンが通信を行う。
    そのため音楽等を聞くとモノラルで電話並みのひどい音質になる。
  • 逆にヘッドフォンを有効にすると、A2DPを用いて通信が行われるため音質は最高になるが、
    ヘッドセットとして認識されない(要するにヘッドセット内蔵のマイクが使えない)
何この残念な状況orz

MacのBluetoothの扱いが悪いのか、このヘッドセット自体に問題があるのか、それともBluetooth2.1の規格がそもそもそんな使い方を想定していないのかは知りませんが、とにかく「Mac OS X 10.5.7上でELANVITAL EVSH-2882を使う場合、そのままではステレオ音質でヘッドセットとしてマイクを自由に使うことはできない」というのは間違いなさそうです。

それでもいろいろ試して見た結果、以下の手順を取ればステレオの音質でマイクが使えるようになりました。
  1. 「ヘッドフォンを有効」にする。
  2. 「環境設定」の「サウンド」を開き、出力と入力を以下のように設定する。

    出力は、「Bluetooth ヘッドフォン」を選択。


    入力は、「Bluetooth ヘッドセット」を選択。
この設定を行った状態でSkypeを開き、オーディオデバイスを適当に設定して友人とSkypeで通話してみましたが、無事ステレオ音質でマイクが使えました。しかしこの方法は何かしらの無理をしているようで、突然音質がモノラルになったり、突然マイクが使えなくなったりします。その上バックグラウンドで動かしていたiTunesの音が全く聞こえませんでした。ということで、おすすめできません。


■iPhoneで使う
設定方法とかはAppBankさんのこちらの記事が詳しいのでそちらをご参照をば。
http://www.appbank.net/2009/06/19/iphone-news/32183.php
http://www.appbank.net/2009/06/21/iphone-application/32492.php

使ってみた感想ですが、A2DPのおかげで音質はかなり良いです。私はオーディオマニアではないので参考にはならないかもしれませんが、有線のヘッドフォンと全く違いが分かりません。普段から1万円以上のイヤフォンやヘッドフォンを使われるような方でなければ問題ないと思います。

着信があったときの移行もスムーズです。また、着信を受けてからBluetoothヘッドセットの電源を入れて、オーディオデバイスをそちらにに切り替えても問題なく通話が出来ました(ただし友人曰く、ちょっと音が遠いとのことなので、ひょっとしたらiPhone本体のマイクで音を拾っていたのかも)

Macでは音声出力先・入力先ごとに音量を保持してくれていたのですが、iPhoneではどうやらそのような機能がないようです(すべての音量が共通)。そのため、Bluetoothヘッドセットで小さいと思った音がiPhone本体のスピーカーでは大音量と言うことがあります。ちょっと不便。

最大の問題が、「一時停止」「次の曲」「前の曲」ボタンがすべて利用できないことです。私は結構頻繁に曲を次に送ったり一時停止したりする使い方をするので、これらのボタンが使えないのが苦痛になっています。
ひょっとしたら使える機種もあるかもしれませんが、少なくとも今回私が購入したものではダメでした。気になる方はご自身で確認されることをお勧めします。

電池の消耗については、有線よりは確実に減ります。どれぐらいがっつり減るかは未確認ですが、1時間程度付けっぱなしにしていると10%ほど減っていました。(別の要因もあるので一概には決められませんが・・・)

なお、音声コントロールはBluetoothヘッドセットが使用できませんでした。ヘッドセットをペアリングさせて使える状態にしていても、本体のマイクとスピーカーが使用されてしまいました。

結論としては、聞くだけ・通話するだけ専門としてはほとんど申し分ありません。ケーブルが邪魔になるジムでの運動時などでは最大限に威力を発揮すると思います。逆に普段使いでは電池の消耗・iPodコントロール不可などのデメリットが目立つかと思います。


■iMacとiPhoneで併用
ペアリング自体は全く何の問題もありません。iPhoneとiMacの両方に同じヘッドセットをペアリングさせることが出来ました。
しかし当然ですが同時に使うことは出来ません。ヘッドセットの電源を入れた後、「先に認識を完了した」デバイスが独占的にヘッドセットを使います。認識が遅れた方はエラーになりヘッドセットを使うことが出来ません。
iPhoneとiMacの両方のBluetoothがオンになっていると、どちらが先に認識するか安定しないため、
  • iPhoneで使うときは、iMac側のBluetooth自体をオフにする
  • iMacで使うときは、逆にiPhone側のBluetoothをオフにする
という面倒な作業が必要になります。結局、私はiPhone専用としてヘッドセットを使うことにしました。

2009年6月8日月曜日

時間を入力するために、カスタムUIPickerViewを作ってみた



時間を入力するためのUIが欲しかったので、こんな感じのカスタムUIPickerViewを作ってみました。ソースコードはこちら。
http://github.com/akisute/YourTurn/blob/8119bf028acf4908edb602d277544bc2cf6a5848/Classes/YTTimePickerView.h
http://github.com/akisute/YourTurn/blob/8119bf028acf4908edb602d277544bc2cf6a5848/Classes/YTTimePickerView.m


使い方はソースを見ていただければきっとご理解いただけると信じておりますので、UIPickerViewを使っていて気づいた点などをいくつか晒してみたいと思います。


■参考にした記事
id:paellaさんのダイアリーを参考にさせていただきました。ありがとうございます。
http://iphone-dev.g.hatena.ne.jp/paella/20090521#20090521fn1

基本的にはこちらの記事にまとめられている内容に従って、
// 2) ビューを返したい場合
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {}
このデリゲートメソッドを使いカスタムViewを利用した実装を行ったのですが、一部私の実装とpaellaさんの記事で異なっている点があるので、ご紹介したいと思います。


■UILabelを直接デリゲートメソッドから返す
元記事では、
UIViewを返すデリゲート、必ずUIViewを返さないとうまく動いてくれません。このサイトやこのサイトで紹介されているようなUILabelを直接渡してあげる方法は、少なくとも私の環境(2.2.1)では何も表示されませんでした。
と、UIViewを返さなくてはダメとご指摘がありますが、どうやらUILabelでも大丈夫みたいです。ビルドターゲット = iPhone OS 2.2.1の環境で確認できました。

UILabelをそのままデリゲートから返却したときに画面に表示されなかった原因は、どうやらUILabelのframe.size.heightが0になってしまっていた(要するにつぶれてしまっていた)のが原因みたいです。そこで、以下のようなコーディングを行いました。
- (UIView *)pickerView:(UIPickerView *)picker viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
{
UILabel *label = (UILabel *)view;
if (!label)
{
label = [[[UILabel alloc] init] autorelease];
label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, 20.0, 23.0);
label.backgroundColor = [UIColor clearColor];
// 以下初期化コードなど・・・
}

return label;
}
このように、label.frameの高さを無理矢理指定してやれば、きちんと画面に出せるみたいです。また、この方法を使えば、元記事で
ポイントはビューの座標。どうも各要素での{0,0}の位置はPicker内各Rowのど真ん中にあるらしく、サンプルのように負数を指定してあげないとどこかに寄った状態になってしまうみたいです。
とご指摘があったビューの座標も、特に気にせず普通に指定することが出来るようになりました。


■Pickerを、UIScrollViewのサブクラス(UITableViewなど)にaddSubviewする場合の注意点
この記事の見出しに貼り付けた画像では、作成したカスタムPickerをGroupedスタイルなUITableViewのfooterViewに貼り付けています。
    // Initialize time picker with a previously selected value
timePicker = [[YTTimePickerView alloc] initWithFrame:CGRectZero];
NSInteger initialValue = [[NSUserDefaults standardUserDefaults] integerForKey:_USERDEFAULTS_TIMEPICKER_INITIALVALUE];
timePicker.time = (initialValue == 0) ? 300 : initialValue;
timePicker.timePickerViewDelegate = self;
[timePicker selectRowWithCurrentTime];
self.tableView.tableFooterView = timePicker;
このように、UIScrollViewのサブクラスのサブビューとしてUIPickerViewを貼り付けると、そのままの状態ではうまくPickerのドラムが回転してくれないという症状が発生してしまいます。
これはどうやらUIPickerViewのスクロールとUIScrollViewのスクロールがけんかしてしまっているみたいです。そこで、スクロールする必要のないTableView側の設定をOFFにします。具体的には、Interface Builderを利用する場合、以下の赤枠でくくった箇所を設定すると良いようです。



こうすることで、綺麗にドラムが回転してくれるようになります。

2009年5月19日火曜日

UINavigationの片方のサイドに複数個のボタンを持たせたい



こんな感じでUINavigationBarの片方のサイドに複数個のボタンをおく方法を探してみました。UIToolBarと異なり、UINavigationBarではUINavigationItem.rightBarButtonItem, UINavigationItem.leftBarButtonItem, それからタイトル部分と、最大でも3個しかアイテムを配置することが出来ません。そのため、複数のボタンを一つの配置箇所にまとめて配置したい場合には、カスタムビューを作成する必要があります。
Appleの配布しているデモアプリケーションにもありますが、こういう場合にはUISegmentedControlのmomentaryプロパティをYESに指定して、ボタンみたいに利用するのがいちばん良いようです。
NSArray *items = [NSArray arrayWithObjects:@"Add", @"YourTurn", nil];
UISegmentedControl *segmentedControl = [[[UISegmentedControl alloc] initWithItems:items] autorelease];
segmentedControl.selectedSegmentIndex = UISegmentedControlNoSegment;
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.momentary = YES;
[segmentedControl addTarget:self action:@selector(segmentedControlClicked:) forControlEvents:UIControlEventValueChanged];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:segmentedControl] autorelease];

この方法でひとまずはUINavigationItemの右側に複数のボタンを作ることが出来ました。しかしまだまだ完璧ではなくて、現地点で分かっている限りでも二つの問題があります。
  1. ボタンの背景色が通常のボタンとは異なる色になる
  2. 個々のボタンひとつひとつを個別にDisableすることができない

Appigo Todoなんかは普通にUINavigationBarの右側に3つもまとめてボタンを綺麗に配置してあったりするのですが、一体全体どうやってるんでしょうね?・・・うーん。

2009年3月19日木曜日

Your mobile device has encountered an unexpected error (0xE800003A)

いつになってもこのエラーの対処法が覚えられなくてGoogle先生にお尋ねすることになっているので、ここに備忘録を記しておきます。

1.info.plistのBundle identifierとProvisioning ProfileのApp Identifierを一致させる


これはネットのどこを見ても書いてある情報なので大丈夫だと思いますが一応。
面倒ならば、裏技的手法として、Provisioning ProfileのApp Identifierを*だけにするという手段があります。こんな風に。
ABCDEFGHIJK123.*
この方法だとオープンソースのプロジェクトを拾ってきたときでもすぐに実機インストールすることができるので、いちばんお勧めかもしれません。


2.クリーニングする(Shift+Command+K)

で、1.は毎回試すのですが、それでもうまくいかないので次はこれ。クリーニングを行います。
もしくはプロジェクトフォルダ以下のbuildディレクトリの中身をすべて消してしまっても同様の効果が得られます。


今のところこの二つを試したら毎回うまくビルド出来ているので、とりあえずはこれで大丈夫かと思います。

2009年1月24日土曜日

route-meでタッチイベントを扱いたい

  • タッチイベントを扱うときはRMMapViewDelegateプロトコルを採用する
  • シングルタッチ、ダブルタッチを感知したり、マーカー上のタッチやドラッグを感知したり、地図の移動およびズームを感知したりできる
  • 現状、マップのドラッグやズームを使用不可能にするための手段は用意されていない。Delegateの返り値による操作もできない。
  • UIMapViewにenableDraggingおよびenableZoomというインスタンス変数が用意されているが、mapView->enableDraggingのようにしてアクセスしようとするとコンパイルエラーになってしまう。

route-me上でタッチイベントを取得してみました。

こんな感じで、画面上のタップした点を取得することができます。

タッチイベントの取得方法は、
まずRMMapViewDelegateプロトコルを任意のクラスに適合させて、
@interface MapViewController : UIViewController  {
/* 中略 */
- (void) singleTapOnMap: (RMMapView*) map At: (CGPoint) point
{
NSString *message = [NSString stringWithFormat:@"(%f, %f)", point.x, point.y];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"singleTapOnMap"
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
}
/* 後略 */

適合させたクラスのオブジェクトをRMMapViewのdelegateプロパティにセットします。
RMMapView *mapView = [[[RMMapView alloc]
initWithFrame:[UIScreen mainScreen].applicationFrame WithLocation:initialLocation.coordinate]
autorelease];
mapView.delegate = self;

普通のUIViewとほとんど同じですね。

しかしこの方法では画面上の座標がとれるだけで、緯度経度を取ることができませんので、
RMMapViewのメソッドを用いて、緯度経度に変換します。
@interface MapViewController : UIViewController  {
/* 中略 */
- (void) singleTapOnMap: (RMMapView*) map At: (CGPoint) point
{
//ポイントをLatLongに変換して表示する
CLLocationCoordinate2D coordinate = [map pixelToLatLong:point];
NSString *message = [NSString stringWithFormat:@"(%f, %f)", coordinate.latitude, coordinate.longitude];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"singleTapOnMap"
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
}
/* 後略 */

これで緯度経度になりました。

ところで、地図のスクロールやズームを制限して利用したいというときがあると思います。
たとえば日本向けのアプリなのにアルゼンチンの地図をスクロールして表示されたら困るという場合です。

早速地図のスクロールを制限する方法を調べてみたところ、
RMMapViewにいかにもそれらしいメンバー変数を発見。
@interface RMMapView : UIView
{
RMMapContents *contents;
id delegate;
BOOL enableDragging;
BOOL enableZoom;
RMGestureDetails lastGesture;
float decelerationFactor;
}

しかしながらこのメンバー変数、プロパティも操作するためのメソッドもなく、外部から操作することができません。
ためしに無理矢理以下のようなコードを書いてアクセスしてみたところ、コンパイルではねられました。
RMMapView *mapView = [[[RMMapView alloc]
initWithFrame:[UIScreen mainScreen].applicationFrame WithLocation:initialLocation.coordinate]
autorelease];
//以下の行でエラー。アクセスできません
mapView->enableDragging = NO;

残念ながらまだこの機能は現在のバージョンのroute-meでは利用できないみたいです。

その他、delegateメソッドの返り値をNOにしたらスクロールしなくなるとかそういう機能がないか調べてみましたがやはりなさそうで、今のところ地図のスクロールおよびズームを制限するのは難しそうです。今後のバージョンアップに期待ですね。

2009年1月11日日曜日

iPhoneやiPhoneシミュレータ上でNSURLCacheクラスを使う

  • NSURLConnectionやNSURLDownloadを利用すると自動的にNSURLCacheにキャッシュデータを蓄える
  • iPhoneシミュレータは/private/var/folders/XX/XXXXXXXXXXXXXXXXXXXXXXX/-Caches-/iPhoneのアプリ名/Cache.dbの中にキャッシュデータを蓄えている
  • iPhone実機では、メモリ上へのキャッシュは働くがファイル上へのキャッシュは行われない。したがってアプリを終了するとキャッシュはすべて消える。
  • NSURLCacheクラスについて参考 http://episteme.arstechnica.com/eve/forums/a/tpc/f/8300945231/m/863005881931/p/5
  • [NSURLRequest setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];を使って、意図的にNSURLCacheクラスへのキャッシュを止めることもできる
  • キャッシュを停止する方法の例 http://github.com/takuma104/ntlniph/tree/master/Classes/models/NTLNHttpClient.m 76行目

HTTP通信を行う際にキャッシュを使いたい場合があると思います。
特に通信状況の良くないiPhoneプログラムでは、キャッシュを利用したいと思う機会が多いはずです。
Cocoaフレームワーク上でHTTP通信を行う場合には、NSURLConnectionクラスやNSURLDownloadクラスを利用するのが一般的だと思いますが、
これらの通信クラスを利用すると、自動的にNSURLCacheクラスのShared Instanceに通信結果がキャッシュされていくようなしくみになっています。

キャッシュされた結果は以下のようなコードで取り出せます。
  NSURL *url = [NSURL URLWithString: urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//キャッシュされたURLレスポンスを、NSURLCacheのshared instanceから取得します
NSCachedURLResponse *cachedData = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];

こうして取得されたNSCachedURLResponseは通常のNSURLResponseと同じように扱うことができます。

で、問題になってくるのはここからです。
このNSURLCacheクラスは2種類のキャッシュを内部的に持っています。メモリキャッシュとファイルキャッシュです。
メモリキャッシュの方はもはや説明不要だと思うのですが、やっかいなのはファイルキャッシュのほうです。再起動しても結果が消えないため、以前の結果が表示されると言うことが起こりえます。
iPhoneシミュレータ上で実行された場合、どこにこのURLレスポンスのキャッシュが保存されているかを調べてみたところ、
/private/var/folders/XX/XXXXXXXXXXXXXXXXXXXXXXX/-Caches-/iPhoneのアプリ名/Cache.db

の中にキャッシュが生成されていることがわかりました。(XXの部分は実行するマシンによって異なります。)
従って、キャッシュが不要になった場合はこのファイルを消してください。

2009年1月5日月曜日

route-meの、地図画像 (Tile) のキャッシュを操作したい

  • 公式のドキュメントはこちら http://code.google.com/p/route-me/wiki/CacheConfiguration
  • キャッシュにはメモリに対するキャッシュ、ファイルを利用したキャッシュ、sqliteを利用したキャッシュの3通りがある。デフォルトではメモリに32枚、sqliteに無制限に地図画像をキャッシュするようになっている。
  • キャッシュの設定を変更するにはrouteme.plistファイルを用いる
  • route-meは内部的にsqliteを処理するためにFMDBライブラリ (http://gusmueller.com/blog/archives/2008/03/fmdb_for_iphone.html) を利用しているため、route-meを利用したプロジェクトでは何もしなくてもFMDBが使える
  • たとえば、一度OpenStreetMapで開いた地図画像のキャッシュは、地図提供元をVirtualEarthにしてもOpenStreetMapにしても残ってしまうので、一度キャッシュクリアする必要がある
  • キャッシュをまとめて削除したいときはrouteme.plist中でdb-cacheのcapacityとminimalPurgeを1にする
  • またはSQLを直接発行してZCACHEテーブルをDELETEすればよい
  • またはiPhone Simulatorのdbファイルを直接Finderから削除する (~/Library/Application Support/iPhone Simulator/User/Applications/英字の長ったらしい識別子/Documents/*.sqlite) 参考はhttp://ameblo.jp/xcc/entry-10171488747.html

route-meを使っているうちに、困った事態に遭遇しました。
最初の頃に使っていたOpenStreetMapの地図画像が、いつまでたってもVirtualEarthの地図画像に入れ替わらないんです。

ごらんの通りOpenStreetMapの地図が出ます。tileSourceはVirtualEarthになっているのに!

iPhoneシミュレータをリセットしたりアプリケーションを削除したりしましたが効果なしです。
そこで原因を調査したところ、route-meは内部的にsqliteを用いた画像データのキャッシュを持っており、このキャッシュが自動的に最新のデータと入れ替わらないのが原因であることがわかりました。
(通常キャッシュはある程度保持期間が決まっていて、保持期間を過ぎたデータは破棄するようになっていると思うのですが、route-meについては少なくとも1日2日程度の期間ではキャッシュを破棄してくれないようです)
(というかsqliteのデータベースってアプリケーションごとに/var/root/Library/アプリケーション名/以下に配置されているからアプリケーションを削除したら消えるんじゃないんですか・・・?それとも全部ゴミになって残るの?単にiPhoneシミュレータが悪い?)

そこでキャッシュを破棄する方法を調べるがてら、route-meの地図画像キャッシュを設定する方法について調べてみました。
http://code.google.com/p/route-me/wiki/CacheConfiguration
公式のドキュメントをみると、"routeme.plist"というplistファイルを作成してビルドターゲットに加えることで、地図画像キャッシュの動作を変更することができるらしいです。

こんな感じで。


こうすることで、sqliteを用いたキャッシュの中に保存されるデータ量を制限することができます。
ただし、データベースの構造をみても、キャッシュ古くなったデータを自動的に削除するような機能はないようです。そのため、毎日決まった箇所の地図データを見るようなアプリの場合は、何年たっても地図の画像データが更新されません。
このように地図の画像データが更新されない恐れがある場合には、sqliteを用いたキャッシュを利用しないように設定するのがよいと思います。


さて、これで今後のキャッシュの利用方法は設定することができましたので、いますでにキャッシュされてしまっているデータを削除しましょう。
route-meではZCACHEというテーブルの中に地図画像キャッシュデータを保存していますので、このテーブルのデータを削除すればよいです。
sqliteにはTRUNCATEが無いので、DELETE文を使ってすべてのデータを削除します。
DELETE FROM ZCACHE

参考:http://www.mail-archive.com/sqlite-users@sqlite.org/msg09144.html

また、iPhoneシミュレータ上でキャッシュを消したい場合には、以下のパスにsqliteのデータベースファイルが存在しますので、そちらを丸ごと削除すればより簡単です。
(~/Library/Application Support/iPhone Simulator/User/Applications/英字の長ったらしい識別子/Documents/*.sqlite)

さぁ削除していざチャレンジ!


・・・あの、変わってないんですけど。何で?どうして?
ログをコンソールから調べてみましたが、routeme.plistが正しく読み込まれていて、間違いなくDBへのアクセスは発生していません。
となると怪しいのは地図提供元URLへのアクセスです。ここで何らかの仕組みが働いてキャッシュされているのではないかと思って調査してみました。
すると、RMWebTileImageクラスの中にこんなソースを発見。
 NSURL *url = [NSURL URLWithString: urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSCachedURLResponse *cachedData = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];

怪しいです。怪しさ大爆発です。次回はちょっと脇道にそれて、このNSURLCacheについて調査してみようと思います。