hitTest:WithEvent和Subviews

我有2个观点,但是我想让1个观点(实际上)更大。 如果我把我的tapGesture放在v1上,那么点击手势的作用就是一个更大的点击区域,但是如果我把tapGesture放在v2上,它不起作用(实际上它根本不能识别tapGesture,甚至不在原始范围内)甚至虽然我循环通过我的TestView1的testing方法,并得到包含在框架中的点。

#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 

您不遍历视图层次结构。 从文档:

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

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

 - (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区域时,它会声明“是的,你已经触动了我,我会检查谁能处理它,也许是我,也许是我的一个子视图。 所以命中testing继续,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]; 在你的TestView1- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event ,它会询问这个点是否落入了v2的区域。 如果v2的回答是肯定的,并且没有任何子视图,那么v2会在- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event返回自己的- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

这就是所有的故事。

据我了解你的问题,你已经在V1之上join了更大的V2。 所以V2只能在V1的范围内触摸。 所以你的手势在V2的额外区域无法识别。

你需要实现的方法:

 - (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,则遍历子视图的层次结构; 否则,视图层次结构的分支被忽略。 你很less需要自己调用这个方法,但是你可以覆盖它来隐藏子视图中的触摸事件。

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

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

我推荐的是将UITapGestureRecognizer添加到他们的公共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; } ... } 

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