Как отменить нажатие кнопки, если срабатывает UIGestureRecognizer?

Обновление: проблема, похоже, связана с ошибкой другого GestureRecognizer. См. Комментарии и тестовый проект под этим вопросом!

В моем приложении для iPhone у меня есть представление с несколькими UIButton в качестве подпредставлений. В представлении также есть UITapGestureRecognizer, который прослушивает касания двумя пальцами.

Когда в представлении происходит касание двумя пальцами, я не хочу, чтобы кнопки реагировали на касание, даже если один из пальцев находился внутри кнопки. Я думал, что "cancelsTouchesInView" для этого, но это не работает.

У меня вопрос: как заставить мои кнопки игнорировать нажатия при распознавании жестов?

Изменить: это мой распознаватель жестов.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapped:)];
[doubleTap setNumberOfTouchesRequired:2];
[doubleTap setNumberOfTapsRequired:1];
[doubleTap setCancelsTouchesInView:YES];
[doubleTap setDelaysTouchesBegan:YES];
[doubleTap setDelaysTouchesEnded:YES];
[self.view addGestureRecognizer:doubleTap];
[doubleTap release];

person Cornelius    schedule 31.05.2011    source источник
comment
Убедившись в том, что Deepak и gcamp убедили меня в том, что cancelsTouchesInView должен работать, я продолжил исследование и нашел корень этой проблемы: рядом с жестом в моем примере у меня также есть распознаватель жестов двумя пальцами и двумя касаниями. Распознаватель doubleTap, указанный выше, настроен так, чтобы требовать сбоя этого twoFingerDoubleTap. По какой-то причине это мешает работе cancelsTouchesInView. Кто-нибудь знает, почему это происходит?   -  person Cornelius    schedule 01.06.2011
comment
Я сделал для этого тестовый проект: dl.dropbox.com/u/3996208/ iPhone / toucheventtest.zip   -  person Cornelius    schedule 01.06.2011


Ответы (4)


По словам разработчика Apple, это ошибка. Я отправил отчет об ошибке в Apple. Большое спасибо за подсказки, Дипак и gcamp!

Сообщение об ошибке:

Сводка: при добавлении двух UITapGestureRecognizer в представление, где один требует сбоя другого (requiresGestureRecognizerToFail :), свойство cancelsTouchesInView первого распознавателя жестов игнорируется.

Шаги для воспроизведения: 1. Создайте два UITapGestureRecognizer (r1 и r2) 2. Настройте r1, чтобы требовать два касания и одно касание и задерживать касания Начало 3. Настройте r2, чтобы требовать два касания и два касания и задерживать касания Начало 4. Настройте r1, чтобы требовать r2 для сбоя [r1 requiresGestureRecognizerToFail: r2] 5. Добавьте r1 и r2 в представление 6. Поместите UIButton в представление 7. Нажмите двумя пальцами на представление, нужно нажать кнопку.

Ожидаемые результаты: r1 должен быть распознан, а нажатие кнопки должно быть отменено (по умолчанию cancelsTouchesInView имеет значение YES для UITapGestureRecognizers).

Фактические результаты: r1 распознается, но также запускается событие button touchedUpInside.

Регрессия: cancelTouchesInView отлично работает для r1 после удаления зависимости от r2 (шаг 4).

person Cornelius    schedule 02.06.2011

В UIGestureRecognizer есть метод, который отвечает на ваш вопрос

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer

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

So,

[singleTapRecognizer requireGestureRecognizerToFail:twoTapRecognizer];
person gcamp    schedule 31.05.2011
comment
Значит, мне пришлось бы добавить распознаватель жестов одиночного касания к каждой из моих кнопок (у меня их много)? Будет ли это отменять обычные события касания? - person Cornelius; 31.05.2011
comment
Это не переопределит обычные события касания, но если вы удалите addTarget:action:forControlEvents:, который вы уже сделали, и поместите только цель / действие в распознаватель одиночного касания, это должно сработать. - person gcamp; 31.05.2011

Используйте свойство delaysTouchesBegan. Установите YES.

Альтернатива

Отключите взаимодействие пользователя с кнопками и прикрепите распознаватель касания одним пальцем, как указано выше. В обработчике касаний проверьте, попадает ли касание в границы кнопки. Если он находится в пределах кнопки, сделайте [theButton sendActionsForControlEvents:UIControlEventTouchUpInside];. Это вызовет событие ретуши по желанию, даже если взаимодействие с пользователем отключено.

person Deepak Danduprolu    schedule 31.05.2011
comment
Я уже этим занимаюсь, но это не помогает. Должен был упомянуть об этом в исходном посте, извините. - person Cornelius; 31.05.2011
comment
Каково поведение, когда вы это используете? - person Deepak Danduprolu; 31.05.2011
comment
Жест распознается, но кнопки все равно нажимаются - person Cornelius; 31.05.2011
comment
Удалите как delaysTouchesEnded, так и cancelsTouchesInView и попробуйте. Я сделал образец приложения, и оно работает. - person Deepak Danduprolu; 31.05.2011
comment
Удалены оба (хотя значения по умолчанию должны быть такими же?), И это не помогло. Тем не менее, распознаватель жестов срабатывает и вызывается действие touchUpInside (связанное с Interfacebuilder) ... Должно быть, здесь происходит что-то действительно странное ...: - / Я добавил некоторое протоколирование, сначала срабатывает распознаватель жестов, затем кнопки. Кажется, что cancelsTouchesInView (по умолчанию NO) игнорируется? - person Cornelius; 31.05.2011
comment
Странный. Я добавил альтернативу. Если вам интересно, вы можете это проверить. - person Deepak Danduprolu; 31.05.2011
comment
Я сузил проблему еще немного. Если у вас есть время, было бы очень любезно с вашей стороны взглянуть на комментарии под моим первоначальным вопросом. Спасибо! - person Cornelius; 01.06.2011

Марк, я столкнулся с той же ошибкой. Если вы опубликуете номер своего радара, я его обману в bugreporter.

Я написал следующее обходное решение. Я создаю подкласс UITapGestureRecognizer, а затем использую подкласс для отслеживания касаний, запускающих действие жеста. Если мы получаем те же штрихи в touchesEnded в представлении, мы перенаправляем на touchesCancelled. «НемедленныйTarget» необходим, потому что вызов touchesEnded в представлении происходит после touchEnded жеста, но до вызова действия жеста.

RPTapGestureRecognizer.h:

#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>

@interface RPTapGestureRecognizer : UITapGestureRecognizer

@property (nonatomic, retain) NSSet *lastTouches;

- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction;

@end

RPTapGestureRecognizer.m:

#import "RPTapGestureRecognizer.h"

@interface RPTapGestureRecognizer ()
@property (nonatomic) SEL immediateAction;
@property (nonatomic, assign) id immediateTarget;
@end

@implementation RPTapGestureRecognizer

@synthesize lastTouches;
@synthesize immediateAction, immediateTarget;

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.lastTouches = nil;

    [super touchesBegan:touches withEvent:event];
}

- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction
{
    self.immediateTarget = inTarget;
    self.immediateAction = inAction;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UIGestureRecognizerState startingState, finalState;

    startingState = self.state;
    [super touchesEnded:touches withEvent:event];
    finalState = self.state;

    if (startingState != finalState &&
        finalState == UIGestureRecognizerStateEnded) {
        /* Must copy; the underlying NSCFSet will be modified by the superclass, removing the touches */
        self.lastTouches = [[touches copy] autorelease];

        if (self.immediateAction)
            [self.immediateTarget performSelector:self.immediateAction
                                       withObject:self];
    }
}

- (void)dealloc
{
    self.lastTouches = nil;
    self.immediateTarget = nil;

    [super dealloc];
}

@end

В представлении:

@synthesize lastTapGesture

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.lastTapGesture && [self.lastTapGesture.lastTouches isEqualToSet:touches]) {
        [self touchesCancelled:touches withEvent:event];
        self.lastTapGesture = nil;
        return;
    }
 /* touches ended implementation here */
}

 - (void)willHandleMouseTapGesture:(RPTapGestureRecognizer *)tapGesture
{
    /* Because one tap gesture is dependent upon another, the touchesEnded method is still going to be called, instead of touchesCanceled.
     * This is an Apple bug against all versions of iOS at least up to 5.0. It will be called in the run loop before tapGesture's action is called.
     *
     * See http://stackoverflow.com/questions/6188997/how-to-cancel-button-tap-if-uigesturerecognizer-fires/6211922#6211922 for details.
     */
    self.lastTapGesture = tapGesture;
}

- (void)configureGestures
{
    /* Two finger taps */
     RPTapGestureRecognizer *tapGestureOne;
     tapGestureOne = [[[RPTapGestureRecognizer alloc] initWithTarget:self 
                                                              action:@selector(handleMouseTapGesture:)] autorelease];
     tapGestureOne.numberOfTapsRequired = 1;
     tapGestureOne.numberOfTouchesRequired = 2;
     [tapGestureOne setImmediateTarget:self action:@selector(willHandleMouseTapGesture:)];
     [self addGestureRecognizer:tapGestureOne];
     [myGestures addObject:tapGestureOne];

     RPTapGestureRecognizer *tapGestureTwo;
     tapGestureTwo = [[[RPTapGestureRecognizer alloc] initWithTarget:self 
                                                              action:@selector(handleMouseTapGesture:)] autorelease];
     tapGestureTwo.numberOfTapsRequired = 2;
     tapGestureTwo.numberOfTouchesRequired = 2;
     [tapGestureTwo setImmediateTarget:self action:@selector(willHandleMouseTapGesture:)];
     [self addGestureRecognizer:tapGestureTwo];
}
person Evan Schoenberg    schedule 10.07.2011
comment
Привет, Эван, спасибо за ответ. Идентификатор обнаруженной мной ошибки: 9540729. - person Cornelius; 11.07.2011