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

Проблемы с распознавателем жестов в iOS 7

Я добавляю несколько объектов UIView (например, 5) к экрану, один внутри другого. Это, например, view5.superview = view4, view4.superview = view3, view3.superview=view2, view2.superview = view1. Для всех этих UIView я устанавливаю uitapgesturerecognizer; для view1-4 Я просто делаю NSLog (@ "tap% @", self) в обратном вызове, тогда как для view5 tap я устанавливаю следующее: удаляю view4 из иерархии, затем помещаю тот же объект view4 'в то же место иерархии, Этот объект также содержит view5 ', для которого установлен UITapGestureRecognizer (практически, я заменяю одну часть разметки на аналогичную).

Затем я нажимаю на view5. Некоторое время view5 продолжает ловить краны и все в порядке, но случайное количество кранов позже (каждый раз, когда этот номер отличается), один из объектов view1-4 начинает ловить этот кран, хотя мы все еще нажимаем на view5. Вся проблема имеет случайный характер - иногда это происходит на 10-м запуске, иногда на втором. Иногда неправильные объекты начинают захватывать краны при первом касании. Также я никогда не знаю, какой объект поймает кран, когда все пойдет не так. Кадр для представления (n + 1) был установлен, например, как половина кадра (n) кадра, тогда как кадр для представления 1 - например, (0,0 320, 460).

Все операции с объектами ui, описанные выше, ведутся в основном потоке, и все, что я рассказывал, отлично работает на iOS 4.3 - 6.1 с гораздо более сложными примерами. Но iOS7 делает из него какой-то случайный ад.

Update: Я создал образец проекта, чтобы упростить процесс отладки. Не добавлять/удалять операции подсмотра при нажатии. Только 4 вида на экране, при нажатии приложения регистрирует, какой вид был использован. Итак, вам нужно нажать на самый маленький вид (4). Если вы видите, что "нажмите 4 tap 4 tap 4..." в журнале - это тот случай, когда все работает нормально, останавливается и запускается снова, останавливается и запускается снова, останавливается и запускается снова и т.д. И при некоторых прогонах (возможно, после 10 + успешные прогоны), вы не увидите "tap 4" в первой строке, вы увидите "tap 1" или "tap 2" или "tap 3", и это будет продолжаться так - это плохие случаи.

Пример проекта можно скачать здесь: http://tech.octopod.com/test/BuggySample.zip (всего 33 Kb в архиве).

Обновление 2

Мы отправили ошибку в Apple, я опубликую здесь, когда мы получим обратную связь. Однако любое хорошее обходное решение было бы высоко оценено!

Обновление 3

Решение, представленное Ювразином, действительно работает над образцовым проектом. К сожалению, он по-прежнему не помогает решить проблему, возникшую в основном проекте, где он изначально появился. Основная причина заключается в том, что если какой-либо вид без собственного жеста лежит на кликабельном содержимом, элемент случайного представления под ним начинает захватывать взаимодействие (а не верхнее с набором жестов взаимодействия). Есть ли у вас идеи, как это можно решить? Обновленный образец можно загрузить здесь: http://tech.octopod.com/test/BuggySample2.zip

4b9b3361

Ответ 1

Поскольку проблема возникает только в iOS 7, вы можете использовать один из новых методов делегирования для решения проблемы:

– gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
– gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

Я разрешил его, выполнив gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer и "обход" вверх по представлению вида жестов, чтобы я мог вернуть "ДА", если я обнаружил, что жест супервизора равен тому, который был предоставлен. Я подробно описываю свое полное разрешение здесь: fooobar.com/questions/210095/....

Объяснение
Проблема с распознавателями жестов в iOS 7 заключается в том, что жест супервизора получает свои прикосновения, прежде чем один из его жестов в подлости получит свои прикосновения. Это приводит к тому, что жест супервизора распознает, а затем отменяет распознаватель подвид... это (неверно?), И несколько ошибок были поданы с Apple. Было указано, что Apple не гарантирует порядок, в котором жесты получают штрихи. Я думаю, что многие из нас "полагаются" на детали реализации, которые изменились в iOS 7. Вот почему мы используем новые методы делегатов, которые, похоже, предназначены для помощи в решении этой проблемы.

Примечание.. Я провел обширное тестирование, используя свои собственные распознаватели подкласса, регистрировав все касания и обнаружив, что причина распознавания не срабатывает, потому что жесты супервизора получали касания до того, как жест подлога был примерно на ~ 5% случаев. Каждый раз, когда это происходило, произошел сбой. Это происходит чаще, если у вас есть "глубокие" иерархии с большим количеством жестов.

Новые методы делегатов могут ввести в заблуждение, поэтому вам нужно внимательно их прочитать.

Я использую метод (я переименовал аргументы, чтобы упростить их понимание)

– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *) otherRecognizer.

Если вы вернете "ДА", то предоставленный опознаватель жестов, otherRecognizer, потребует thisRecognizer для отказа, прежде чем он сможет быть распознан. Вот почему в моем ответе я просматриваю иерархию супервизора, чтобы проверить, содержит ли он супервизор с otherRecognizer. Если это так, я хочу, чтобы otherRecognizer требовал, чтобы thisRecognizer терпит неудачу, потому что thisRecognizer находится в подчиненном представлении и должен выйти из строя, пока не будет распознан жест супервизора. Это позволит убедиться, что жесты subview распознаются до их жестов надзора. Есть смысл?

Alternative
Я мог бы сделать это по-другому и использовать:

– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherRecognizer

Теперь мне нужно просканировать всю мою иерархию subview, проверить, есть ли в ней otherRecognizer и вернуть YES, если это так. Я не использую этот метод, потому что обход всей иерархии подзаголов намного сложнее и дороже, чем проверять иерархию супервизора. Обход иерархии подзаголовков должен быть рекурсивной функцией, тогда как я могу использовать простой цикл while для проверки иерархии супервизора. Поэтому я рекомендую первый подход, который я опишу.

Внимание!
Будьте осторожны с использованием gestureRecognizer:shouldReceiveTouch:. Проблема заключается в том, какой жест сначала принимает штрихи (отменяет другой жест)... это проблема разрешения конфликтов. Если вы реализуете gestureRecognizer:shouldReceiveTouch:, вы рискуете отказаться от жестов супервизора, если жест subview не будет выполнен, потому что вы должны угадать, когда может быть распознан жест subview. Подводный жест может на законных основаниях терпеть неудачу по причинам, отличным от тех, что касаются вне пределов, поэтому вам нужно будет знать детали реализации, чтобы правильно угадать. Вы хотите, чтобы жест супервизора был распознан, когда жест subview не удался, но вы действительно не знаете, наверняка, если он потерпит неудачу, прежде чем он на самом деле потерпит неудачу. Если жест подзаголовка не удался, обычно вы хотите, чтобы жест надзора распознал. Это нормальная цепочка ответчиков (subview superview), и если вы возитесь с этим, вы можете столкнуться с неожиданным поведением.

Ответ 2

Я сделал некоторые изменения в вашем коде, и я также много испытал его, и проблема не генерируется.

При создании представления я устанавливаю тег для каждого представления, чтобы отличить его:

View1234 *v1 = [[View1234 alloc] initWithId:@"1"];
v1.tag =1;
v1.backgroundColor = [UIColor redColor];
v1.frame = CGRectMake(0, 0, 320, 460);

View1234 *v2 = [[View1234 alloc] initWithId:@"2"];
v2.tag=2;
v2.backgroundColor = [UIColor greenColor];
v2.frame = CGRectMake(0, 0, 160, 230);

View1234 *v3 = [[View1234 alloc] initWithId:@"3"];
v3.tag=3;
v3.backgroundColor = [UIColor blueColor];
v3.frame = CGRectMake(0, 0, 80, 115);

View1234 *v4 = [[View1234 alloc] initWithId:@"4"];
v4.tag=4;
v4.backgroundColor = [UIColor orangeColor];
v4.frame = CGRectMake(0, 0, 40, 50);

View1234.h

@interface View1234 : UIView <UIGestureRecognizerDelegate> {
    NSString *vid;
}

- (id) initWithId:(NSString *)vid_;
@end

И вот весь код View1234.m

- (id) initWithId:(NSString *)vid_ {
    if (self = [super init]) {

        vid = [vid_ retain];

        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init];
        tapGesture.delegate = self;
        tapGesture.numberOfTapsRequired = 1;
        tapGesture.numberOfTouchesRequired = 1;
        [tapGesture addTarget:self action:@selector(handleTap:)];

        [self addGestureRecognizer:tapGesture];

        [tapGesture release];

    }

    return self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
    if (touch.view==self ) {
        return YES;
    }
    else if ([self.subviews containsObject:touch.view] && ![touch.view isKindOfClass:[self class]])
    {
        return YES;
    }
    return NO;
}

/*- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{

    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        if (self.tag==gestureRecognizer.view.tag) {
            return YES;
        }
    }

    return NO;
}*/

- (void) handleTap:(UITapGestureRecognizer *)tap {
    NSLog(@"tap %@", vid);
}

- (void) dealloc {

    [vid release];
    vid = nil;

    [super dealloc];
}

ОБНОВЛЕНИЕ: почему эта проблема действительно на самом деле.

Когда вы добавляете UIView в качестве подсмотра другого UIView с UITapGestureRecognizer в каждом представлении, то в каком-то редком случае состояние UITapGestureRecognizer становится каким-то образом Failed (у меня его отладка более 50 раз и узнают об этом). Поэтому, когда какой-либо подзадачный просмотр любого вида не способен обрабатывать жест нажатия, система будет передавать жест его супер-представлению для обработки этого жеста, и это продолжается.

Если вы отлаживаете, вы узнаете, что gestureRecognizerShouldBegin вызывается несколько раз в соответствии с иерархией представления. В этом конкретном случае, если я коснусь view3, тогда gestureRecognizerShouldBegin вызовет 3 раза, поскольку view3 находится на иерархии 3-го уровня, поэтому gestureRecognizerShouldBegin будет вызываться для view3, view2 and view1.

Поэтому для решения проблемы я возвращаю YES form gestureRecognizerShouldBegin для правильного представления и NO для остальных, поэтому он решает проблему.

ОБНОВЛЕНИЕ 2: Я изменил код в моем отредактированном ответе, и надежда решит вашу проблему. А также благодаря @masmor, я также нашел некоторые подсказки из его ответа, чтобы решить проблему.

Ответ 3

Установите делегата в распознаватель и выполните gestureRecognizer:shouldReceiveTouch:.

Реализация должна в основном блокировать привязки к subviews, но, вероятно, будут некоторые дополнительные критерии в соответствии с вашей фактической иерархией и настройкой представления.

В приведенном ниже примере просто проверяется, является ли просмотр хитов прямым подчиненным, и имеет ли он какие-либо распознаватели жестов, и в этом случае разрешено видеть штрихи.

Блокирующие штрихи должны быть более надежными, чем возиться с состояниями распознавателя, поскольку нет никаких шансов на то, что какие-либо нежелательные представления будут запускать их распознаватели. Требование реализации пользовательских критериев является недостатком, но опять-таки я считаю более надежным явное внедрение поведения при работе с ошибкой неизвестной причины, как в этом случае.

#import "View1234.h"

@interface View1234 () <UIGestureRecognizerDelegate>

@end

@implementation View1234

- (id) initWithId:(NSString *)vid_ {
    if (self = [super init]) {

        vid = [vid_ retain];

        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init];
        tapGesture.numberOfTapsRequired = 1;
        tapGesture.numberOfTouchesRequired = 1;
        [tapGesture addTarget:self action:@selector(handleTap:)];

        tapGesture.delegate = self;

        [self addGestureRecognizer:tapGesture];

        [tapGesture release];
    }

    return self;
}

//- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
//    
//    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
//        if (self.tag==gestureRecognizer.view.tag) {
//            return YES;
//        }
//    }
//    
//    return NO;
//}

- (void) handleTap:(id)tap {
    NSLog(@"tap %@", vid);
}

- (void) dealloc {

    [vid release];
    vid = nil;

    [super dealloc];
}

- (BOOL)shouldReceiveTouchOnView:(UIView *)hitView {
  NSLog(@"Should view:\n%@\nreceive touch on view:\n%@", self, hitView);

  // Replace this implementation with whatever you need...
  // Here, we simply check if the view has a gesture recognizer and
  // is a direct subview.
  BOOL res = (hitView.gestureRecognizers.count == 0 &&
              [self.subviews containsObject:hitView]);

  NSLog(@"%@", res? @"YES":@"NO");

  return res;
}

#pragma mark - Gesture Recognizer Delegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch {
  UIView *hitView = [self hitTest:[touch locationInView:self.superview]
                        withEvent:nil];
  if (hitView == self) {
    NSLog(@"Touch not in subview");
    return YES;
  }

  return [self shouldReceiveTouchOnView:hitView];
}

@end

Ответ 4

Я не пробовал ваш проект или ниже.

Вы можете использовать gestureRecognizerShouldBegin:, чтобы предотвратить любой жест, который не относится к просмотру, при стрельбе при касании вида.

Вы можете сделать это с подклассом UIView, или вы можете создать категорию на UIView (со свойством или ассоциированным объектом), который добавляет флаг для определения того, что должен делать каждый экземпляр представления - это сломает некоторые поэтому будьте осторожны.

Это не поможет, если проблема - это порядок представлений...