Search code examples
iosobjective-ccore-animationcalayer

iOS animation presentationLayer hitTest does not seem to work as expected


I'm trying to add touch events to small view blocks drifting from screen right side to left side. The problem is layer.presentationLayer hitTest:point method returns nothing, even if I do actually tap within the bounds of the view block.

Finally, I find a workaround. But, two questions still confuse me.

  1. The converted point does not seem to be right, how to properly use presentationLayer hitTest?
  2. In my code snippet, UIViewAnimationOptionAllowUserInteraction is not set, why I can handle touch event anyway?

The following are the code, any help will be appreciated

viewDidLoad

CGRect bounds = [[UIScreen mainScreen] bounds];

for (int i = 0; i < 15; i++) {

    ViewBlock *view = [[ViewBlock alloc] initWithFrame:CGRectMake(bounds.size.width, 200, 40, 40)];
    [self.view addSubview:view];


    [UIView animateWithDuration:20 delay:i * 21 options:UIViewAnimationOptionCurveLinear animations:^{
        view.frame = CGRectMake(-40, 200, 40, 40);
    } completion:^(BOOL finished) {
        [view removeFromSuperview];
    }];
}

ViewBlock.m

@implementation ViewBlock
- (instancetype)initWithFrame:(CGRect)frame{

    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor redColor];
    }
    return self;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    CALayer *presentationLayer = self.layer.presentationLayer; //Do NOT modify presentationLayer
    if (presentationLayer) {
        CGPoint layer_point = [presentationLayer convertPoint:point fromLayer:self.layer.modelLayer];
        if ([presentationLayer hitTest:point]) {
            return self; //not worked
        }

        if ([presentationLayer hitTest:layer_point]) {
            return self; //not worked either
        }

        if (CGRectContainsPoint(presentationLayer.bounds, layer_point)) {
            return self;  //the workaround
        }
    }
    return [super hitTest:point withEvent:event];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    NSLog(@"touches began");
}

- (void)didMoveToSuperview{
    [super didMoveToSuperview];

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapTouched:)]
    ;
    tap.cancelsTouchesInView = NO;

    [self addGestureRecognizer:tap];
}

- (void)tapTouched:(UITapGestureRecognizer *)sender{
    NSLog(@"touches gesture");
}
@end

Solution

  • Q1: The converted point does not seem to be right, how to properly use presentationLayer hitTest?

    What you are missing is the parameter of hitTest: should be in the coordinate system of the receiver's super layer, so the code should be:

    CGPoint superLayerP = [presentationLayer.superlayer convertPoint:layer_point fromLayer:presentationLayer];
    if ([presentationLayer hitTest:superLayerP]) {
        return self;
    }
    

    Q2:In my code snippet, UIViewAnimationOptionAllowUserInteraction is not set, why I can handle touch event anyway

    I think you can get touch event because you override - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event and return self as result, so the event will pass to it. By default(If you don't override the hitTest:withEvent: method), you won't get touch event.

    If you set UIViewAnimationOptionAllowUserInteraction option, you can get touch event on the view's final frame (CGRectMake(-40, 200, 40, 40)), but in this example, the final frame is off the screen, you can set it to CGRectMake(0, 200, 40, 40) and have a test.