web-dev-qa-db-fra.com

Transférer UIGesture aux vues derrière

Je travaille sur une application iphone (iOS 4.0 ou version ultérieure) et j'ai des problèmes avec la manipulation tactile entre plusieurs vues. J'ai une structure de vue comme celle-ci

---> A superView 
     |
     ---> SubView - A 
     |
     ---> SubView - B (exactly on top of A, completely blocking A).

enter image description here

Fondamentalement, j'ai une superView, et les sous-vues de frère A et B. B a le même cadre que A, cachant donc complètement A.

Maintenant, mon exigence est la suivante.

  1. SubView B devrait recevoir tous les mouvements de balayage et de toucher (simples et doubles).
  2. SubView A devrait recevoir tous les gestes de pincement.

Voici comment j'ai ajouté des reconnaisseurs de gestes aux vues

UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
leftSwipe.delegate  =   self;
[bView addGestureRecognizer:leftSwipe];
[leftSwipe release];

UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
[rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
rightSwipe.delegate   =   self;
[bView addGestureRecognizer:rightSwipe];
[rightSwipe release];

UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
pinch.delegate    =   self;
[aView addGestureRecognizer:pinch];
[pinch release];

J'ai fait quelques recherches et pour moi UIGestureRecognizerDelegate semblait prometteur et j'ai implémenté la méthode déléguée

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

J'ai renvoyé NON pour SubView B, en espérant que la vue sous-jacente obtiendra ces événements. Mais pas de chance

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] && [gestureRecognizer view] == bView) {
        NSLog(@"pinchGesture");
        //bView.userInteractionEnabled = NO;
        return NO;
    }
    return YES;
}

Ensuite, j'ai désactivé l'interaction utilisateur de SubView B à l'intérieur du rappel du délégué (code commenté dans le bloc ci-dessus), lorsque le geste de pincement est reconnu, en espérant que la partie restante du geste sera reçue par SubView A. Pas de chance là aussi ..

Voilà où j'en suis maintenant. J'ai vu this question, où la réponse acceptée implique la propriété cancelsTouchesInView. Mais je crois que cancelsTouchesInView annule uniquement un événement tactile particulier, ne le transférez pas.

Une autre manière de réaliser mon exigence? Je suis prêt à travailler sur tout indice que vous fournissez.

EDIT: BOUNTY TIME

Ma soi-disant sous-vue A est en fait une instance de la classe View d'une bibliothèque tierce, qui enlève toutes les touches et je n'ai aucun contrôle sur les gestes. Je veux une implémentation différente pour le balayage vers la gauche et vers la droite et je veux que le pincement, le toucher, etc. fonctionnent comme il fonctionne avec cette vue tierce. J'ai donc mis une vue au-dessus de A (SubView B) pour obtenir des balayages gauche et droit. Mais maintenant, je veux transmettre d'autres événements gestuels à la bibliothèque sous-jacente.

57
Krishnabhadra

Si je comprends que votre problème est correct, vous pouvez simplement ajouter une autre vue claire avec rect, identique à la vue A et B, et implémenter tous les gestes dessus: lorsque vous effectuez un geste de pincement, contrôlez la sous-vue A, lorsque vous glissez et touchez (simple et double) gestes - contrôlez la sous-vue B. Vous pouvez le faire de différentes manières: via des pointeurs ou simplement en envoyant un geste reçu à la méthode en classe, qui contrôle votre sous-vue.

par exemple:

UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
leftSwipe.delegate  =   subViewAcontroller;
[clearView addGestureRecognizer:leftSwipe];
[leftSwipe release];

UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
[rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
rightSwipe.delegate   =   subViewAcontroller;
[clearView addGestureRecognizer:rightSwipe];
[rightSwipe release];

UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
pinch.delegate    =   subViewBcontroller;
[clearView addGestureRecognizer:pinch];
[pinch release];

ou:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
        NSLog(@"pinchGesture");
        [subViewBcontroller solvePinchGesture: gestureRecognizer];
    }
//etc
    return YES;
}
18
SentineL

Le geste de pincement ne sera jamais reçu par l'aView, car vous voyez que la hiérarchie est incorrecte.

---> A superView 
 |
 ---> SubView - A 
 |
 ---> SubView - B (exactly on top of A, completely blocking A).

bView capture toutes les touches multiples, y compris le geste de pincement, mais bView ne gère pas le geste de pincement, il le transmet donc au Répondeur suivant dans la chaîne Répondeur, c'est-à-dire la superView de bView ou le viewController de bView. L'événement pinch ne sera jamais transmis à ses vues frères, car elles sont en parallèle (pas de relation parent-enfant ou view-viewController).

Vous pouvez modifier votre hiérarchie de vues comme ci-dessous:

---> A superView 
 |
 ---> SubView - A 
       |
       ---> SubView - B (child view of b, exactly on top of A, completely blocking A)



- (void)viewDidLoad {
    [super viewDidLoad];

    aView = [[UIView alloc] initWithFrame:self.view.bounds];
    aView.backgroundColor = [UIColor blueColor];

    bView = [[UIView alloc] initWithFrame:self.view.bounds];
    bView.backgroundColor = [UIColor redColor];

    [self.view addSubview:aView];
    [aView addSubview:bView];

    UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
    [leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
    leftSwipe.delegate  =   self;
    [bView addGestureRecognizer:leftSwipe];

    UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
    [rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
    rightSwipe.delegate   =   self;
    [bView addGestureRecognizer:rightSwipe];

    UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
    pinch.delegate    =   self;
    [aView addGestureRecognizer:pinch];
}

Veuillez obtenir plus d'informations à partir du lien ci-dessous: https://developer.Apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/EventsiPhoneOS/EventsiPhoneOS.html#//Apple_ref/doc/uid/TP40009541 -CH2-SW1

-> Note importante de l'événement passant dans la chaîne de répondeur "Lorsque le système délivre un événement tactile, il l'envoie d'abord à une vue spécifique. Pour les événements tactiles, cette vue est celle renvoyée par hitTest: withEvent :; pour" shaking " "-Motion events, remote-control events, action messages, and edit-menu messages, that view is the first responder. Si la vue initiale ne gère pas l'événement, elle parcourt la chaîne de répondeurs le long d'un chemin particulier:

  1. La vue hit-test ou le premier répondant transmet l'événement ou le message à son contrôleur de vue s'il en a un; si la vue n'a pas de contrôleur de vue, elle transmet l'événement ou le message à sa vue d'ensemble.
  2. Si une vue ou son contrôleur de vue ne peut pas gérer l'événement ou le message, il le transmet à la vue d'ensemble de la vue.
  3. Chaque superview suivante dans la hiérarchie suit le modèle décrit dans les deux premières étapes si elle ne peut pas gérer l'événement ou le message.
  4. La vue la plus élevée de la hiérarchie des vues, si elle ne gère pas l'événement ou le message, la transmet à l'objet fenêtre pour la gestion.
  5. L'objet UIWindow, s'il ne gère pas l'événement ou le message, le transmet à l'objet d'application singleton. "

ma réponse ci-dessus n'est pas la seule solution, ce que vous devez faire est de faire des logiques de gestion des gestes de balayage et de pincement dans la même chaîne de répondeurs, de sorte que l'événement de non-traitement des pincements sera transmis au bon répondeur. Par exemple, vous pouvez omettre la sous-vue b et ajouter les mouvements de balayage gauche/droite à superView de aView.

15
flypig

J'ai fait quelque chose de similaire il y a quelque temps. Je voulais faire deux choses différentes avec un doigt et deux doigts ou plus. Ma solution pourrait avoir besoin de quelques modifications pour vous et je ne peux pas m'assurer que tout fonctionnera exactement de la même manière dans votre environnement de code. Je n'ai pas trouvé la documentation appropriée de hitTest et pourquoi il est appelé plusieurs fois de suite. J'ai donc fait quelques tests et suis parvenu à une solution, comment séparer les gestes à un doigt et à plusieurs doigts, ce qui fonctionnait très bien pour mes besoins.

J'avais cette hiérarchie de vues:

---> A superView 
 |
 ---> ScrollView - A - all touches
 |
 ---> ScrollView - B - only two+ finger scroll (exactly on top of A, completely blocking A).

J'ai implémenté le commutateur dans hitTest de la superView:

BOOL endDrag; // indicates scrollviewB did finish
BOOL shouldClearHitTest; // bool to help calling the method clearHitTest only once.
BOOL multiTouch; // needed for the method clearHitTest to know if a multi-touch is detected or not
NSMutableArray *events; // store all events within one eventloop/touch;


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!endDrag) {
        if (!shouldClearHitTest) { // as hitTest will be called multible times in one event-loop and i need all events, i will clean the temporaries when everything is done
            shouldClearHitTest = YES;
            [[NSRunLoop mainRunLoop] performSelector:@selector(clearHitTest) target:self argument:nil order:1 modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
        }
        [events addObject:event]; // store the events so i can access them after all hittests for one touch are evaluated

        if (event.type == UIEventTypeTouches && ([_events count] > 3 || [[event allTouches] count] > 0 || _currentEvent)) { // two or more fingers detected. at least for my view hierarchy
            multiTouch = YES;
            return scrollViewB;
        }
    }else {
        endDrag = NO;
    }
    return scrollViewA;
}

- (void)clearHitTest {
    if (shouldClearHitTest) {
        shouldClearHitTest = NO;
        [events removeAllObjects];
        if (multiTouch) {
           // Do some special stuff for multiTouch
        }
        multiTouch = NO;
    }
}
1
Jonathan Cichon

Si bView a un geste de pincement. De l'action de geste de pincement de bView si elle est appelée action de geste de pincement de aView, alors ...

UIPinchGestureRecognizer *pinchA   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinchA:)];
[aView addGestureRecognizer:pinchA];
[pinchA release];    

UIPinchGestureRecognizer *pinchB   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinchB:)];
[bView addGestureRecognizer:pinchB];
[pinchB release];


-(void)handlePinchA:(UIGestureRecognizer*)gestureRecognizer
{
NSLog(@"handlePinch on A");  
}

-(void)handlePinchB:(UIGestureRecognizer*)gestureRecognizer
{
NSLog(@"handlePinch on B");
[self handlePinchA:gestureRecognizer];  
}

aView est une bibliothèque ThirdParty. Pouvez-vous appeler la méthode handlePinchA de l'extérieur? NSLog gestureRecognizers m'imprime action = handlePinchA

NSLog(@"gestureRecognizers %@", [aView gestureRecognizers]);

Imprime moi:

gestureRecognizers (
"<UIPinchGestureRecognizer: 0x8b3e500; state = Possible; view = <UIView 0x8b2f470>; target= <(action=handlePinchA:, target=....
0

Je vous suggère d'utiliser la ligne de code ci-dessous lorsque cela est nécessaire peut vous aider.

//--->for bview gesture to detect swipe
[pinch requireGestureRecognizerToFail:leftSwipe]; && 
[pinch requireGestureRecognizerToFail:rightSwipe];

//--->for aview gesture to detect pinch
[rightSwipe requireGestureRecognizerToFail:rightSwipe]; ||
[leftSwipe requireGestureRecognizerToFail:pinch];
0
Kuldeep