在UITableView中观察捏合多点触控手势

19

我希望在UITableView上实现捏合手势,已经查看了几种方法,包括这个:

相似问题

但是虽然我可以创建一个UIViewTouch对象并将其叠加到我的UITableView上,但滚动事件不会被传递到我的UITableView,我仍然可以选择单元格,并且它们通过触发转换到新的视图控制器对象而正确响应。但是我无法滚动UITableView,尽管传递了touchesBegan、touchesMoved和touchesEnded事件。

2个回答

42
这似乎是一个经典问题。在我的情况下,我想要拦截一些事件超过UIWebView,但无法进行子类化等等。
我发现最好的方法是使用UIWindow来拦截事件:

EventInterceptWindow.h

@protocol EventInterceptWindowDelegate
- (BOOL)interceptEvent:(UIEvent *)event; // return YES if event handled
@end


@interface EventInterceptWindow : UIWindow {
    // It would appear that using the variable name 'delegate' in any UI Kit
    // subclass is a really bad idea because it can occlude the same name in a
    // superclass and silently break things like autorotation.
    id <EventInterceptWindowDelegate> eventInterceptDelegate;
}

@property(nonatomic, assign)
    id <EventInterceptWindowDelegate> eventInterceptDelegate;

@end

EventInterceptWindow.m:

#import "EventInterceptWindow.h"

@implementation EventInterceptWindow

@synthesize eventInterceptDelegate;

- (void)sendEvent:(UIEvent *)event {
    if ([eventInterceptDelegate interceptEvent:event] == NO)
        [super sendEvent:event];
}

@end

创建该类,将MainWindow.xib中的UIWindow类更改为EventInterceptWindow,然后在某个地方将eventInterceptDelegate设置为您想要拦截事件的视图控制器。例如,拦截双击的示例:

- (BOOL)interceptEvent:(UIEvent *)event {
    NSSet *touches = [event allTouches];
    UITouch *oneTouch = [touches anyObject];
    UIView *touchView = [oneTouch view];
    //  NSLog(@"tap count = %d", [oneTouch tapCount]);
    // check for taps on the web view which really end up being dispatched to
    // a scroll view
    if (touchView && [touchView isDescendantOfView:webView]
            && touches && oneTouch.phase == UITouchPhaseBegan) {
        if ([oneTouch tapCount] == 2) {
            [self toggleScreenDecorations];
            return YES;
        }
    }   
    return NO;
}

相关信息在此处: http://iphoneincubator.com/blog/windows-views/360idev-iphone-developers-conference-presentation


那个方法非常好用,而且比覆盖对象的方法更加简洁。谢谢! - user243562
真是一个非常棒的解决方案,我一直在寻找这样的东西来为scrollview/tableview添加手势,谢谢! :) - Jaanus
1
很棒的解决方案。我已经成功运行了几个月,捕获了一个三指双击手势。最近,我的一位更神经质的同事能够引起崩溃,似乎是由此引起的。堆栈跟踪没有显示任何Objective-C代码,但它在[UIScrollViewPanGestureRecognizer touchesCancelled:withEvent:] -[UIApplication _cancelTouches:withEvent:sendingTouchesCancelled:includingGestures:]上抛出了EXEC_BAD_ACCESS。我的假设是第一个“3次点击”开始了一个PanGesture,但是我只是杀死了事件(不够优雅)。始终返回“NO”可以解决这个问题。 - Michael Kernahan
上面的空间不足了...有没有关于优雅取消的想法,将不胜感激。 - Michael Kernahan
1
请参考 https://dev59.com/oGkv5IYBdhLWcg3wtTC7,了解在使用故事板时如何使用自定义UIWindow。 - schieferstapel

5

Nimrod写道:

在某个地方设置eventInterceptDelegate为您想要拦截事件的视图控制器

我一开始没有理解这个声明。 为了让其他和我有同样问题的人受益,我是通过将以下代码添加到必须检测触摸的UIView子类中来实现的。

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Register to receive touch events
    MyApplicationAppDelegate *appDelegate = (MyApplicationAppDelegate *) [[UIApplication sharedApplication] delegate];
    EventInterceptWindow *window = (EventInterceptWindow *) appDelegate.window;
    window.eventInterceptDelegate = self;
}


- (void) viewWillDisappear:(BOOL) animated
{
    // Deregister from receiving touch events
    MyApplicationAppDelegate *appDelegate = (MyApplicationAppDelegate *) [[UIApplication sharedApplication] delegate];
    EventInterceptWindow *window = (EventInterceptWindow *) appDelegate.window;
    window.eventInterceptDelegate = nil;

    [super viewWillDisappear:animated];
}


- (BOOL) interceptEvent:(UIEvent *) event
{
    NSLog(@"interceptEvent is being called...");
    return NO;
}

这个interceptEvent:的版本是一个简单的捏合缩放检测实现。注意:部分代码来源于Apress的《Beginning iPhone 3 Development》书籍。
CGFloat initialDistance;

- (BOOL) interceptEvent:(UIEvent *) event
{
    NSSet *touches = [event allTouches];

    // Give up if user wasn't using two fingers
    if([touches count] != 2) return NO;

    UITouchPhase phase = ((UITouch *) [touches anyObject]).phase;
    CGPoint firstPoint = [[[touches allObjects] objectAtIndex:0] locationInView:self.view];
    CGPoint secondPoint = [[[touches allObjects] objectAtIndex:1] locationInView:self.view];

    CGFloat deltaX = secondPoint.x - firstPoint.x;
    CGFloat deltaY = secondPoint.y - firstPoint.y;
    CGFloat distance = sqrt(deltaX*deltaX + deltaY*deltaY);

    if(phase == UITouchPhaseBegan)
    {
        initialDistance = distance;
    }
    else if(phase == UITouchPhaseMoved)
    {
        CGFloat currentDistance = distance;
        if(initialDistance == 0) initialDistance = currentDistance;
        else if(currentDistance - initialDistance > kMinimumPinchDelta) NSLog(@"Zoom in");
        else if(initialDistance - currentDistance > kMinimumPinchDelta) NSLog(@"Zoom out");
    }
    else if(phase == UITouchPhaseEnded)
    {
        initialDistance = 0;
    }

    return YES;
}

编辑:虽然这段代码在iPhone模拟器上运行完全正常,但当我在iPhone设备上运行时,遇到了与表格滚动相关的奇怪错误。如果您也遇到了这种情况,请强制interceptEvent:方法在所有情况下返回NO。这意味着超类也将处理触摸事件,但幸运的是,这并没有破坏我的代码。

为什么在viewDidAppear中不使用self.view.window - schieferstapel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接