2 ページの UIScrollView があり、それらの間を水平方向にスクロールできます。ただし、私のページの 1 つで UIDatePicker があり、スクロール ビューが垂直方向のタッチ イベントをインターセプトしているため、日付ピッカーを操作できなくなりました (クリックまたはタップを除く)。ScrollView に垂直タッチ イベントを日付ピッカーに送信するように指示する方法はありますが、水平タッチ イベントをスクロール ビューに送信してページを切り替えますか?
3 に答える
実際には、Bob が提案したものよりもはるかに単純な実装があります。これは私にとって完璧に機能します。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 になるためです。
乾杯
この問題には2つの部分があると思います。1つ目はユーザーの意図を判断することであり、2つ目はその意図に応答するための正しいコントロールを取得することです。
意図の決定
ユーザーが何を意図しているのかを明確にすることが重要だと思います。このシナリオを想像してみてください。ユーザーが画面に触れ始め、指を左に移動しますが、少し上に移動します。ユーザーはおそらくビューをスクロールするつもりであり、日付を変更するつもりはまったくありませんでした。ビューをスクロールして日付を変更することは、特に画面外に移動するときに悪いことです。したがって、ユーザーが何を意図しているかを判断するには、次のアルゴリズムを提案します。
ユーザーが画面に触れ始めたら、開始位置を記録します。ユーザーの指がその位置から離れ始めると、コントロールはまったく反応しないはずです。タッチが開始位置から特定のしきい値距離を超えて移動したら、それがより水平に移動したか、垂直に移動したかを判断します。垂直方向に移動した場合、ユーザーは日付を変更する予定なので、移動の水平方向の部分は無視して、日付のみを変更してください。より水平方向に移動した場合、ユーザーはビューをスクロールすることを意図しているため、移動の垂直部分を無視して、ビューのみをスクロールします。
実装
これを実装するには、UIScrollViewまたは日付ピッカーが実行する前にイベントを処理する必要があります。これを行うにはおそらくいくつかの方法がありますが、特に頭に浮かぶのは、ScrollingDateMediatorViewというカスタムUIViewを作成することです。UIScrollViewをこのビューの子として設定します。ScrollingDateMediatorViewのhitTest:withEvent:メソッドとpointInside:withEvent:メソッドをオーバーライドします。これらのメソッドは、通常行われるのと同じ種類のヒットテストを実行する必要がありますが、結果が日付ピッカーである場合は、代わりに自分自身を返します。これにより、日付ピッカー宛てのタッチイベントが効果的にハイジャックされ、ScrollingDateMediatorViewが最初にそれらを処理できるようになります。次に、さまざまなtouches*メソッドで上記のアルゴリズムを実装します。具体的には:
touchesBegan:withEventメソッドで、開始位置を保存します。
touchesMoved:withEventで、ユーザーの意図がまだわからない場合は、タッチが開始位置から十分に離れているかどうかを判断します。含まれている場合は、ユーザーが日付をスクロールするか変更するかを決定し、その意図を保存します。ユーザーの意図がすでにわかっていて、日付を変更する場合は、日付ピッカーにtouchedMoved:withEventメッセージを送信します。それ以外の場合は、UIScrollViewにtouchesMoved:withEventメッセージを送信します。他のビューが適切なメッセージを受け取るようにするには、touchesEnded:withEventとtouchesCancelled:withEvent内で同様の作業を行う必要があります。これらの方法は両方とも、保存された値をリセットする必要があります。
イベントを適切に伝播させたら、移動のしきい値を調整するために、ユーザーテストを試す必要があります。
すごい助けてサム!これを使用して、メソッドを入れ替える単純なカテゴリを作成しました (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];