将UIGesture转发到后面的视图

58

我正在开发一个iPhone(iOS 4.0或更高版本)应用程序,在多个视图之间处理触摸操作时遇到了一些问题。我的视图结构如下:

---> A superView 
     |
     ---> SubView - A 
     |
     ---> SubView - B (exactly on top of A, completely blocking A).

enter image description here

基本上我有一个超级视图,以及同级的子视图A和B。B具有与A相同的框架,因此完全隐藏了A。

现在我的要求是这样的。

  1. 子视图B应接收所有滑动和点击(单击和双击)手势。
  2. 子视图A应接收所有捏合手势。

这是我如何向视图添加手势识别器的方式。

UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
leftSwipe.delegate  =   self;
[bView addGestureRecognizer:leftSwipe];
[leftSwipe release];

UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
[rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
rightSwipe.delegate   =   self;
[bView addGestureRecognizer:rightSwipe];
[rightSwipe release];

UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
pinch.delegate    =   self;
[aView addGestureRecognizer:pinch];
[pinch release];

我做了一些研究,对我来说UIGestureRecognizerDelegate看起来很有前途,我实现了委托方法

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

我为SubView B返回了NO,希望底层视图能够接收到这些事件。但是没有这样的运气。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] && [gestureRecognizer view] == bView) {
        NSLog(@"pinchGesture");
        //bView.userInteractionEnabled = NO;
        return NO;
    }
    return YES;
}

然后,在委托回调中禁用了SubView B的用户交互(在上面的代码块中注释掉的代码),当识别到捏手势时,希望剩余部分的手势将被SubView A接收。但是也没有这样的运气...
所以这就是我现在的立场。我看过this问题,其中被接受的答案涉及属性。但我认为只会取消特定的触摸事件,而不会转发它。
有其他方法可以实现我的要求吗?我准备根据您提供的任何提示进行工作。
编辑:赏金时间
我的所谓的subView A实际上是第三方库的View类的一个实例,它接管了所有的触摸操作,我对其上的任何手势都没有控制权。我想要左右滑动的不同实现,并且希望捏、轻敲等手势与这个第三方视图的工作方式完全相同。因此,我在A的顶部放置了一个视图(SubView B)来获取左右滑动。但现在我想将其他手势事件转发到底层库。
5个回答

18
如果我正确理解了你的问题,你可以添加另一个清晰的视图(使用rect),与A和B视图相同,并在其上实现所有手势:当进行捏合手势时,控制子视图A;当进行滑动、单击和双击手势时,控制子视图B。你可以通过指针或将接收到的手势发送到控制子视图的类中的方法来实现不同的方式。
例如:
UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
leftSwipe.delegate  =   subViewAcontroller;
[clearView addGestureRecognizer:leftSwipe];
[leftSwipe release];

UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
[rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
rightSwipe.delegate   =   subViewAcontroller;
[clearView addGestureRecognizer:rightSwipe];
[rightSwipe release];

UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
pinch.delegate    =   subViewBcontroller;
[clearView addGestureRecognizer:pinch];
[pinch release];

或者:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
        NSLog(@"pinchGesture");
        [subViewBcontroller solvePinchGesture: gestureRecognizer];
    }
//etc
    return YES;
}

17

由于您的视图层次结构不正确,因此aView永远不会收到捏合手势。

---> A superView 
 |
 ---> SubView - A 
 |
 ---> SubView - B (exactly on top of A, completely blocking A).
bView将捕获所有多点触控事件,包括“捏”手势,但bView不会处理“捏”手势,所以它会将其传递给Responder链中的下一个响应者,即bView的superView或bView的viewController。因为兄弟视图是并列的(没有父子或视图-视图控制器关系),所以“捏”事件将永远不会传递给它的兄弟视图。
您可以按以下方式更改视图层次结构:
---> A superView 
 |
 ---> SubView - A 
       |
       ---> SubView - B (child view of b, exactly on top of A, completely blocking A)



- (void)viewDidLoad {
    [super viewDidLoad];

    aView = [[UIView alloc] initWithFrame:self.view.bounds];
    aView.backgroundColor = [UIColor blueColor];

    bView = [[UIView alloc] initWithFrame:self.view.bounds];
    bView.backgroundColor = [UIColor redColor];

    [self.view addSubview:aView];
    [aView addSubview:bView];

    UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
    [leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
    leftSwipe.delegate  =   self;
    [bView addGestureRecognizer:leftSwipe];

    UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
    [rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
    rightSwipe.delegate   =   self;
    [bView addGestureRecognizer:rightSwipe];

    UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
    pinch.delegate    =   self;
    [aView addGestureRecognizer:pinch];
}

请从以下链接获取更多信息:https://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/EventsiPhoneOS/EventsiPhoneOS.html#//apple_ref/doc/uid/TP40009541-CH2-SW1 -->事件在响应者链中传递的重要注意事项 “当系统传递触摸事件时,它首先将其发送到特定视图。对于触摸事件,该视图是hitTest:withEvent:返回的视图;对于“晃动”运动事件,遥控器事件,操作消息和编辑菜单消息,该视图是第一个响应者。如果最初的视图不能处理事件,则沿着特定路径沿响应者链向上移动:
  1. 命中测试视图或第一响应者将事件或消息传递给其视图控制器(如果有);如果视图没有视图控制器,则将事件或消息传递给其父视图。
  2. 如果视图或其视图控制器无法处理事件或消息,则将其传递给视图的父视图。
  3. 如果每个后续的超级视图在层次结构中无法处理事件或消息,则会按照前两个步骤中描述的模式进行处理。
  4. 如果视图层次结构中最上层的视图不能处理事件或消息,则将其传递给窗口对象进行处理。
  5. 窗口对象如果无法处理事件或消息,则将其传递给单例应用程序对象。”
我的答案不是唯一的解决方案,您需要在同一个响应者链中制定滑动和收缩手势处理逻辑,以便将未处理的收缩手势事件传递到正确的响应者。例如,您可以省略子视图 b,并将向左/向右滑动手势添加到aView的superView中。

嗯,@flypig,我知道通常触摸事件会通过响应者链向上传递(从子视图到父视图,而不是从子视图到兄弟视图)。一种选择是将View B作为View A的子视图。但正如我在问题编辑中指出的那样,由于子视图A是第三方库视图的实例,我相信该视图在运行时会添加更多的视图。因此,我不能将subView B作为subView A的子视图,因为有可能subView A会在B上面添加更多的视图,从而阻止所有对B的触摸。我无法控制这个库正在做什么。 - Krishnabhadra
我提出这个问题只是想知道是否有其他方法来传递触摸事件到除父视图以外的任何视图。 - Krishnabhadra
1
请尝试将左右滑动添加到aView的superView中。也许我们不应该将触摸事件传递到其他地方,因为响应者链是一个设计模式,我们不应该打破它。 - flypig

1

我之前做过类似的事情。我想用一个手指和两个或更多手指做两件不同的事情。我的解决方案可能需要一些调整,而且我不能保证在你的代码环境中一切都能完全按照我的方式工作。我没有找到hitTest的适当文档,也不知道为什么它会连续调用多次。所以我进行了一些测试,并得出了一个解决方案,如何区分单指和多指手势,这对我的需求非常有效。

我有这个视图层次结构:

---> A superView 
 |
 ---> ScrollView - A - all touches
 |
 ---> ScrollView - B - only two+ finger scroll (exactly on top of A, completely blocking A).

我在superView的hitTest中实现了开关:
BOOL endDrag; // indicates scrollviewB did finish
BOOL shouldClearHitTest; // bool to help calling the method clearHitTest only once.
BOOL multiTouch; // needed for the method clearHitTest to know if a multi-touch is detected or not
NSMutableArray *events; // store all events within one eventloop/touch;


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!endDrag) {
        if (!shouldClearHitTest) { // as hitTest will be called multible times in one event-loop and i need all events, i will clean the temporaries when everything is done
            shouldClearHitTest = YES;
            [[NSRunLoop mainRunLoop] performSelector:@selector(clearHitTest) target:self argument:nil order:1 modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
        }
        [events addObject:event]; // store the events so i can access them after all hittests for one touch are evaluated

        if (event.type == UIEventTypeTouches && ([_events count] > 3 || [[event allTouches] count] > 0 || _currentEvent)) { // two or more fingers detected. at least for my view hierarchy
            multiTouch = YES;
            return scrollViewB;
        }
    }else {
        endDrag = NO;
    }
    return scrollViewA;
}

- (void)clearHitTest {
    if (shouldClearHitTest) {
        shouldClearHitTest = NO;
        [events removeAllObjects];
        if (multiTouch) {
           // Do some special stuff for multiTouch
        }
        multiTouch = NO;
    }
}

0
我建议您在需要的地方使用以下代码行,可能会帮助您解决问题。
//--->for bview gesture to detect swipe
[pinch requireGestureRecognizerToFail:leftSwipe]; && 
[pinch requireGestureRecognizerToFail:rightSwipe];

//--->for aview gesture to detect pinch
[rightSwipe requireGestureRecognizerToFail:rightSwipe]; ||
[leftSwipe requireGestureRecognizerToFail:pinch];

实际上,这是所有识别器的父类UIGestureRecognizer的一种方法。 当您将leftSwipe或rightSwipe设置为失败时,它将在向左或向右滑动时消除捏合手势的效果。 同样,捏合手势也可以失败左右滑动手势。 - Kuldeep
@Krishnabhadra 我猜这肯定会对你有所帮助。当你想要滑动时,它会自动移除捏合手势的检测,而当你想要捏合时,它会自动移除滑动手势的检测。 - Kuldeep

-1
如果bView具有捏合手势,则从bView的捏合手势操作调用aView的捏合手势操作,那么...
UIPinchGestureRecognizer *pinchA   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinchA:)];
[aView addGestureRecognizer:pinchA];
[pinchA release];    

UIPinchGestureRecognizer *pinchB   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinchB:)];
[bView addGestureRecognizer:pinchB];
[pinchB release];


-(void)handlePinchA:(UIGestureRecognizer*)gestureRecognizer
{
NSLog(@"handlePinch on A");  
}

-(void)handlePinchB:(UIGestureRecognizer*)gestureRecognizer
{
NSLog(@"handlePinch on B");
[self handlePinchA:gestureRecognizer];  
}

aView 是一个第三方库。你能从外部调用 handlePinchA 方法吗?NSLog gestureRecognizers 会打印出 action=handlePinchA

NSLog(@"gestureRecognizers %@", [aView gestureRecognizers]);

输出我:

gestureRecognizers (
"<UIPinchGestureRecognizer: 0x8b3e500; state = Possible; view = <UIView 0x8b2f470>; target= <(action=handlePinchA:, target=....

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