iosuievent

Why can't the event be triggered when the iOS view moves


I tested it. When I moved the View, the tap event on the view doesn't respond, and it only responded when it stopped. I don't know why this happens. Below is my code

@interface ViewController ()
@property(nonatomic,strong)UIView *testView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.testView = [[UIView alloc]init];
    self.testView.backgroundColor = [UIColor redColor];
    self.testView.frame = CGRectMake(0, 100, 100, 100);
    [self.view addSubview:self.testView];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapTestView:)];
    [self.testView addGestureRecognizer:tap];
}
- (void)tapTestView:(UITapGestureRecognizer *)tap {
    
    NSLog(@"Event triggered");
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [UIView animateWithDuration:6 animations:^{
        CGRect frame = self.testView.frame;
        frame.origin.x = 400;
        self.testView.frame = frame;
    }];
}

Solution

  • First, you want to make sure to use animatewithduration:delay:options:animations:completion: with the UIViewAnimationOptionAllowUserInteraction option. If you do not use this option, animated views will not receive touches.

    [UIView animateWithDuration:26 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
        CGRect frame = self.testView.frame;
        frame.origin.x = ...;
        self.testView.frame = frame;
    } completion:nil];
    

    Second, when views are animated, if you examine the frame of that view as it is moving, you will see that the frame property won’t reflect where the view really is at that moment of time, but, rather, where it will eventually be. Specifically, the frame property will be that value you set in that animations block, even if it hasn’t finished actually moving there, yet.

    The implication of this behavior is that, even if you enable user interaction during animation, as shown above, gestures will be recognized based upon the view’s final destination rather than where it is now. E.g., if you start animation of frame from rect A to rect B, the gesture recognizer will recognize taps within rect B even if the view is not there yet! And conversely, it won’t recognize taps where the animated view happens to be, because the frame effectively thinks the view is already at the destination.

    To get around that issue, one must refer to the view’s layer’s presentationLayer to get the location of the view mid-animation. One approach is to add a gesture recognizer to the main view and then, in there, see if the touch was inside the animated view’s presentation layer:

    - (void)tapMainView:(UITapGestureRecognizer *)tap {
        CGPoint point = [tap locationInView:self.testView.superview];
        CGRect frame = self.testView.layer.presentationLayer.frame;
    
        BOOL isInTestView = CGRectContainsPoint(frame, point);
    
        if (isInTestView) {
            [self testViewAction];
            return;
        }
    
        // otherwise, we're in the super view
        [self mainViewAction];
    }
    

    Alternatively, you can (a) define a UIView subclass for the child view and (b) override its hitTest implementation to factor in the presentation layer:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        if ([self.layer.presentationLayer hitTest:[self convertPoint:point toView:self.superview]]) {
            return self;
        } else {
            return nil;
        }
    }