hitTest:WithEvent和Subviews

时间:2013-08-05 14:11:58

标签: iphone ios objective-c hittest uitapgesturerecognizer

我有2个视图,但我想让1个视图(虚拟)更大。 如果我将tapGesture放在v1上,则点击手势可以使用更大的命中区域 但是如果我将我的tapGesture放在v2上它就不起作用(实际上它根本不识别tapGesture,甚至不在原始边界内),即使我循环通过我的TestView1 hittest方法并且点数得到控制在框架中。

#import "ViewController.h"

@interface TestView1 : UIView
@end

@implementation TestView1

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

@end

@interface TestView2 : UIView
@end

@implementation TestView2

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}
@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    TestView1 *v1 = [[TestView1 alloc] initWithFrame:CGRectMake(50.f, 50.f, 100.f, 100.f)];
    [self.view addSubview:v1];

    TestView2 *v2 = [[TestView2 alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
    v2.backgroundColor = UIColor.yellowColor;
    [v1 addSubview:v2];

    UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
    [v2 addGestureRecognizer:gesture];
}

- (void) panGesture:(UIPanGestureRecognizer *)recognizer
{
    NSLog(@"tap");
}
@end

5 个答案:

答案 0 :(得分:10)

您没有遍历视图层次结构。来自文档:

  

此方法通过向每个子视图发送pointInside:withEvent:消息来遍历视图层次结构,以确定哪个子视图应该接收触摸事件。如果pointInside:withEvent:返回YES,则遍历子视图的层次结构;否则,忽略视图层次结构的分支。

您只需要实施pointInside:withEvent:。您不应该覆盖hitTest:withEvent:,因为标准实现将调用您的pointInside:withEvent:实现,并为您完成遍历层次结构的所有艰苦工作。

像这样实施:

@interface TestView1 : UIView
@end

@implementation TestView1

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    return (CGRectContainsPoint(frame, point));
}

@end

@interface TestView2 : UIView
@end

@implementation TestView2

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    return (CGRectContainsPoint(frame, point));
}

@end

现在,您是否需要这两种观点取决于您。您已经成功扩展了v1的可触摸区域,使用上面的代码,您可以使用v2作为v1的子视图。

但是,TestView1TestView2实现完全相同(即使在您的原始帖子中),那么为什么需要将它们分开?您可以使v1和v2成为同一类的两个实例,例如TestView,并按如下方式实例化它们:

TestView *v1 = [[TestView alloc] initWithFrame:CGRectMake(50.f, 50.f, 100.f, 100.f)];
[self.view addSubview:v1];
v1.clipsToBounds = YES;

TestView *v2 = [[TestView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
v2.backgroundColor = UIColor.yellowColor;
[v1 addSubview:v2];

答案 1 :(得分:5)

您的v2不会收到任何触摸事件。因为当你单击v1周围的区域时,它会在self中返回- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event,这意味着你已经声明它是“v1”,即命中测试视图,谁是所有的目的地触摸事件。
扩展v1可触摸区域的正确方法是在- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventTestView1中实施TestView2

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                          self.frame.size.width + radius,
                          self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return YES;
    }
    return [super pointInside:point withEvent:event];
}

上面的代码意味着,当你点击v1周围的区域时,它会声明“是的,你已经触动了我。我会检查谁可以处理它。也许是我,也许这是我的一个子视图”。所以命中测试继续,v1会发现它的子视图v2是最顶层的视图,因此v2是你点击事件的目的地。

你可能会问v1怎么知道v2就是那个。这是揭示诀窍的伪代码:

@implementation UIView
//...
//...

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return CGRectContainsPoint(self.bounds, point); // Honestly tell others if the point is inside the bounds. That's the normal case.
}

// This method returns a hit-test view who or whose gesture recognizer is responsible for handling the events
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    for(UIView *aSubview in self.subviews)
    {
        // Ask each subview if the point falls in its area.
        if ([aSubview pointInside:[self convertPoint:point toView:aSubview]  point withEvent:event])
        {
            return [aSubview hitTest:[self convertPoint:point toView:aSubview] withEvent:event];
        }
    }

    // If no one can handle the event.
    return self;
}

//...
//...
@end

为简单起见,这些代码未考虑userInteractionEnablealpha和其他因素 当您在[super pointInside:point withEvent:event];的{​​{1}}中致电TestView1时,会询问该点是否位于v2的区域内。如果v2的答案是肯定的,并且因为它没有任何子视图,那么v2将在其- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event中返回。

这就是故事。

答案 2 :(得分:2)

据我所知,你在V1之上添加了更大的V2。所以V2只能在V1的范围内触摸。因此,在V2​​的额外区域中无法识别您的手势。

答案 3 :(得分:2)

您需要实现方法:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return YES;
    }

    return [super pointInside:point withEvent:event];
}

然后:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(0, 0,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return [super hitTest:point withEvent:event;
}

来自文档

  

此方法通过发送遍历视图层次结构   pointInside:withEvent:消息到每个子视图以确定哪个   子视图应该会收到触摸事件。如果pointInside:withEvent:   返回YES,然后遍历子视图的层次结构;否则,它   视图层次结构的分支被忽略。你很少需要打电话   方法你自己,但你可以覆盖它来隐藏触摸事件   子视图。

     

此方法忽略已隐藏且已禁用的视图对象   用户交互,或者alpha级别小于0.01。这种方法   在确定点击时不会考虑视图的内容。   因此,即使指定的点在a中,仍然可以返回视图   该视图内容的透明部分。

答案 4 :(得分:2)

你这样做的方式是可能的,但很难做到正确。

我建议将UITapGestureRecognizer添加到他们的公共超级视图中,然后根据点击位置决定接收者的视图,例如

- (void)onTap:(UITapGestureRecognizer*)tapRecognizer {
    CGPoint touchPosition = [tapRecognizer locationInView:self];

    CGRect view1Frame = self.view1.frame;
    view1Frame.width += 100;
    view1Frame.height += 100;

    if (CGRectContainsPoint(view1Frame, touchPosition)) {
        [self.view1 handleTap];
        return;
    }

    ...
}

如果视图没有共同的超视图,只需将每个视图嵌入更大的透明视图中,并将识别器添加到更大的视图中。