ページとUIScrollViewの内部UIDatePicker
-
06-09-2019 - |
質問
私は2ページとUIScrollViewのを持っている、と私はそれらの間で水平方向にスクロールすることができます。しかし、私のページのいずれかに、私はUIDatePickerを持っている、と私はもはや(クリックまたはタップすることを除いて)、日付ピッカーを操作することはできませんので、スクロールビューは垂直タッチイベントをインターセプトされます。日付ピッカーに垂直タッチイベントを送信するためにScrollViewを伝えますが、ページを切り替えるには、スクロールビューに水平タッチイベントを送信するためにいくつかの方法がありますか?
解決
私はこの問題には2つの部分があると思います。最初は、ユーザーの意図を決定され、第二は、その意思に対応するための正しい制御を取得されます。
決定テント
私はそれは、ユーザが意図するかについて明確にすることが重要だと思います。このシナリオを想像:ユーザーが画面をタッチ起動し、左に遠くに指を動かすだけでなく、少しアップ。ユーザーは、おそらくビューをスクロールするためのもの、とのすべての日付を変更するつもりはなかったです。それはオフスクリーンに移動し、特に同じように、ビューをスクロールして日付を変更両方に悪いだろう。したがって、ユーザーは、私は次のアルゴリズムを提案するつもりかを決定します。
ユーザが画面に触れて起動すると、、開始位置が記録されます。ユーザの指がその位置から離れて移動し始めると、コントロールがまったく反応してはなりません。開始位置からある閾値距離過去タッチ移動すると、それはより多くの水平方向または垂直方向に移動させるか否かを決定します。 それは垂直に移動した場合、ユーザーは、日付を変更するので、動きの水平部分を無視して、日付のみを変更しようとします。 それはより水平に移動した場合、ユーザは、ビューをスクロールするので、運動の垂直部分を無視してのみビューをスクロールすることを意図している。
実装
UIScrollViewの日付ピッカーを行う前に、これを実現するためには、イベントを処理する必要があります。そこにこれを行うにはいくつかの方法が、おそらくですが、特に1が頭に浮かぶ:ScrollingDateMediatorViewというカスタムUIViewのを確認します。このビューの子としてUIScrollViewのを設定します。 withEvent::とpointInside:withEvent:メソッドScrollingDateMediatorViewのhitTestをオーバーライドします。これらのメソッドは、通常発生するヒットテストの同じ種類を実行する必要がありますが、結果は日付ピッカーであれば、代わりに自己を返します。これは、効果的にScrollingDateMediatorViewが最初にそれらを処理することができ、日付ピッカーに宛てたすべてのタッチイベントをハイジャックします。その後、様々なタッチで、上記のアルゴリズムは、メソッドを実装します*。具体的に:
touchesBeganあり:withEvent方法、開始位置を保存
touchesMovedでは:ユーザの意図がまだ知られていない場合withEventは、十分に離れた開始位置から移動した触れたかどうかを判断します。それが持っている場合は、ユーザーがスクロールしたり、日付を変更し、その意図を保存するつもりかどうかを判断します。 ユーザの意図が既に知られており、それが日付を変更するなら、日付ピッカーtouchedMoved送る:withEventメッセージを、そうでない場合はUIScrollViewのにtouchesMovedを送る:withEventメッセージを。 あなたはtouchesEnded以内に、いくつかのsimliarの作業を行う必要があるでしょう:withEventとtouchesCancelled:withEventは、他のビューは、適切なメッセージを取得することを確認します。これらのメソッドの両方が保存された値をリセットする必要があります。
あなたはそれが適切なイベントを伝播したら、、あなたはおそらくチューニングする動きしきい値をいくつかのユーザーテストをしようとする必要があります。
他のヒント
実際には、ボブが提案されたものよりはるかに単純な実装があります。これは私のために完璧に動作します。あなたがまだの場合は、あなたのUIScrollViewのをサブクラス化する必要があり、このメソッドが含まれます: -
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if ([result.superview isKindOfClass:[UIPickerView class]])
{
self.canCancelContentTouches = NO;
self.delaysContentTouches = NO;
}
else
{
self.canCancelContentTouches = YES; // (or restore bool from prev value if needed)
self.delaysContentTouches = YES; // (same as above)
}
return result;
}
私はresult.superview
を使用する理由は、タッチを取得するビューが実際にプライベートAPIであるUIPickerTable、になるということです。
乾杯
恐ろしい助けサム!私は(私はのUITableViewControllerでこれをやっていたので、したがって、スクロールビューのサブクラスを作成するためにいくつかの本当に厄介なものをしなければならなかっただろう)メソッドをスウィズル簡単なカテゴリを作成することを使用します。
#import <UIKit/UIKit.h>
@interface UIScrollView (withControls)
+ (void) swizzle;
@end
そしてメインのコード:
#import </usr/include/objc/objc-class.h>
#import "UIScrollView+withControls.h"
#define kUIViewBackgroundImageTag 6183746
static BOOL swizzled = NO;
@implementation UIScrollView (withControls)
+ (void)swizzleSelector:(SEL)orig ofClass:(Class)c withSelector:(SEL)new;
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if (class_addMethod(c, orig, method_getImplementation(newMethod),
method_getTypeEncoding(newMethod))) {
class_replaceMethod(c, new, method_getImplementation(origMethod),
method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, newMethod);
}
}
+ (void) swizzle {
@synchronized(self) {
if (!swizzled) {
[UIScrollView swizzleSelector:@selector(hitTest:withEvent:)
ofClass:[UIScrollView class]
withSelector:@selector(swizzledHitTest:withEvent:)];
swizzled = YES;
}
}
}
- (UIView*)swizzledHitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView* result = [self swizzledHitTest:point withEvent:event]; // actually calling the original hitTest method
if ([result.superview isKindOfClass:[UIPickerView class]]) {
self.canCancelContentTouches = NO;
self.delaysContentTouches = NO;
} else {
self.canCancelContentTouches = YES; // (or restore bool from prev value if needed)
self.delaysContentTouches = YES; // (same as above)
}
return result;
}
@end
すると、私のviewDidLoadメソッドでは、私はちょうどと呼ばれる
[UIScrollView swizzle];