Подтвердить что ты не робот

Как отправить события касания UIScrollView в представление позади него?

У меня есть прозрачный 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, учитывая тот вариант использования, который у меня есть.

4b9b3361

Ответ 1

Сначала UIScrollView распознает только UIPanGestureRecognizer и UIPinchGestureRecognizer, поэтому вам нужно добавить 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 location in the entire 
    // view 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 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, как обычно.

Ответ 2

Здесь я представляю свое полное решение, что:

  • Нападающие непосредственно касаются представлений вместо вызова контрольного события.
  • Пользователь может указать, какие классы пересылать.
  • Пользователь может указать, какие представления проверять, требуется ли пересылка.

Здесь интерфейс:

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

Спасибо

Ответ 3

Проблема заключается в том, что ваш UIScrollView потребляет событие. Чтобы пройти через это, вам придется отключить взаимодействие пользователя с ним, но тогда он не будет прокручиваться. Однако, если у вас есть привязка к местоположению, вы можете рассчитать, где это будет падать в базовом представлении, используя метод convertPoint:toView:, и вызовите mathod на нем, передав CGPoint. Оттуда вы можете рассчитать, какое изображение было использовано.