3
votes

I'm currently implementing analytics related scenarios that will fire events using Localytics.

I would like to track every "wrong" gesture, that means if a user tapped something he thought was tappable (but wasn't) or panned a view he thought was pannable (but wasn't).

How exactly can I track those wrong gestures?

Edit - trying to make Leo Natan's answer work

I have added an extension on UIView class with the following methods

    @implementation UIView (NRExtensions)

    #pragma mark - finding unresponsive touches

    + (void)load {
        Method orig = class_getInstanceMethod([UIView class], @selector(hitTest:withEvent:));
        Method debg = class_getInstanceMethod([UIView class], @selector(_swiz_hitTest:withEvent:));

        method_exchangeImplementations(orig, debg);
    }

    - (UIView *)_swiz_hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        //Will actually call the original implementation of the method, not infinite recursion.
        UIView* hitTestObj = [self _swiz_hitTest:point withEvent:event];
        NSLog(@"View - %@\n\n",self);
        if(hitTestObj == nil && [self pointInside:point withEvent:event])
        {
            NSLog(@"\nIGNORING :View <%@> got event of type <%@> in point (%.0f,%.0f) but was ignored.\n", self, [event class], point.x, point.y);
        }

        return hitTestObj;
    }

As could be seen I added a log of the view itself to see which views are being checked along the way for responding

and the result is that even though the button that was clicked responded to the touch event, it got logged as if it ignored it (twice)

this is the log output (of just the 1 click):

2014-06-15 16:58:07.393 Nutrino[63516:60b] View - [UIButton: 0xcc92750; frame = (11 27; 20 16); opaque = NO; layer = [CALayer: 0xcc92360]]

2014-06-15 16:58:07.393 Nutrino[63516:60b] View - [NRDiaryLogPlanView: 0xcc902b0; frame = (0 480; 320 88); gestureRecognizers = [NSArray: 0xcc92f90]; layer = [CALayer: 0xcc908e0]]

2014-06-15 16:58:07.394 Nutrino[63516:60b] View - [UIView: 0xce79b00; frame = (0 516; 320 568); layer = [CALayer: 0xce79b60]]

2014-06-15 16:58:07.394 Nutrino[63516:60b] View - [UILabel: 0xce799d0; frame = (139.685 27; 40.63 20.264); text = 'Diary'; hidden = YES; userInteractionEnabled = NO; layer = [CALayer: 0xce794b0]]

2014-06-15 16:58:07.395 Nutrino[63516:60b] View - [UIView: 0xce78900; frame = (0 0; 540 568); alpha = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; layer = [CALayer: 0xce78960]]

2014-06-15 16:58:07.395 Nutrino[63516:60b] IGNORING :View [[UIView: 0xce78900; frame = (0 0; 540 568); alpha = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; layer = [CALayer: 0xce78960]]] got event of type [UITouchesEvent] in point (268,90) but was ignored. 2014-06-15 16:58:07.396 Nutrino[63516:60b] View - [UIButtonLabel: 0xce76c10; frame = (21 4; 77 14); text = ' NEW CARDS '; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = [CALayer: 0xce76d60]]

2014-06-15 16:58:07.396 Nutrino[63516:60b] IGNORING :View [[UIButtonLabel: 0xce76c10; frame = (21 4; 77 14); text = ' NEW CARDS '; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = [CALayer: 0xce76d60]]] got event of type [UITouchesEvent] in point (22,8) but was ignored. 2014-06-15 16:58:07.397 Nutrino[63516:60b] View - [UIImageView: 0xce7ebc0; frame = (11.5 6; 10 10); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = [CALayer: 0xce86a50]]

2014-06-15 16:58:07.397 Nutrino[63516:60b] View - [UIButton: 0xce764c0; frame = (225 77; 110 22); opaque = NO; layer = [CALayer: 0xce76670]]

2014-06-15 16:58:07.397 Nutrino[63516:60b] View - [NRCoachFeedView: 0xce67560; frame = (0 0; 540 568); layer = [CALayer: 0xce65370]]

2014-06-15 16:58:07.398 Nutrino[63516:60b] View - [UIView: 0xce48c00; frame = (0 0; 320 568); layer = [CALayer: 0xce44980]]

2014-06-15 16:58:07.398 Nutrino[63516:60b] View - [UIView: 0xcf55af0; frame = (0 0; 320 568); autoresize = RM+BM; autoresizesSubviews = NO; layer = [CALayer: 0xcf55470]]

2014-06-15 16:58:07.399 Nutrino[63516:60b] View - [UIViewControllerWrapperView: 0xcc93030; frame = (0 0; 320 568); autoresize = RM+BM; layer = [CALayer: 0xcc93100]]

2014-06-15 16:58:07.399 Nutrino[63516:60b] View - [UINavigationTransitionView: 0xcf48cf0; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = [CALayer: 0xcf48e10]]

2014-06-15 16:58:07.400 Nutrino[63516:60b] View - [UILayoutContainerView: 0xcf40270; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = [NSArray: 0xcf4d680]; layer = [CALayer: 0xcf3e570]]

2014-06-15 16:58:07.400 Nutrino[63516:60b] View - [UIView: 0xcf40110; frame = (0 0; 320 568); autoresize = RM+BM; autoresizesSubviews = NO; layer = [CALayer: 0xcf3f980]]

2014-06-15 16:58:07.400 Nutrino[63516:60b] View - [UIWindow: 0xce42c20; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = [NSArray: 0xce452b0]; layer = [UIWindowLayer: 0xce43aa0]]

2014-06-15 16:58:07.401 Nutrino[63516:60b] View - [UIStatusBar: 0xd075600; frame = (0 0; 320 20); alpha = 0; hidden = YES; opaque = NO; autoresize = W+BM; layer = [CALayer: 0xce30f50]]

2014-06-15 16:58:07.402 Nutrino[63516:60b] View - [UIStatusBarWindow: 0xcc3ee20; frame = (0 0; 320 568); gestureRecognizers = [NSArray: 0xcc3fdc0]; layer = [UIWindowLayer: 0xcc3f060]]

2014-06-15 16:58:07.402 Nutrino[63516:60b] View - [UIButton: 0xcc92750; frame = (11 27; 20 16); opaque = NO; layer = [CALayer: 0xcc92360]]

2014-06-15 16:58:07.403 Nutrino[63516:60b] View - [NRDiaryLogPlanView: 0xcc902b0; frame = (0 480; 320 88); gestureRecognizers = [NSArray: 0xcc92f90]; layer = [CALayer: 0xcc908e0]]

2014-06-15 16:58:07.403 Nutrino[63516:60b] View - [UIView: 0xce79b00; frame = (0 516; 320 568); layer = [CALayer: 0xce79b60]]

2014-06-15 16:58:07.403 Nutrino[63516:60b] View - [UILabel: 0xce799d0; frame = (139.685 27; 40.63 20.264); text = 'Diary'; hidden = YES; userInteractionEnabled = NO; layer = [CALayer: 0xce794b0]]

2014-06-15 16:58:07.404 Nutrino[63516:60b] View - [UIView: 0xce78900; frame = (0 0; 540 568); alpha = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; layer = [CALayer: 0xce78960]]

2014-06-15 16:58:07.404 Nutrino[63516:60b] IGNORING :View [[UIView: 0xce78900; frame = (0 0; 540 568); alpha = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; layer = [CALayer: 0xce78960]]] got event of type [UITouchesEvent] in point (268,90) but was ignored. 2014-06-15 16:58:07.405 Nutrino[63516:60b] View - [UIButtonLabel: 0xce76c10; frame = (21 4; 77 14); text = ' NEW CARDS '; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = [CALayer: 0xce76d60]]

2014-06-15 16:58:07.405 Nutrino[63516:60b] IGNORING :View [[UIButtonLabel: 0xce76c10; frame = (21 4; 77 14); text = ' NEW CARDS '; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = [CALayer: 0xce76d60]]] got event of type [UITouchesEvent] in point (22,8) but was ignored. 2014-06-15 16:58:07.406 Nutrino[63516:60b] View - [UIImageView: 0xce7ebc0; frame = (11.5 6; 10 10); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = [CALayer: 0xce86a50]]

2014-06-15 16:58:07.406 Nutrino[63516:60b] View - [UIButton: 0xce764c0; frame = (225 77; 110 22); opaque = NO; layer = [CALayer: 0xce76670]]

2014-06-15 16:58:07.406 Nutrino[63516:60b] View - [NRCoachFeedView: 0xce67560; frame = (0 0; 540 568); layer = [CALayer: 0xce65370]]

2014-06-15 16:58:07.407 Nutrino[63516:60b] View - [UIView: 0xce48c00; frame = (0 0; 320 568); layer = [CALayer: 0xce44980]]

2014-06-15 16:58:07.407 Nutrino[63516:60b] View - [UIView: 0xcf55af0; frame = (0 0; 320 568); autoresize = RM+BM; autoresizesSubviews = NO; layer = [CALayer: 0xcf55470]]

2014-06-15 16:58:07.407 Nutrino[63516:60b] View - [UIViewControllerWrapperView: 0xcc93030; frame = (0 0; 320 568); autoresize = RM+BM; layer = [CALayer: 0xcc93100]]

2014-06-15 16:58:07.408 Nutrino[63516:60b] View - [UINavigationTransitionView: 0xcf48cf0; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = [CALayer: 0xcf48e10]]

2014-06-15 16:58:07.408 Nutrino[63516:60b] View - [UILayoutContainerView: 0xcf40270; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = [NSArray: 0xcf4d680]; layer = [CALayer: 0xcf3e570]]

2014-06-15 16:58:07.409 Nutrino[63516:60b] View - [UIView: 0xcf40110; frame = (0 0; 320 568); autoresize = RM+BM; autoresizesSubviews = NO; layer = [CALayer: 0xcf3f980]]

2014-06-15 16:58:07.409 Nutrino[63516:60b] View - [UIWindow: 0xce42c20; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = [NSArray: 0xce452b0]; layer = [UIWindowLayer: 0xce43aa0]]

1

1 Answers

4
votes

One way to go about this is to wrap the implementation of hitTest:withEvent: and, in certain conditions, print a debug output.

@interface UIView (DebugHitTest) @end

@implementation UIView (DebugHitTest)

+ (void)load
{
    Method orig = class_getInstanceMethod([UIView class], @selector(hitTest:withEvent:));
    Method debg = class_getInstanceMethod([UIView class], @selector(_swiz_hitTest:withEvent:));

    method_exchangeImplementations(orig, debg);
}

- (UIView *)_swiz_hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //Will actually call the original implementation of the method, not infinite recursion.
    UIView* hitTestObj = [self _swiz_hitTest:point withEvent:event];

    if(hitTestObj == nil && [self pointInside:point withEvent:event])
    {
        NSLog(@"View <%@> got event of type <%@> but was ignored.", self, [event class]);
    }

    return hitTestObj;
}

@end

What this gives you is to see where touch events that started inside the view but were ignored due to certain conditions (by default, iOS ignores events when a view is hidden, disabled, user interactions disabled or alpha is less than 0.01.

You will see output like this:

View <<UINavigationItemView: 0xe7cd560; frame = (138 8; 44.5 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xe7d2360>>> got event of type <UITouchesEvent> but was ignored.
View <<_UINavigationBarBackground: 0xe771070; frame = (0 -20; 320 64); opaque = NO; autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0xe7bb010>>> got event of type <UITouchesEvent> but was ignored.
View <<UINavigationItemView: 0xe7cd560; frame = (138 8; 44.5 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xe7d2360>>> got event of type <UITouchesEvent> but was ignored.
View <<_UINavigationBarBackground: 0xe771070; frame = (0 -20; 320 64); opaque = NO; autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0xe7bb010>>> got event of type <UITouchesEvent> but was ignored.

In this case, I touched the navigation bar's title.

Hopefully, this gives you an idea how to go about debugging touch issues.