如何将UIScrollView的触摸事件发送到它后面的视图?

我有一个透明的UIScrollView在另一个视图之上。

滚动视图具有内容 – 文本和图像,用于显示信息。

它背后的观点有一些图像,用户应该能够点击。 并使用所提到的滚动视图来滚动它们上的内容。

我希望能够正常使用滚动视图(尽pipe没有放大),但是当滚动视图实际上并没有滚动,让轻拍事件通过后面的视图。

使用触摸和滚动事件的组合我可以决定什么时候让水龙头通过。 但它背后的观点仍然没有得到它们。

我曾尝试使用类似的东西来处理所有触摸事件:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"touchesBegan %@", (_isScrolling ? @"YES" : @"NO")); if(!_isScrolling) { NSLog(@"sending"); [self.viewBehind touchesBegan:touches withEvent:event]; } } 

但它不起作用。

也是在我的情况下,我不能真正应用hitTest和pointInside解决scheme,给我的用例。

首先, UIScrollView只能识别UIPanGestureRecognizerUIPinchGestureRecognizer因此您需要将UITapGestureRecognizer添加到UIScrollView以便它可以识别任何轻敲手势:

 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; // To prevent the pan gesture of the UIScrollView from swallowing up the // touch event tap.cancelsTouchesInView = NO; [scrollView addGestureRecognizer:tap]; 

然后,一旦接收到轻handleTap:手势并触发了handleTap:操作,就可以使用locationInView:来检测轻叩手势的位置是否位于滚动视图之下的某个图像的框架内,例如:

 - (void)handleTap:(UITapGestureRecognizer *)recognizer { // First get the tap gesture recognizers's location in the entire // view's window CGPoint tapPoint = [recognizer locationInView:self.view]; // Then see if it falls within one of your below images' frames for (UIImageView* image in relevantImages) { // If the image's coordinate system isn't already equivalent to // self.view, convert it so it has the same coordinate system // as the tap. CGRect imageFrameInSuperview = [image.superview convertRect:image toView:self.view] // If the tap in fact lies inside the image bounds, // perform the appropriate action. if (CGRectContainsPoint(imageFrameInSuperview, tapPoint)) { // Perhaps call a method here to react to the image tap [self reactToImageTap:image]; break; } } } 

这样,只有在识别到轻击手势时才执行上述代码,如果轻击位置落在图像内,则您的程序仅对滚动视图上的轻击作出反应; 否则,你可以像往常一样滚动你的UIScrollView

在这里我提出我的完整解决scheme:

  • 转发直接触及视图,而不是调用控件事件。
  • 用户可以指定要转发哪些类。
  • 用户可以指定哪些视图来检查是否需要转发。

这里的界面:

 /** * This subclass of UIScrollView allow views in a deeper Z index to react when touched, even if the scrollview instance is in front of them. **/ @interface MJForwardingTouchesScrollView : UIScrollView /** * Set of Class objects. The scrollview will events pass through if the initial tap is not over a view of the specified classes. **/ @property (nonatomic, strong) NSSet <Class> *forwardsTouchesToClasses; /** * Optional array of underlying views to test touches forward. Default is nil. * @discussion By default the scroll view will attempt to forward to views located in the same self.superview.subviews array. However, optionally by providing specific views inside this property, the scroll view subclass will check als among them. **/ @property (nonatomic, strong) NSArray <__kindof UIView*> *underlyingViews; @end 

而实施:

 #import "MJForwardingTouchesScrollView.h" #import "UIView+Additions.h" @implementation MJForwardingTouchesScrollView - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self != nil) { _forwardsTouchesToClasses = [NSSet setWithArray:@[UIControl.class]]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self != nil) { _forwardsTouchesToClasses = [NSSet setWithArray:@[UIControl.class]]; } return self; } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { BOOL pointInside = [self mjz_mustCapturePoint:point withEvent:event]; if (!pointInside) return NO; return [super pointInside:point withEvent:event]; } #pragma mark Private Methods - (BOOL)mjz_mustCapturePoint:(CGPoint)point withEvent:(UIEvent*)event { if (![self mjz_mustCapturePoint:point withEvent:event view:self.superview]) return NO; __block BOOL mustCapturePoint = YES; [_underlyingViews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (![self mjz_mustCapturePoint:point withEvent:event view:obj]) { mustCapturePoint = NO; *stop = YES; } }]; return mustCapturePoint; } - (BOOL)mjz_mustCapturePoint:(CGPoint)point withEvent:(UIEvent *)event view:(UIView*)view { CGPoint tapPoint = [self convertPoint:point toView:view]; __block BOOL mustCapturePoint = YES; [view add_enumerateSubviewsPassingTest:^BOOL(UIView * _Nonnull testView) { BOOL forwardTouches = [self mjz_forwardTouchesToClass:testView.class]; return forwardTouches; } objects:^(UIView * _Nonnull testView, BOOL * _Nullable stop) { CGRect imageFrameInSuperview = [testView.superview convertRect:testView.frame toView:view]; if (CGRectContainsPoint(imageFrameInSuperview, tapPoint)) { mustCapturePoint = NO; *stop = YES; } }]; return mustCapturePoint; } - (BOOL)mjz_forwardTouchesToClass:(Class)class { while ([class isSubclassOfClass:NSObject.class]) { if ([_forwardsTouchesToClasses containsObject:class]) return YES; class = [class superclass]; } return NO; } @end 

唯一使用的额外代码是在UIView+Additions.h类别中,它包含以下方法:

 - (void)add_enumerateSubviewsPassingTest:(BOOL (^_Nonnull)(UIView * _Nonnull view))testBlock objects:(void (^)(id _Nonnull obj, BOOL * _Nullable stop))block { if (!block) return; NSMutableArray *array = [NSMutableArray array]; [array addObject:self]; while (array.count > 0) { UIView *view = [array firstObject]; [array removeObjectAtIndex:0]; if (view != self && testBlock(view)) { BOOL stop = NO; block(view, &stop); if (stop) return; } [array addObjectsFromArray:view.subviews]; } } 

谢谢

问题是你的UIScrollView消耗事件。 要通过它,你将不得不禁用用户的交互,但它不会滚动。 但是,如果您有触摸位置,则可以使用convertPoint:toView:方法来计算底层视图的位置,然后通过传递CGPoint调用它的方法。 从那里,你可以计算出哪个图像被点击。