web-dev-qa-db-fra.com

Autoriser UIScrollView et ses sous-vues à répondre au temps

Je souhaite que mon UIScrollView et ses sous-vues reçoivent tous les événements tactiles de la sous-vue. Chacun peut répondre à sa manière.

Autrement, si les gestes de prise étaient transmis aux sous-vues, tout irait bien.

Beaucoup de gens luttent dans ce domaine général. Voici quelques-unes des nombreuses questions connexes:

Comment UIScrollView dérobe-t-il les contacts dans ses sous-vues
Comment voler des contacts dans UIScrollView?
Comment annuler le défilement dans UIScrollView

Incidemment, si je remplace hitTest: withEvent: dans la vue de défilement, je vois les touches tant que userInteractionEnabled a pour valeur YES. Mais cela ne résout pas vraiment mon problème, car:

1) À ce stade, je ne sais pas si c'est un robinet ou non.
2) Parfois, je dois définir userInteractionEnabled sur NO.

EDIT: Pour clarifier, oui, je veux traiter les robinets différemment des casseroles. Les taps doivent être manipulés par des sous-vues. Les casseroles peuvent être manipulées par la vue de défilement de la manière habituelle. 

21
William Jockusch

Tout d'abord, un avertissement. Si vous définissez userInteractionEnabled sur NO sur UIScrollView, aucun événement tactile ne sera transmis aux sous-vues. Autant que je sache, il n'y a pas d'autre solution à ce problème, à une exception près: intercepter des événements tactiles dans la vue supérieure de UIScrollView et les transmettre spécifiquement aux sous-vues de UIScrollView. Pour être honnête, cependant, je ne sais pas pourquoi vous voudriez faire cela. Si vous souhaitez désactiver des fonctionnalités spécifiques de UIScrollView (telles que ... bien, le défilement), vous pouvez le faire assez facilement sans désactiver UserInteraction.

Si je comprends votre question, vous avez besoin que les événements de tap soient traités par UIScrollView et transmis aux sous-vues? Dans tous les cas (quel que soit le geste), je pense que vous recherchez la méthode de protocole gestureRecognizer: shouldRecognizeSimultaneousWithGestureRecognizer: dans le protocole UIGestureRecognizerDelegate. Dans vos sous-vues, quel que soit le type de reconnaissance de geste que vous avez, définissez un délégué (probablement quelle que soit la classe qui définit UIGestureReconginzer en premier lieu) sur la reconnaissance de geste. Remplacez la méthode ci-dessus et retournez YES. Maintenant, ce geste sera reconnu avec tous les autres outils de reconnaissance qui pourraient l'avoir "volé" (dans votre cas, un tap). En utilisant cette méthode, vous pouvez même affiner votre code pour n’envoyer que certains types de gestes aux sous-vues ou uniquement dans certains cas. Cela vous donne beaucoup de contrôle. Veillez simplement à lire sur la méthode, en particulier cette partie: 

Cette méthode est appelée lors de la reconnaissance d'un geste par gestureRecognizer ou otherGestureRecognizer bloquerait le fichier autre reconnaissance de geste de reconnaître son geste. Notez que renvoyer YES est garanti pour permettre la reconnaissance simultanée; renvoyer NON, d'autre part, n'est pas garanti d'empêcher reconnaissance simultanée parce que l'autre reconnaisseur de geste le délégué peut retourner OUI.

Bien sûr, il y a une mise en garde: cela ne s'applique qu'aux dispositifs de reconnaissance de geste. Donc, vous pouvez toujours avoir des problèmes si vous essayez d'utiliser touchesBegan:, touchesEnded, etc. pour traiter les touches. Vous pouvez bien sûr utiliser hitTest: pour envoyer des événements tactiles bruts aux sous-vues, mais pourquoi? Pourquoi traiter les événements à l'aide de ces méthodes dans UIView, lorsque vous pouvez attacher une UIGestureRecognizer à une vue et obtenir gratuitement toutes ces fonctionnalités? Si vous avez besoin que les touches soient traitées de manière qu'aucune UIGestureRecognizer ne puisse fournir, sous-classe} _ UIGestureRecognizer et y traite les touches. De cette façon, vous obtenez toutes les fonctionnalités d'une UIGestureRecognizer avec votre propre traitement tactile personnalisé. Je pense vraiment que Apple souhaitait que UIGestureRecognizer remplace la plupart (sinon la totalité) du code de traitement tactile personnalisé utilisé par les développeurs sur UIView. Cela permet la réutilisation du code et il est beaucoup plus facile de traiter lorsque le code traite quel événement tactile. 

38
Aaron Hayman

Je ne sais pas si cela peut vous aider, mais j'ai eu un problème similaire, dans lequel je voulais que le scrollview gère le double-tap, mais transfère le single tap aux sous-vues. Voici le code utilisé dans une CustomScrollView

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch* touch = [touches anyObject];
    // Coordinates
    CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]];

    // One tap, forward
    if(touch.tapCount == 1){
        // for each subview
        for(UIView* overlayView in self.subviews){
            // Forward to my subclasss only
            if([overlayView isKindOfClass:[OverlayView class]]){
                // translate coordinate
                CGPoint newPoint = [touch locationInView:overlayView];
                //NSLog(@"%@",NSStringFromCGPoint(newPoint));

                BOOL isInside = [overlayView pointInside:newPoint withEvent:event];
                //if subview is hit
                if(isInside){
                    Forwarding
                    [overlayView touchesEnded:touches withEvent:event];
                    break;
                }
            }
        }

    }
    // double tap : handle zoom
    else if(touch.tapCount == 2){

        if(self.zoomScale == self.maximumZoomScale){
            [self setZoomScale:[self minimumZoomScale] animated:YES];
        } else {
            CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];            

            [self zoomToRect:zoomRect animated:YES];
        }

        [self setNeedsDisplay];

    }
}

Bien entendu, le code en vigueur doit être modifié, mais vous devez disposer à présent de toutes les informations nécessaires pour décider si vous devez transférer l'événement. Vous devrez peut-être implémenter cela dans une autre méthode comme touchesMoved: withEvent :.

J'espère que cela peut aider.

3
Oyashiro

J'avais le même problème, mais avec une vue de défilement qui se trouvait à l'intérieur de UIPageViewController, il a donc dû être traité légèrement différemment.

En modifiant la propriété cancelsTouchesInView en false pour chaque identificateur sur la UIScrollView, j'ai pu recevoir des contacts avec des boutons à l'intérieur de la UIPageViewController.

Je l'ai fait en ajoutant ce code dans viewDidLoad:

guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else {
     print("No gesture recognizers on scrollview.")
     return
}

for recognizer in recognizers {
    recognizer.cancelsTouchesInView = false
}

Si vous avez besoin de faire la différence entre une touche et un parchemin, vous pouvez vérifier si les touches ont été déplacées. S'il s'agit d'un toucher, alors touchHasBeenMoved ne sera pas appelé, vous pouvez donc supposer qu'il s'agit d'un contact.

À ce stade, vous pouvez définir un booléen pour indiquer si un mouvement est pris en compte et définir ce booléen comme condition de vos autres méthodes. 

Je suis sur la route mais si c'est ce dont vous avez besoin, je pourrai mieux l'expliquer plus tard. 

0
shannoga

Une façon astucieuse de réaliser votre objectif - et non à 100% avec exactitude - consiste à sous-classer UIWindow et à remplacer l'événement - (void) sendEvent: (UIEvent *);

Un exemple rapide:

dans l'en-tête SecondResponderWindow.h

//SecondResponderWindow.h

@protocol SecondResponderWindowDelegate
- (void)userTouchBegan:(id)tapPoint onView:(UIView*)aView;
- (void)userTouchMoved:(id)tapPoint onView:(UIView*)aView;
- (void)userTouchEnded:(id)tapPoint onView:(UIView*)aView;
@end

@interface SecondResponderWindow : UIWindow
@property (nonatomic, retain) UIView *viewToObserve;
@property (nonatomic, assign) id <SecondResponderWindowDelegate> controllerThatObserves;
@end

dans SecondResponderWindow.m

//SecondResponderWindow.m

- (void)forwardTouchBegan:(id)touch onView:(UIView*)aView {
    [controllerThatObserves userTouchBegan:touch onView:aView];
}
- (void)forwardTouchMoved:(id)touch onView:(UIView*)aView {
    [controllerThatObserves userTouchMoved:touch onView:aView];
}
- (void)forwardTouchEnded:(id)touch onView:(UIView*)aView {
    [controllerThatObserves userTouchEnded:touch onView:aView];
}

- (void)sendEvent:(UIEvent *)event {
    [super sendEvent:event];

    if (viewToObserve == nil || controllerThatObserves == nil) return;

    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    if ([touch.view isDescendantOfView:viewToObserve] == NO) return;

    CGPoint tapPoint = [touch locationInView:viewToObserve];
    NSValue *pointValue = [NSValue valueWithCGPoint:tapPoint];

    if (touch.phase == UITouchPhaseBegan)
        [self forwardTouchBegan:pointValue onView:touch.view];
    else if (touch.phase == UITouchPhaseMoved)
        [self forwardTouchMoved:pointValue onView:touch.view];
    else if (touch.phase == UITouchPhaseEnded)
        [self forwardTouchEnded:pointValue onView:touch.view];
    else if (touch.phase == UITouchPhaseCancelled)
        [self forwardTouchEnded:pointValue onView:touch.view];
}

Ce n'est pas à 100% conforme à vos attentes, car votre deuxième vue répondeur ne gère pas l'événement tactile de manière native via -touchDidBegin: ou autre, et doit implémenter SecondResponderWindowDelegate. Cependant, ce hack vous permet de gérer les événements tactiles sur d'autres intervenants.

Cette méthode est inspirée et étendue de TapDetectingWindow de MITHIN KUMAR

0
xingzhi.sg