12

我有 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
4

5 回答 5

11

您没有遍历视图层次结构。从文档中:

此方法通过向每个子视图发送 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 的子视图。

但是,TestView1实现TestView2是完全相同的(即使在您的原始帖子中),那么为什么需要将它们分开呢?您可以使 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];
于 2013-08-08T09:08:20.600 回答
5

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

userInteractionEnable为简单起见,这些代码没有考虑alpha和其他因素。
当您调用[super pointInside:point withEvent:event];' TestView1s - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event时,它会询问该点是否在 v2 的区域内。如果 v2 的答案是肯定的并且因为它没有任何子视图,所以 v2 将在其- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event.

这就是所有的故事。

于 2013-08-08T09:08:20.600 回答
2

据我从您的问题中了解到,您在 V1 之上添加了更大的 V2。所以 V2 只能在 V1 的范围内被触摸。所以你的手势在 V2 的额外区域中是无法识别的。

于 2013-08-05T14:22:38.153 回答
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 的视图对象。此方法在确定命中时不考虑视图的内容。因此,即使指定点位于视图内容的透明部分中,仍可以返回视图。

于 2013-08-08T08:25:11.083 回答
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;
    }

    ...
}

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

于 2013-08-08T09:31:26.593 回答