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

jdgnovmf  于 2023-03-24  发布在  iOS
关注(0)|答案(4)|浏览(184)

我在另一个视图上有一个透明的UIScrollView。
滚动视图具有用于显示信息的内容-文本和图像。
它后面的视图具有用户应该能够点击的一些图像,并且它们上面的内容可以使用所提到的滚动视图来滚动。
我希望能够正常使用滚动视图(没有缩放虽然),但当滚动视图实际上并没有滚动,让点击事件通过它后面的视图。
使用触摸和滚动事件的组合,我可以确定什么时候让点击通过。但是它后面的视图仍然没有接收到它们。
我已经尝试使用这样的东西为所有触摸事件:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    NSLog(@"touchesBegan %@", (_isScrolling ? @"YES" : @"NO"));

    if(!_isScrolling) 
    {
        NSLog(@"sending");
        [self.viewBehind touchesBegan:touches withEvent:event];
    }
}

但它不起作用。
另外,在我的情况下,我不能真正应用hitTest和pointInside解决方案,考虑到我的用例。

jobtbby3

jobtbby31#

首先,UIScrollView s只能识别UIPanGestureRecognizer s和UIPinchGestureRecognizer s,因此您需要将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:操作,您可以使用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;
        }
    }
}

这样,上面的代码只有在识别出点击手势时才执行,如果点击位置福尔斯图像内,程序只对滚动视图上的点击做出React;否则,您可以像往常一样滚动UIScrollView

6ju8rftf

6ju8rftf2#

在这里,我提出了我的完整解决方案:

  • Forwards直接接触视图而不是调用控件事件。
  • 用户可以指定要转发的类。
  • 如果需要转发,用户可以指定要检查的视图。

这里的接口:

/**
 * 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];
    }
}

谢谢

gcuhipw9

gcuhipw93#

问题是你的UIScrollView正在使用事件。要传递它,你必须禁用用户在它上面的交互,但是它不会滚动。但是,如果你有触摸位置,你可以使用convertPoint:toView:方法计算它会落在底层视图的什么位置,然后通过传递CGPoint来调用它的方法。从那里,你可以计算出哪个图像被点击了。

kmynzznz

kmynzznz4#

这样做非常简单:
class TrickScrollView: UIScrollView {
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        
        print("point! \(point)")
        
        if point.y < 400 { // example
            print("     .. pass on to whatever is behind")
            return false
        }
        
        // operate normally
        return super.point(inside: point, with: event)
    }
}

在代码行“y〈400”处,只需使用自己的逻辑来决定是否要通过或正常处理。

相关问题