前回の目玉商品
もう、お試しになりましたか?
デフォルトのYESだと、ずっとUIScrollView側も指の動きを監視し続けるみたいですね。
なので、こいつをNOで呼んでいないと、UIScrollViewに埋め込んだthumbViewをドラッグしようとしても、ちょっと指を動かしただけでUIScrollView側のフリックジェスチャとかドラッグとして取られてしまい、thumbViewには「オメーの出る幕じゃねえずら」とばかりにtouchesCancelledが飛んでくる・と。虎眼流の「流れ」なみに飛んで、ひつこいですか?そうですか...
埋め込みUIView側をドラッグ可能にする予定ならsetCanCancelContentTouches:NOを呼んでおけ、ちゅーことですな。
あと、NOを指定したからといって、まったくUIScrollView側のフリックやドラッグが無効になるというわけじゃなく、ちゃんと機能してくれるんですよね。
これは「iPhoneアプリ開発、その(121)」で調べたdelaysContentTouchesが効いてるせいだと思われるっす。
こいつが機能してるとthumbViewにtouchesBeganが届くのはUIScrollView側のフリックやドラッグ判定が終わってからってことになる。
試しに
とすると、UIScrollView側のフリックやドラッグがまったくできなく(もちろんthumbView以外の部分をタッチしたなら、ちゃんと機能するけど)なりました。
ちなみに以前から適当にドット表記を混ぜてますが
としても
としても問題ナッシングです。ここらへんは「Objective-C 2.0 プログラミング言語(日本語訳)」読みましょう。
delaysContentTouches=YESでディレイがあるとthumbViewを押してちょっと待ってから動かさないとthumbViewをドラッグできないんだけど、使ってみると、そんなに不便は感じない。メリットの方が大きいように思えるから、デフォルトYESなんでしょう。私もYESで行きます。
は、YESにすると下の図のようにUIScrollView外への書き出しを許さなくできるみたい。
画像ドラッグ時の見た目だけの問題だけど、だいぶイメージが変わる
よくわからないのが、Apple'sサンプルソースThumbImageViewクラスのinitWithImageメソッドにある
って呼び出し。試しにtouchesMovedメソッドに
とやってみた(selfの値はインスタンス毎に変わるので)んだけど、特に別のビューにtouchesMovedが渡る事はなかった。
が必要なのかと加えてみても効果無し。今のところ、なぜ必要なのかわかりません。
オートスクロールや配置アニメーションのさいに意味があるのかもしれませんな。
ということで、いよいよデリゲートの追加っす。
@optionalってのは、実装が無くても許すって事か?ドキュメント読んでもいまいち不明。
今回、thumbViewWasTappedは実装しません。
で、このデリゲートをthumbView側で利用、listViewControllerクラス側に実装するわけですが、ここらへんの過程は「iPhoneアプリ開発、その(120)」を参照してください。
今回はlistViewControllerクラス側のデリゲート実装の解説。
thumbImageViewStartedTracking
ドラッグを開始するthumbViewをUIScrollViewに埋め込まれたthumbView群の一番手前に持ってくる。
ま、配置された順でも、それはそれで正しい。ここらへんはアプリケーションの用途によって変わってきますね。
thumbImageViewMoved
Apple'sサンプルではオートスクロールと、thumbView再配置アニメーションの二つを担当してます。
がオートスクロールで、その後の処理はthumbView再配置アニメーションです。
今回はオートスクロールだけ実装することにします。
maybeAutoscrollForThumb
ドラッグ中のthumbViewからドラッグ開始点を取り出し、以下のメソッドでthumbView上の座標からthumbScrollView上の座標に変換する。
これが、以下の図のように[thumbScrollView bounds]のAUTOSCROLL_THRESHOLD領域であれば、右、左いずれかの方向にスクロールさせる。
ただし、その場でスクロールするのではなくNSTimer使い、1/60秒間隔でオートスクロールさせる。
1/60秒間隔てselfで指定されたインスタンスのautoscrollTimerFired:メソッドを繰り返し呼び出す。
userInfo:にドラッグ中のthumbViewであるthumbを指定しているのは、autoscrollTimerFiredメソッドでドラッグ中のthumbViewを特定する必要があるため。
thumbViewのmoveByOffset:メソッドをmaybeAutoscrollForThumbメソッド内で呼び出すと無限ループになっちゃうってのもあるけど、それは実装の仕方でどうとでもなる。たぶん一番の理由は一定間隔でスクロールさせたいからでしょう。CAAnimation使う場合CALayerからいろいろ準備が必要なんで、てっとりばやくNSTimerを使うのか?
あと、オートスクロール中にユーザーのドラッグでthumbViewがthumbScrollViewのbounds内に入った場合、ただちにオートスクロールをやめるようになってます。それが
です。
autoscrollTimerFired
タイマーで1/60秒ごとに実行され、thumbScrollViewをオートスクロールするメソッド。
setContentOffsetメソッドを使ってスクロールさせている。
あと、このままだとドラッグ中のthumbViewも一緒にスクロールしちゃうので
として、ドラッグ中のthumbViewはその場に残るようにしてます。
内部で呼びだしてるautoscrollDistanceForProximityToEdgeで使われている
は引き数を下回らない最小の整数値を返す関数だそうです。
例) ceilf(0.5) = 1.0、 ceil(-0.5) = 0.0
1.2ピクセルとか、2.35ピクセルとかではなく1ピクセル、3ピクセルというような移動にしたいみたい。
これで、オートスクロールまで実装完了。
次回の再配置アニメーション実装でサンプルプロジェクトAutoscroll.xcodeprojの解析は、ほぼ完了ですな。
------------
サンプルプロジェクト:sc6.zip
[thumbScrollView setCanCancelContentTouches:NO];
もう、お試しになりましたか?
デフォルトのYESだと、ずっとUIScrollView側も指の動きを監視し続けるみたいですね。
なので、こいつをNOで呼んでいないと、UIScrollViewに埋め込んだthumbViewをドラッグしようとしても、ちょっと指を動かしただけでUIScrollView側のフリックジェスチャとかドラッグとして取られてしまい、thumbViewには「オメーの出る幕じゃねえずら」とばかりにtouchesCancelledが飛んでくる・と。虎眼流の「流れ」なみに飛んで、ひつこいですか?そうですか...
埋め込みUIView側をドラッグ可能にする予定ならsetCanCancelContentTouches:NOを呼んでおけ、ちゅーことですな。
あと、NOを指定したからといって、まったくUIScrollView側のフリックやドラッグが無効になるというわけじゃなく、ちゃんと機能してくれるんですよね。
これは「iPhoneアプリ開発、その(121)」で調べたdelaysContentTouchesが効いてるせいだと思われるっす。
こいつが機能してるとthumbViewにtouchesBeganが届くのはUIScrollView側のフリックやドラッグ判定が終わってからってことになる。
試しに
[thumbScrollView setCanCancelContentTouches:NO];
thumbScrollView.delaysContentTouches = NO;
とすると、UIScrollView側のフリックやドラッグがまったくできなく(もちろんthumbView以外の部分をタッチしたなら、ちゃんと機能するけど)なりました。
ちなみに以前から適当にドット表記を混ぜてますが
thumbScrollView.canCancelContentTouches = NO;
thumbScrollView.delaysContentTouches = NO;
としても
[thumbScrollView setCanCancelContentTouches:NO];
[thumbScrollView setDelaysContentTouches:NO];
としても問題ナッシングです。ここらへんは「Objective-C 2.0 プログラミング言語(日本語訳)」読みましょう。
delaysContentTouches=YESでディレイがあるとthumbViewを押してちょっと待ってから動かさないとthumbViewをドラッグできないんだけど、使ってみると、そんなに不便は感じない。メリットの方が大きいように思えるから、デフォルトYESなんでしょう。私もYESで行きます。
[thumbScrollView setClipsToBounds:NO];
は、YESにすると下の図のようにUIScrollView外への書き出しを許さなくできるみたい。
画像ドラッグ時の見た目だけの問題だけど、だいぶイメージが変わる
よくわからないのが、Apple'sサンプルソースThumbImageViewクラスのinitWithImageメソッドにある
[self setExclusiveTouch:YES];
って呼び出し。試しにtouchesMovedメソッドに
printf("touchesMoved %x\n", self);
とやってみた(selfの値はインスタンス毎に変わるので)んだけど、特に別のビューにtouchesMovedが渡る事はなかった。
[self.nextResponder touchesMoved:touches withEvent:event];
が必要なのかと加えてみても効果無し。今のところ、なぜ必要なのかわかりません。
オートスクロールや配置アニメーションのさいに意味があるのかもしれませんな。
ということで、いよいよデリゲートの追加っす。
@protocol thumbViewDelegate
@optional
- (void)thumbViewStartedTracking:(thumbView *)tiv;
- (void)thumbViewMoved:(thumbView *)tiv;
- (void)thumbViewStoppedTracking:(thumbView *)tiv;
@end
@optionalってのは、実装が無くても許すって事か?ドキュメント読んでもいまいち不明。
今回、thumbViewWasTappedは実装しません。
で、このデリゲートをthumbView側で利用、listViewControllerクラス側に実装するわけですが、ここらへんの過程は「iPhoneアプリ開発、その(120)」を参照してください。
今回はlistViewControllerクラス側のデリゲート実装の解説。
thumbImageViewStartedTracking
ドラッグを開始するthumbViewをUIScrollViewに埋め込まれたthumbView群の一番手前に持ってくる。
ま、配置された順でも、それはそれで正しい。ここらへんはアプリケーションの用途によって変わってきますね。
thumbImageViewMoved
Apple'sサンプルではオートスクロールと、thumbView再配置アニメーションの二つを担当してます。
[self maybeAutoscrollForThumb:draggingThumb];
がオートスクロールで、その後の処理はthumbView再配置アニメーションです。
今回はオートスクロールだけ実装することにします。
maybeAutoscrollForThumb
ドラッグ中のthumbViewからドラッグ開始点を取り出し、以下のメソッドでthumbView上の座標からthumbScrollView上の座標に変換する。
[thumb convertPoint:[thumb touchLocation] toView:thumbScrollView]
これが、以下の図のように[thumbScrollView bounds]のAUTOSCROLL_THRESHOLD領域であれば、右、左いずれかの方向にスクロールさせる。
ただし、その場でスクロールするのではなくNSTimer使い、1/60秒間隔でオートスクロールさせる。
[NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
target:self
selector:@selector(autoscrollTimerFired:)
userInfo:thumb
repeats:YES];
1/60秒間隔てselfで指定されたインスタンスのautoscrollTimerFired:メソッドを繰り返し呼び出す。
userInfo:にドラッグ中のthumbViewであるthumbを指定しているのは、autoscrollTimerFiredメソッドでドラッグ中のthumbViewを特定する必要があるため。
thumbViewのmoveByOffset:メソッドをmaybeAutoscrollForThumbメソッド内で呼び出すと無限ループになっちゃうってのもあるけど、それは実装の仕方でどうとでもなる。たぶん一番の理由は一定間隔でスクロールさせたいからでしょう。CAAnimation使う場合CALayerからいろいろ準備が必要なんで、てっとりばやくNSTimerを使うのか?
あと、オートスクロール中にユーザーのドラッグでthumbViewがthumbScrollViewのbounds内に入った場合、ただちにオートスクロールをやめるようになってます。それが
[autoscrollTimer invalidate];
autoscrollTimer = nil;
です。
autoscrollTimerFired
タイマーで1/60秒ごとに実行され、thumbScrollViewをオートスクロールするメソッド。
setContentOffsetメソッドを使ってスクロールさせている。
あと、このままだとドラッグ中のthumbViewも一緒にスクロールしちゃうので
thumbView *thumb = (thumbView *)[timer userInfo];
[thumb moveByOffset:CGPointMake(autoscrollDistance, 0)];
として、ドラッグ中のthumbViewはその場に残るようにしてます。
内部で呼びだしてるautoscrollDistanceForProximityToEdgeで使われている
float ceilf(float x);
は引き数を下回らない最小の整数値を返す関数だそうです。
例) ceilf(0.5) = 1.0、 ceil(-0.5) = 0.0
1.2ピクセルとか、2.35ピクセルとかではなく1ピクセル、3ピクセルというような移動にしたいみたい。
これで、オートスクロールまで実装完了。
次回の再配置アニメーション実装でサンプルプロジェクトAutoscroll.xcodeprojの解析は、ほぼ完了ですな。
------------
サンプルプロジェクト:sc6.zip