2009年08月21日(金) 23時37分13秒
iPhoneアプリ開発、その(120) 一人時間差攻撃〜!
テーマ:Xcode
「iPhoneアプリ開発、その(119)」のツヨボンさんからのコメントでわかったんですが、今回のサンプルプロジェクトってiPhone OS 3.0以降でないと駄目みたいですね。
参考にしてるAppleのサンプルScrollViewSuiteもReadMe.txtでも
とか書いてますた。
UIScrollViewの
とか全滅。
イタタタ。
とりあえず、iPhone OS 2.2.1でもコンパイル通るようにはしときます。
でも、今回やろうとしてるダブルタップでのズームインではzoomToRect:animated使うんで、これの代替案は見つかりませんでした。なので2.2.1でもコンパイルは通るし実行もデバッガで追う事もできるようにしてますが、ズームインはしません。
外から指定できないんじゃ、2.2.1でスケーリングを細かくコントロールしようとしたらピンチジェスチャから対応しないと駄目っぽいっすね。なんか他にいい方法があるとか?謎。
ちなみにプロジェクトを2.2.1用にビルドするにはプロジェクト>プロジェクト設定を編集メニューで現れるウィンドウで以下のiPhone OS Deployment Targetを変更します。

左上にある構成ポップアップメニューにはDebugとReleaseがあるので、Release版でも2.2.1にしたいなら、Releaseに切り替えてから設定
こいつをiPhone OS 2.2.1にする。

こんな感じで押すとポップアップします
これでプロジェクト>アクティブSDKを設定メニューでiPhone Device 2.2.1と、iPhone Simulator 2.2.1が選べるようになるはず。
気を取り直して、自前のジェスチャ対応の一歩としてダブルタップによるズームに進みます。
サンプルではUIScrollViewに埋め込むUIViewのtouchesBegan、touchesEndedを拡張して対応しているんだよね。
何故にUIScrollViewを拡張するのではなく埋め込み側のUIView側でやるんだろと思ったんですが、しばらくして理由がわかりました。この話は次回。
なんにせよ注目は、埋め込み側のUIView側のtouchesBeganメソッドでやってる
とtouchesEndedメソッドでやってる
ですわ。
AppleのサンプルプロジェクトScrollViewSuiteでは2フィンガータップまでサポートしてるので実装がややこしくなってるんで、まずは1フィンガーから始めます。
まずhandleSingleTapメソッドをtestView.mに移植。
ここでもオリジナルのdelegate使ってますな。
基本的にユーザーのアクションを検出して、それをどう対応するかはビューではなく移譲先に決めさせるってのがCocoaの流儀みたいですね~。
ということで私もプロトコル定義します。プロトコルについては「iPhoneアプリ開発、その(89)」で触れてますよん。
<NSObject>を付ける理由はhandleSingleTapやhandleDoubleTapメソッド内部でrespondsToSelectorてのを使うからです。別に付けてなくてもコンパイル時に「そのメソッドは持ってないんじゃないっすか?」て注意されるだけだけどね(すげーわObjective C、どこまでもゆるい)。
respondsToSelectorは指定したインスタンスが、指定したメソッドを実装してるか調べるメソッド。例えば
この場合view:gotSingleTapAtPoint:がdelegateインスタンスに実装されてるかですな。@selector?な人は「iPhoneアプリ開発、その(33)」を参照。
で、このメソッド返り値がYESなら実装されているので、安心してそのメソッドを呼び出すとしてます。
delegate変数は
でtestView.hに定義。nonatomicは特にいらないようには思うけどね。@property?な人は「iPhoneアプリ開発、号外 propertyをちょっと調べてみた」を参照。
testView.m側で
とすることで、scViewController.m側で
が使えるようになります。testview.delegateはドット構文であって、C/C++の意味合いと違う事に注意。ドット構文についてはObjective-C 2.0プログラミング言語 > オブジェクトとクラス >オブジェクトメッセージングのドット構文の章を参照。
で、本題の
こいつは、afterDelayで指定した時間経ってから指定したメソッドを実行する機能だそうです。NSObject Class Referenceに書かれています。Referenceの引き方は「iPhoneアプリ開発、その(33)」を参照。
この指定メソッドを指定時間に実行する装置を、仮にアクションキューと呼ぶと、このキューに登録する作業が上記メソッドで
が、この登録したメソッドをやっぱやめるわといってとりはぶくメソッドになるわけですな。
こいつでタップする間隔によって登録したhandleSingleTapが実行されたりされなかったりを、下の図のように制御するわけです。
タップ間隔が十分に開いている場合

タップ間隔がperformSelectorで指定したafterDelay時間よりも短い場合

2回目のtouchesBeganがDOUBLE_TAP_DELAYまでに発生しなければ、そのまま登録したメソッド(handleSingleTap)が実行され、そうでなければアクションキューから取り省かれhandleSingleTapは実行されず、そのあとの
が実行されるわけですわ。
使ってわかると思うけど、1本指か2本指かは判別してません。これは以下のメソッドを呼び出してないので複数の指情報が来ないからです。
複数の指情報を取っちゃうと、Appleのサンプルソースのようにかなりの処理を追加しないといけなくなるんですな。
ちなみに、フリックだとスクロール、タップだとズームと見事に切り替わるんですが、今回のtouchesBeganメソッド追加でフリックの時にズームされないのはなんでって思うんじゃないかと思います。
私は思いました。
なので、そこらを次回掘り下げます。
というかサンプルプロジェクトはすでに加工済み。予習したい人はコンソール見れ~とだけ言っておこう。
ではでは。
------------
サンプルプロジェクト:sc3.zip
「ダイナミックObjective-C」Web版、読みました。
前半のハッキング記事、後半のCocoaフレームワークのデザインパターンへの分類と読み応えは抜群だけど、当然ハードルは高め。秋の夜長は
詳解 Objective-C 2.0 荻原 剛志
の丁寧な説明を読んでObjective-Cの構文や仕様を勉強(これの初代坂といえるObjective‐C―MacOS Xプログラミング入門を読んでて2.0の方は未読だけど、おそらく一番のおすすめ)して
Dynamic Objective-C 木下 誠
で、Objective-Cの実装方法への理解を深める。
そんな感じなりよ。
ゲット!

参考にしてるAppleのサンプルScrollViewSuiteもReadMe.txtでも
BUILD REQUIREMENTS:
Xcode 3.2 or later, Mac OS X v10.5.7 or later, iPhone OS v3.0.
とか書いてますた。
UIScrollViewの
– zoomToRect:animated:
zoomScale property
– setZoomScale:animated:
とか全滅。
イタタタ。
とりあえず、iPhone OS 2.2.1でもコンパイル通るようにはしときます。
でも、今回やろうとしてるダブルタップでのズームインではzoomToRect:animated使うんで、これの代替案は見つかりませんでした。なので2.2.1でもコンパイルは通るし実行もデバッガで追う事もできるようにしてますが、ズームインはしません。
外から指定できないんじゃ、2.2.1でスケーリングを細かくコントロールしようとしたらピンチジェスチャから対応しないと駄目っぽいっすね。なんか他にいい方法があるとか?謎。
ちなみにプロジェクトを2.2.1用にビルドするにはプロジェクト>プロジェクト設定を編集メニューで現れるウィンドウで以下のiPhone OS Deployment Targetを変更します。

左上にある構成ポップアップメニューにはDebugとReleaseがあるので、Release版でも2.2.1にしたいなら、Releaseに切り替えてから設定
こいつをiPhone OS 2.2.1にする。

こんな感じで押すとポップアップします
これでプロジェクト>アクティブSDKを設定メニューでiPhone Device 2.2.1と、iPhone Simulator 2.2.1が選べるようになるはず。
気を取り直して、自前のジェスチャ対応の一歩としてダブルタップによるズームに進みます。
サンプルではUIScrollViewに埋め込むUIViewのtouchesBegan、touchesEndedを拡張して対応しているんだよね。
何故にUIScrollViewを拡張するのではなく埋め込み側のUIView側でやるんだろと思ったんですが、しばらくして理由がわかりました。この話は次回。
なんにせよ注目は、埋め込み側のUIView側のtouchesBeganメソッドでやってる
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(handleSingleTap) object:nil];
とtouchesEndedメソッドでやってる
[self performSelector:@selector(handleSingleTap)
withObject:nil afterDelay:DOUBLE_TAP_DELAY];
ですわ。
AppleのサンプルプロジェクトScrollViewSuiteでは2フィンガータップまでサポートしてるので実装がややこしくなってるんで、まずは1フィンガーから始めます。
まずhandleSingleTapメソッドをtestView.mに移植。
ここでもオリジナルのdelegate使ってますな。
基本的にユーザーのアクションを検出して、それをどう対応するかはビューではなく移譲先に決めさせるってのがCocoaの流儀みたいですね~。
ということで私もプロトコル定義します。プロトコルについては「iPhoneアプリ開発、その(89)」で触れてますよん。
@protocol testViewDelegate<NSObject>
@optional
- (void)view:(testView *)view gotSingleTapAtPoint:(CGPoint)tapPoint;
- (void)view:(testView *)view gotDoubleTapAtPoint:(CGPoint)tapPoint;
@end
<NSObject>を付ける理由はhandleSingleTapやhandleDoubleTapメソッド内部でrespondsToSelectorてのを使うからです。別に付けてなくてもコンパイル時に「そのメソッドは持ってないんじゃないっすか?」て注意されるだけだけどね(すげーわObjective C、どこまでもゆるい)。
respondsToSelectorは指定したインスタンスが、指定したメソッドを実装してるか調べるメソッド。例えば
[delegate respondsToSelector:@selector(view:gotSingleTapAtPoint:)]
この場合view:gotSingleTapAtPoint:がdelegateインスタンスに実装されてるかですな。@selector?な人は「iPhoneアプリ開発、その(33)」を参照。
で、このメソッド返り値がYESなら実装されているので、安心してそのメソッドを呼び出すとしてます。
delegate変数は
@interface testView : UIView {
id delegate;
}
@property (nonatomic, assign) id delegate;
@end
でtestView.hに定義。nonatomicは特にいらないようには思うけどね。@property?な人は「iPhoneアプリ開発、号外 propertyをちょっと調べてみた」を参照。
testView.m側で
@synthesize delegate;
とすることで、scViewController.m側で
testview.delegate = self;
が使えるようになります。testview.delegateはドット構文であって、C/C++の意味合いと違う事に注意。ドット構文についてはObjective-C 2.0プログラミング言語 > オブジェクトとクラス >オブジェクトメッセージングのドット構文の章を参照。
で、本題の
performSelector:withObject:afterDelay
こいつは、afterDelayで指定した時間経ってから指定したメソッドを実行する機能だそうです。NSObject Class Referenceに書かれています。Referenceの引き方は「iPhoneアプリ開発、その(33)」を参照。
この指定メソッドを指定時間に実行する装置を、仮にアクションキューと呼ぶと、このキューに登録する作業が上記メソッドで
cancelPreviousPerformRequestsWithTarget
が、この登録したメソッドをやっぱやめるわといってとりはぶくメソッドになるわけですな。
こいつでタップする間隔によって登録したhandleSingleTapが実行されたりされなかったりを、下の図のように制御するわけです。
タップ間隔が十分に開いている場合

タップ間隔がperformSelectorで指定したafterDelay時間よりも短い場合

2回目のtouchesBeganがDOUBLE_TAP_DELAYまでに発生しなければ、そのまま登録したメソッド(handleSingleTap)が実行され、そうでなければアクションキューから取り省かれhandleSingleTapは実行されず、そのあとの
} else if([touch tapCount] == 2) {
[self handleDoubleTap];
}
が実行されるわけですわ。
使ってわかると思うけど、1本指か2本指かは判別してません。これは以下のメソッドを呼び出してないので複数の指情報が来ないからです。
[self setMultipleTouchEnabled:YES];
複数の指情報を取っちゃうと、Appleのサンプルソースのようにかなりの処理を追加しないといけなくなるんですな。
ちなみに、フリックだとスクロール、タップだとズームと見事に切り替わるんですが、今回のtouchesBeganメソッド追加でフリックの時にズームされないのはなんでって思うんじゃないかと思います。
私は思いました。
なので、そこらを次回掘り下げます。
というかサンプルプロジェクトはすでに加工済み。予習したい人はコンソール見れ~とだけ言っておこう。
ではでは。
------------
サンプルプロジェクト:sc3.zip
「ダイナミックObjective-C」Web版、読みました。
前半のハッキング記事、後半のCocoaフレームワークのデザインパターンへの分類と読み応えは抜群だけど、当然ハードルは高め。秋の夜長は
詳解 Objective-C 2.0 荻原 剛志
の丁寧な説明を読んでObjective-Cの構文や仕様を勉強(これの初代坂といえるObjective‐C―MacOS Xプログラミング入門を読んでて2.0の方は未読だけど、おそらく一番のおすすめ)して
Dynamic Objective-C 木下 誠
で、Objective-Cの実装方法への理解を深める。
そんな感じなりよ。
ゲット!














1 ■早速の対応感謝です
りぼんさん、早速の対応感謝です。
おそらく、2.0でダブルタップで拡大/マルチタップで縮小の機能を実装するには、UIScrollViewを作り直す処理を入れるしかないのかな?って思っています。
あと、サンプルソースですが、
+ (IMP)instanceMethodForSelector:(SEL)aSelector
- (BOOL)respondsToSelector:(SEL)aSelector
これらを使って、メソッドが使えるか判定する方法も使えるんじゃないかと…。
参考URL:http://blog.popino.net/article/30922925.html