4

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

4

3 回答 3

6

实际上,有一个比 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。

干杯

于 2010-04-21T10:50:47.397 回答
5

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

确定意图

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

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

执行

为了实现这一点,您需要在 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 中做一些类似的工作,以确保其他视图获得适当的消息。这两种方法都应该重置保存的值。

一旦你让它正确传播事件,你可能不得不尝试一些用户测试来调整移动阈值。

于 2009-07-02T07:13:03.740 回答
1

真棒帮助山姆!我用它来创建一个简单的类别来调整方法(因为我在 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];
于 2011-08-20T20:02:06.577 回答