我有一个 2 页的 UIScrollView,我可以在它们之间水平滚动。但是,在我的一个页面上,我有一个 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
是获得触摸的视图实际上是一个 UIPickerTable,它是一个私有 API。
干杯
我认为这个问题有两个部分。第一个是确定用户的意图,第二个是获得正确的控制以响应该意图。
确定意图
我认为重要的是要清楚用户的意图。想象一下这样的场景:用户开始触摸屏幕并将手指向左移动很远,但也向上移动了一点。用户可能打算滚动视图,根本不打算更改日期。滚动视图和更改日期都是不好的,尤其是当它移出屏幕时。因此,为了确定用户的意图,我建议使用以下算法:
当用户开始触摸屏幕时,记录起始位置。当用户的手指开始从该位置移开时,控件根本不应该做出反应。一旦触摸从起始位置移动超过某个阈值距离,确定它是水平移动还是垂直移动。如果它垂直移动,则用户打算更改日期,因此忽略移动的水平部分,只更改日期。如果它更水平地移动,则用户打算滚动视图,因此忽略移动的垂直部分并仅滚动视图。
执行
为了实现这一点,您需要在 UIScrollView 或日期选择器之前处理事件。可能有几种方法可以做到这一点,但特别想到一种方法:制作一个名为 ScrollingDateMediatorView 的自定义 UIView。将 UIScrollView 设置为此视图的子视图。覆盖 ScrollingDateMediatorView 的 hitTest:withEvent: 和 pointInside:withEvent: 方法。这些方法需要执行通常会发生的相同类型的命中测试,但如果结果是日期选择器,则返回 self。这有效地劫持了指定给日期选择器的任何触摸事件,允许 ScrollingDateMediatorView 首先处理它们。然后在各种 touches* 方法中实现上述算法。具体来说:
在 touchesBegan:withEvent 方法中,保存起始位置。
在 touchesMoved:withEvent 中,如果尚不知道用户的意图,则确定触摸是否已从起始位置移动得足够远。如果有,确定用户是否打算滚动或更改日期,并保存该意图。如果用户的意图是已知的并且是更改日期,则向日期选择器发送touchesMoved: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];