我有一个包含 2 个页面的 UIScrollView,我可以在它们之间水平滚动。但是,在我的其中一个页面上,我有一个 UIDatePicker,并且滚动视图正在拦截垂直触摸事件,因此我无法再操作日期选择器(除非通过单击或点击)。有没有办法告诉 ScrollView 将垂直触摸事件发送到日期选择器,但将水平触摸事件发送到滚动视图以切换页面?

有帮助吗?

解决方案

我认为这个问题有两个部分。第一个是确定用户的意图,第二个是获得正确的控件来响应该意图。

确定意图

我认为明确用户的意图很重要。想象一下这个场景:用户开始触摸屏幕,并将手指向左移动很远,但也向上移动一点。用户可能打算滚动视图,而根本不打算更改日期。滚动视图并更改日期是很糟糕的,尤其是当它移出屏幕时。因此,为了确定用户的意图,我建议使用以下算法:

当用户开始触摸屏幕时,记录起始位置。当用户的手指开始离开该位置时,控件不应该有任何反应。一旦触摸从起始位置移过一定的阈值距离,就确定它是水平移动还是垂直移动。如果它垂直移动,则用户打算更改日期,因此忽略移动的水平部分,仅更改日期。如果它水平移动得更多,则用户打算滚动视图,因此忽略移动的垂直部分,仅滚动视图。

执行

为了实现这一点,您需要在 UIScrollView 或日期选择器之前处理事件。可能有几种方法可以做到这一点,但我特别想到一种:创建一个名为 ScrollingDateMediatorView 的自定义 UIView。将 UIScrollView 设置为该视图的子视图。覆盖 ScrollingDateMediatorView 的 hitTest:withEvent:和 pointInside:withEvent:方法。这些方法需要执行通常会发生的相同类型的命中测试,但如果结果是日期选择器,则返回 self 。这有效地劫持了发送给日期选择器的任何触摸事件,允许 ScrollingDateMediatorView 首先处理它们。然后,您可以在各种 Touchs* 方法中实现上述算法。具体来说:

在touchesBegan:withEvent方法中,保存起始位置。

在touchesMoved:withEvent中,如果尚不知道用户的意图,则确定触摸是否已距离起始位置足够远。如果有,确定用户是否打算滚动或更改日期,并保存该意图。如果用户的意图已知并且要更改日期,请向日期选择器发送 TouchMoved:withEvent 消息,否则向 UIScrollView 发送 TouchMoved:withEvent 消息。您必须在touchesEnded: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的原因是,它获取的触摸的视图实际上是一个UIPickerTable,这是一个私有API。

干杯

真棒帮助山姆!我使用了创建一个简单的类别,碎冰鸡尾酒的方法(因为我是在一个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];
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top