How to send the touch events of UIScrollView to the view behind it?

后端 未结 3 1240
忘了有多久
忘了有多久 2021-02-19 04:37

i have a transparent UIScrollView on top of another view.

the scroll view has content - text and images, used to display info.

the view behind it has some images

3条回答
  •  不知归路
    2021-02-19 05:27

    Here I present my complete solution that:

    • Forwards touches directly to views instead of calling a control event.
    • User can specify which classes to forward.
    • User can specify which views to check if forward is needed.

    Here the interface:

    /**
     * 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  *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
    

    And the Implementation:

    #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
    

    The only extra code used is inside the UIView+Additions.h category, which contains the following method:

    - (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];
        }
    }
    

    Thanks

提交回复
热议问题