Question

J'ai un UIScrollView avec 2 pages, et je peux faire défiler horizontalement entre eux. Cependant, sur un de mes pages, j'ai UIDatePicker, et la vue de défilement est Intercepter les événements tactiles verticaux, donc je ne peux plus manipuler le sélecteur de date (sauf en cliquant ou en tapant). Est-il possible de dire au ScrollView d'envoyer les événements tactiles verticaux au sélecteur de date, mais envoyer les événements tactiles horizontaux à la vue de défilement pour passer d'une page?

Était-ce utile?

La solution

Je pense qu'il ya deux parties à ce problème. La première consiste à déterminer l'intention de l'utilisateur, et le second est d'obtenir le contrôle correct de répondre à cette intention.

Détermination intention

Je pense qu'il est important d'être clair sur ce que l'utilisateur a l'intention. Imaginez ce scénario: L'utilisateur commence toucher l'écran et déplace son doigt loin vers la gauche, mais aussi un peu. L'utilisateur probablement destiné à faire défiler la vue, et n'a pas l'intention de changer la date du tout. Il serait mauvais à la fois faire défiler la vue et changer la date, en particulier comme il se déplace hors de l'écran. Donc, pour déterminer ce que l'utilisateur a l'intention, je suggère l'algorithme suivant:

Lorsque l'utilisateur commence à toucher l'écran, enregistrer la position de départ. Comme le doigt de l'utilisateur commence à se éloigner de cette position, les contrôles ne doivent pas réagir du tout. Une fois que les toucher déplace au-delà d'une certaine distance de seuil à partir de la position de départ, déterminer si elle propose plus horizontalement ou verticalement. Si elle déplacée verticalement, l'utilisateur a l'intention de changer la date, donc ne pas tenir compte de la partie horizontale du mouvement et seulement changer la date. Si elle déplacé plus horizontalement, l'utilisateur a l'intention de faire défiler la vue, donc ignorer la partie verticale du mouvement et ne faire défiler la vue.

Mise en œuvre

Pour mettre en œuvre, vous devez gérer les événements avant que le sélecteur de UIScrollView ou la date faire. Il y a probablement quelques façons de le faire, mais un en particulier vient à l'esprit: Faire un UIView personnalisé appelé ScrollingDateMediatorView. Réglez le UIScrollView comme un enfant de ce point de vue. Remplacer hitTest du ScrollingDateMediatorView: withEvent: et pointInside: withEvent: méthodes. Ces méthodes doivent effectuer le même genre de test de recherche qui se produirait normalement, mais si le résultat est le sélecteur de date, retour auto à la place. Cette façon efficace tous les événements détourne tactiles qui ont été destinés au sélecteur de date, ce qui permet la ScrollingDateMediatorView de les traiter en premier. Ensuite, vous mettre en œuvre l'algorithme décrit ci-dessus dans les différentes touches * méthodes. Plus précisément:

Dans le touchesBegan. Méthode withEvent, enregistrer la position de départ

Dans touchesMoved: withEvent, si l'intention de l'utilisateur ne sait pas encore, déterminer si le touché a déplacé assez loin de la position de départ. Si elle a, déterminer si l'utilisateur a l'intention de faire défiler ou changer la date et enregistrer cette intention. Si on connaît déjà l'intention de l'utilisateur et il est de changer la date, envoyer le sélecteur de date du touchedMoved: message withEvent, sinon envoyer le UIScrollView le touchesMoved: message withEvent. Vous aurez à faire un travail simliar au sein touchesEnded: withEvent et touchesCancelled: withEvent pour vous assurer que les autres vues obtenir les messages appropriés. Ces deux méthodes doivent réinitialiser les valeurs enregistrées.

Une fois que vous avez correctement la propagation des événements, vous aurez probablement essayer quelques tests utilisateur pour régler le seuil de mouvement.

Autres conseils

En fait, il y a une mise en œuvre beaucoup plus simple que ce que suggère Bob. Cela fonctionne parfaitement pour moi. Vous aurez besoin de sous-classe votre UIScrollView si vous avez pas déjà, et inclure cette méthode: -

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView* result = [super hitTest:point withEvent:event];

    if ([result.superview isKindOfClass:[UIPickerView class]])
    {
         self.canCancelContentTouches = NO;  
         self.delaysContentTouches = NO;
    }
    else 
    {
         self.canCancelContentTouches = YES; // (or restore bool from prev value if needed)
         self.delaysContentTouches = YES;    // (same as above)
    }
    return result;
}

La raison pour laquelle j'utilise result.superview est que le point de vue qui obtient la touche sera en fait un UIPickerTable, qui est une API privée.

Vive

help Impressionnant Sam! J'utilisé que pour créer une catégorie simple qui Swizzles la méthode (parce que je faisais cela dans un UITableViewController et aurait donc dû faire des choses vraiment désordre à la vue sous-classe de défilement).

#import <UIKit/UIKit.h>

@interface UIScrollView (withControls)

+ (void) swizzle;

@end

Et le code principal:

#import </usr/include/objc/objc-class.h>
#import "UIScrollView+withControls.h"

#define kUIViewBackgroundImageTag 6183746
static BOOL swizzled = NO;

@implementation UIScrollView (withControls)

+ (void)swizzleSelector:(SEL)orig ofClass:(Class)c withSelector:(SEL)new;
{
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);

    if (class_addMethod(c, orig, method_getImplementation(newMethod),
                        method_getTypeEncoding(newMethod))) {
        class_replaceMethod(c, new, method_getImplementation(origMethod),
                            method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, newMethod);
    }
}

+ (void) swizzle {
    @synchronized(self) {
        if (!swizzled) {

            [UIScrollView swizzleSelector:@selector(hitTest:withEvent:)
                                  ofClass:[UIScrollView class]
                             withSelector:@selector(swizzledHitTest:withEvent:)];
            swizzled = YES;
        }
    }
}

- (UIView*)swizzledHitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView* result = [self swizzledHitTest:point withEvent:event]; // actually calling the original hitTest method

    if ([result.superview isKindOfClass:[UIPickerView class]]) {
        self.canCancelContentTouches = NO;  
        self.delaysContentTouches = NO;
    } else {
        self.canCancelContentTouches = YES; // (or restore bool from prev value if needed)
        self.delaysContentTouches = YES;    // (same as above)
    }
    return result;
}

@end

Ensuite, dans ma méthode de viewDidLoad, je viens d'appeler

[UIScrollView swizzle];
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top