Отслеживание и анализ неотзывчивых жестов в ios

В настоящее время я реализую сценарии, связанные с аналитикой, которые будут запускать события с помощью Localytics.

Я хотел бы отслеживать каждый «неправильный» жест, это означает, что если пользователь нажимал что-то, что, по его мнению, можно было нажать (но не было), или панорамировал вид, который, по его мнению, можно было панорамировать (но не было).

Как именно я могу отследить эти неправильные жесты?

Изменить - попытка заставить Лео Натана ответить

Я добавил расширение в класс UIView со следующими методами

    @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;
    }

Как можно было видеть, я добавил журнал самого представления, чтобы увидеть, какие представления проверяются на пути ответа.

и в результате, хотя кнопка, по которой была нажата кнопка, реагировала на событие касания, она регистрировалась, как если бы она проигнорировала его (дважды)

это вывод журнала (всего 1 щелчок):

2014-06-15 16: 58: 07.393 Nutrino [63516: 60b] Просмотр - [UIButton: 0xcc92750; кадр = (11 27; 20 16); непрозрачный = НЕТ; слой = [CALayer: 0xcc92360]]

2014-06-15 16: 58: 07.393 Nutrino [63516: 60b] Просмотр - [NRDiaryLogPlanView: 0xcc902b0; кадр = (0480; 320 88); gestureRecognizers = [NSArray: 0xcc92f90]; слой = [CALayer: 0xcc908e0]]

2014-06-15 16: 58: 07.394 Nutrino [63516: 60b] Просмотр - [UIView: 0xce79b00; кадр = (0 516; 320 568); слой = [CALayer: 0xce79b60]]

2014-06-15 16: 58: 07.394 Nutrino [63516: 60b] Просмотр - [UILabel: 0xce799d0; кадр = (139,685 27; 40,63 20,264); text = 'Дневник'; скрытый = ДА; userInteractionEnabled = НЕТ; слой = [CALayer: 0xce794b0]]

2014-06-15 16: 58: 07.395 Nutrino [63516: 60b] Просмотр - [UIView: 0xce78900; кадр = (0 0; 540 568); альфа = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; слой = [CALayer: 0xce78960]]

2014-06-15 16: 58: 07.395 Nutrino [63516: 60b] ИГНОРИРОВАНИЕ: Просмотр [[UIView: 0xce78900; кадр = (0 0; 540 568); альфа = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; layer = [CALayer: 0xce78960]]] получил событие типа [UITouchesEvent] в точке (268,90), но было проигнорировано. 2014-06-15 16: 58: 07.396 Nutrino [63516: 60b] Просмотр - [UIButtonLabel: 0xce76c10; кадр = (21 4; 77 14); text = 'НОВЫЕ КАРТЫ'; clipsToBounds = ДА; непрозрачный = НЕТ; userInteractionEnabled = НЕТ; слой = [CALayer: 0xce76d60]]

2014-06-15 16: 58: 07.396 Nutrino [63516: 60b] ИГНОРИРОВАНИЕ: Просмотр [[UIButtonLabel: 0xce76c10; кадр = (21 4; 77 14); text = 'НОВЫЕ КАРТЫ'; clipsToBounds = ДА; непрозрачный = НЕТ; userInteractionEnabled = НЕТ; layer = [CALayer: 0xce76d60]]] получил событие типа [UITouchesEvent] в точке (22,8), но было проигнорировано. 2014-06-15 16: 58: 07.397 Nutrino [63516: 60b] Просмотр - [UIImageView: 0xce7ebc0; frame = (11,5 6; 10 10); clipsToBounds = ДА; непрозрачный = НЕТ; userInteractionEnabled = НЕТ; слой = [CALayer: 0xce86a50]]

2014-06-15 16: 58: 07.397 Nutrino [63516: 60b] Просмотр - [UIButton: 0xce764c0; кадр = (225 77; 110 22); непрозрачный = НЕТ; слой = [CALayer: 0xce76670]]

2014-06-15 16: 58: 07.397 Nutrino [63516: 60b] Просмотр - [NRCoachFeedView: 0xce67560; кадр = (0 0; 540 568); слой = [CALayer: 0xce65370]]

2014-06-15 16: 58: 07.398 Nutrino [63516: 60b] Просмотр - [UIView: 0xce48c00; кадр = (0 0; 320 568); слой = [CALayer: 0xce44980]]

2014-06-15 16: 58: 07.398 Nutrino [63516: 60b] Просмотр - [UIView: 0xcf55af0; кадр = (0 0; 320 568); autoresize = RM + BM; autoresizesSubviews = НЕТ; слой = [CALayer: 0xcf55470]]

2014-06-15 16: 58: 07.399 Nutrino [63516: 60b] Просмотр - [UIViewControllerWrapperView: 0xcc93030; кадр = (0 0; 320 568); autoresize = RM + BM; слой = [CALayer: 0xcc93100]]

2014-06-15 16: 58: 07.399 Nutrino [63516: 60b] Просмотр - [UINavigationTransitionView: 0xcf48cf0; кадр = (0 0; 320 568); clipsToBounds = ДА; авторазмер = W + H; слой = [CALayer: 0xcf48e10]]

2014-06-15 16: 58: 07.400 Nutrino [63516: 60b] Просмотр - [UILayoutContainerView: 0xcf40270; кадр = (0 0; 320 568); авторазмер = W + H; gestureRecognizers = [NSArray: 0xcf4d680]; слой = [CALayer: 0xcf3e570]]

2014-06-15 16: 58: 07.400 Nutrino [63516: 60b] Просмотр - [UIView: 0xcf40110; кадр = (0 0; 320 568); autoresize = RM + BM; autoresizesSubviews = НЕТ; слой = [CALayer: 0xcf3f980]]

2014-06-15 16: 58: 07.400 Nutrino [63516: 60b] Просмотр - [UIWindow: 0xce42c20; кадр = (0 0; 320 568); авторазмер = W + H; gestureRecognizers = [NSArray: 0xce452b0]; слой = [UIWindowLayer: 0xce43aa0]]

2014-06-15 16: 58: 07.401 Nutrino [63516: 60b] Просмотр - [UIStatusBar: 0xd075600; кадр = (0 0; 320 20); альфа = 0; скрытый = ДА; непрозрачный = НЕТ; авторазмер = W + BM; слой = [CALayer: 0xce30f50]]

2014-06-15 16: 58: 07.402 Нутрино [63516: 60b] Просмотр - [UIStatusBarWindow: 0xcc3ee20; кадр = (0 0; 320 568); gestureRecognizers = [NSArray: 0xcc3fdc0]; слой = [UIWindowLayer: 0xcc3f060]]

2014-06-15 16: 58: 07.402 Nutrino [63516: 60b] Просмотр - [UIButton: 0xcc92750; кадр = (11 27; 20 16); непрозрачный = НЕТ; слой = [CALayer: 0xcc92360]]

2014-06-15 16: 58: 07.403 Nutrino [63516: 60b] Просмотр - [NRDiaryLogPlanView: 0xcc902b0; кадр = (0480; 320 88); gestureRecognizers = [NSArray: 0xcc92f90]; слой = [CALayer: 0xcc908e0]]

2014-06-15 16: 58: 07.403 Nutrino [63516: 60b] Просмотр - [UIView: 0xce79b00; кадр = (0 516; 320 568); слой = [CALayer: 0xce79b60]]

2014-06-15 16: 58: 07.403 Nutrino [63516: 60b] Просмотр - [UILabel: 0xce799d0; кадр = (139,685 27; 40,63 20,264); text = 'Дневник'; скрытый = ДА; userInteractionEnabled = НЕТ; слой = [CALayer: 0xce794b0]]

2014-06-15 16: 58: 07.404 Nutrino [63516: 60b] Просмотр - [UIView: 0xce78900; кадр = (0 0; 540 568); альфа = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; слой = [CALayer: 0xce78960]]

2014-06-15 16: 58: 07.404 Nutrino [63516: 60b] ИГНОРИРОВАНИЕ: Просмотр [[UIView: 0xce78900; кадр = (0 0; 540 568); альфа = 0; gestureRecognizers = [NSArray: 0xcc92fc0]; layer = [CALayer: 0xce78960]]] получил событие типа [UITouchesEvent] в точке (268,90), но было проигнорировано. 2014-06-15 16: 58: 07.405 Nutrino [63516: 60b] Просмотр - [UIButtonLabel: 0xce76c10; кадр = (21 4; 77 14); text = 'НОВЫЕ КАРТЫ'; clipsToBounds = ДА; непрозрачный = НЕТ; userInteractionEnabled = НЕТ; слой = [CALayer: 0xce76d60]]

2014-06-15 16: 58: 07.405 Nutrino [63516: 60b] ИГНОРИРОВАНИЕ: Просмотр [[UIButtonLabel: 0xce76c10; кадр = (21 4; 77 14); text = 'НОВЫЕ КАРТЫ'; clipsToBounds = ДА; непрозрачный = НЕТ; userInteractionEnabled = НЕТ; layer = [CALayer: 0xce76d60]]] получил событие типа [UITouchesEvent] в точке (22,8), но было проигнорировано. 2014-06-15 16: 58: 07.406 Nutrino [63516: 60b] Просмотр - [UIImageView: 0xce7ebc0; frame = (11,5 6; 10 10); clipsToBounds = ДА; непрозрачный = НЕТ; userInteractionEnabled = НЕТ; слой = [CALayer: 0xce86a50]]

2014-06-15 16: 58: 07.406 Nutrino [63516: 60b] Просмотр - [UIButton: 0xce764c0; кадр = (225 77; 110 22); непрозрачный = НЕТ; слой = [CALayer: 0xce76670]]

2014-06-15 16: 58: 07.406 Nutrino [63516: 60b] Просмотр - [NRCoachFeedView: 0xce67560; кадр = (0 0; 540 568); слой = [CALayer: 0xce65370]]

2014-06-15 16: 58: 07.407 Nutrino [63516: 60b] Просмотр - [UIView: 0xce48c00; кадр = (0 0; 320 568); слой = [CALayer: 0xce44980]]

2014-06-15 16: 58: 07.407 Nutrino [63516: 60b] Просмотр - [UIView: 0xcf55af0; кадр = (0 0; 320 568); autoresize = RM + BM; autoresizesSubviews = НЕТ; слой = [CALayer: 0xcf55470]]

2014-06-15 16: 58: 07.407 Нутрино [63516: 60b] Представление - [UIViewControllerWrapperView: 0xcc93030; кадр = (0 0; 320 568); autoresize = RM + BM; слой = [CALayer: 0xcc93100]]

2014-06-15 16: 58: 07.408 Nutrino [63516: 60b] Просмотр - [UINavigationTransitionView: 0xcf48cf0; кадр = (0 0; 320 568); clipsToBounds = ДА; авторазмер = W + H; слой = [CALayer: 0xcf48e10]]

2014-06-15 16: 58: 07.408 Представление Nutrino [63516: 60b] - [UILayoutContainerView: 0xcf40270; кадр = (0 0; 320 568); авторазмер = W + H; gestureRecognizers = [NSArray: 0xcf4d680]; слой = [CALayer: 0xcf3e570]]

2014-06-15 16: 58: 07.409 Nutrino [63516: 60b] Просмотр - [UIView: 0xcf40110; кадр = (0 0; 320 568); autoresize = RM + BM; autoresizesSubviews = НЕТ; слой = [CALayer: 0xcf3f980]]

2014-06-15 16: 58: 07.409 Nutrino [63516: 60b] Просмотр - [UIWindow: 0xce42c20; кадр = (0 0; 320 568); авторазмер = W + H; gestureRecognizers = [NSArray: 0xce452b0]; слой = [UIWindowLayer: 0xce43aa0]]


person David Ben Ari    schedule 10.06.2014    source источник


Ответы (1)


Один из способов сделать это - обернуть реализацию hitTest:withEvent: и, при определенных условиях, распечатать отладочный вывод.

@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

Это дает вам возможность увидеть, где события касания, которые начались внутри представления, но были проигнорированы из-за определенных условий (по умолчанию iOS игнорирует события, когда представление скрыто, отключено, взаимодействие с пользователем отключено или альфа меньше 0,01.

Вы увидите такой вывод:

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.

В данном случае я коснулся заголовка панели навигации.

Надеюсь, это даст вам представление о том, как решать проблемы с сенсорным экраном.

person Leo Natan    schedule 15.06.2014
comment
Я добавил эту точную логику в свой код, но теперь каждое событие касания регистрируется дважды (независимо от того, игнорировалось оно или нет), что мне не хватает? - person David Ben Ari; 15.06.2014
comment
Проверка на nil должна гарантировать, что только представления, игнорирующие журнал касаний. Вы видите запись дважды, потому что есть событие касания и касания. Вы можете развернуть объект event и найти способ определить его тип (прикоснуться или коснуться). - person Leo Natan; 15.06.2014
comment
Таким образом, в этом случае, если на представление, которое является частью иерархии нескольких представлений, был получен ответ, представления контейнера будут регистрироваться как проигнорированные. Я также хотел бы игнорировать их, так как мне в конечном итоге узнать в конце события касания, было ли оно проигнорировано всеми содержащими представлениями или нет? - person David Ben Ari; 15.06.2014
comment
Процесс проверки попадания начинается с содержания представления и детализируется до подпредставлений, пока больше не останется подпредставлений, говорящих о существовании ДА. Теперь, когда этот вывод сделан, каждое представление поднимается либо с результатом своих подвидов, либо с тестированием себя на ДА или НЕТ. Если в конце концов все скажут НЕТ, то прикосновение игнорируется. - person Leo Natan; 15.06.2014
comment
С распознавателями жестов система немного жульничает. Сначала он обращается к распознавателям и позволяет им предотвращать прикосновения к просмотрам. Вероятно, вы могли бы создать аналогичный swizzle для жестов с каким-нибудь частным API (для отладки все в порядке). - person Leo Natan; 15.06.2014
comment
Либо мне не хватает чего-то очевидного, либо что-то не так с кодом в вашем ответе, потому что он регистрирует события касания в представлениях, которые реагируют, хотя этого не должно быть. - person David Ben Ari; 15.06.2014
comment
Давайте продолжим это обсуждение в чате. - person David Ben Ari; 15.06.2014