web-dev-qa-db-fra.com

UIScrollview with UIButtons - comment recréer un tremplin?

J'essaie de créer une interface semblable à un tremplin au sein de mon application. J'essaie d'utiliser les boutons UIB ajoutés à un UIScrollView. Le problème que je rencontre est lié au fait que les boutons ne transmettent aucun contact à UIScrollView. Si j'essaie de glisser/glisser et que j'appuie sur le bouton, cela ne sert à rien, mais si je déplace l'espace boutons cela fonctionnera. Les boutons cliquent/fonctionnent si je les touche.

Existe-t-il une propriété ou un paramètre obligeant le bouton à envoyer les événements tactiles à son parent (superview)? Les boutons doivent-ils être ajoutés à quelque chose avant d’être ajoutés à UIScrollView?

Voici mon code:

//init scrolling area
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 480, 480)];
scrollView.contentSize = CGSizeMake(480, 1000);
scrollView.bounces = NO;
scrollView.delaysContentTouches = NO;

//create background image
UIImageView *rowsBackground = [[UIImageView alloc] initWithImage:[self scaleAndRotateImage:[UIImage imageNamed:@"mylongbackground.png"]]];
rowsBackground.userInteractionEnabled = YES;

//create button
UIButton *btn = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
btn.frame = CGRectMake(100, 850, 150, 150);
btn.bounds = CGRectMake(0, 0, 150.0, 150.0);
[btn setImage:[self scaleAndRotateImage:[UIImage imageNamed:@"basicbutton.png"]] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];

//add "stuff" to scrolling area
[scrollView addSubview:rowsBackground];
[scrollView addSubview:btn];

//add scrolling area to cocos2d
//this is just a UIWindow
[[[Director sharedDirector] openGLView] addSubview:scrollView];

//mem-mgmt
[rowsBackground release];
[btn release];
[scrollView release];
34
Patrick

Pour que UIScrollView puisse déterminer la différence entre un clic qui passe dans la ou les vues de contenu et un contact qui se transforme en un glissement ou un pincement, il doit retarder le toucher et voir si votre doigt s'est déplacé pendant ce délai. En définissant delaysContentTouches sur NO dans votre exemple ci-dessus, vous empêchez cela de se produire. Par conséquent, la vue de défilement transmet toujours le toucher au bouton au lieu de l’annuler s’il s’avère que l’utilisateur effectue un geste de balayage. Essayez de régler delaysContentTouches à YES.

Il peut également être judicieux d'ajouter structurellement toutes les vues à héberger dans votre vue de défilement à une vue de contenu commune et de n'utiliser que cette vue comme sous-vue de la vue de défilement.

28
Brad Larson

La solution qui a fonctionné pour moi inclut:

  1. Régler canCancelContentTouches dans UIScrollView à YES
  2. Extension de UIScrollView pour remplacer touchesShouldCancelInContentView:(UIView *)view pour renvoyer YES lorsque view est une UIButton

Selon la documentation, touchesShouldCancelInContentView retourne "YES pour annuler d'autres messages tactiles à afficher, NO pour continuer à recevoir ces messages. La valeur renvoyée par défaut est YES si view n'est pas un objet UIControl; sinon, il renvoie NO."

Étant donné que UIButton est une UIControl, l'extension est nécessaire pour que canCancelContentTouches prenne effet, ce qui active le défilement. 

44
Roman Kishchenko

J'ai le même cas qu'un certain nombre de boutons sur un UIScrollView et je souhaite les faire défiler. Au début, j'ai sous-classé UIScrollView et UIButton. Cependant, j'ai remarqué que mon sous-groupe UIScrollView ne recevait pas l'événement touchesEnded. J'ai donc changé pour un sous-type UIButton.


@interface MyPhotoButton : UIButton {
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end

@implementation MyPhotoButton

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesMoved:touches withEvent:event];
    [self setEnabled:NO];
    [self setSelected:NO];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    [self setEnabled:YES];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    [self setEnabled:YES];
}

@end
5
Richard Chen

UIScrollView gère un grand nombre d'événements lui-même. Vous devez gérer touchesDidEnd et cliquer sur test pour les boutons à l'intérieur de UIScrollView manuellement.

1
Rog

OK voici ta réponse:

Sous-classe UIButton. (REMARQUE: appelez [super ....] au début de chaque substitution.

  • Ajouter une propriété. Un de type BOOL (Appelé enableToRestore)
  • Ajouter une propriété. Un de type CGPoint .__ (appelé startTouchPosition)
  • dans awakeFromNib et initWithFrame, définissez enableToRestore sur isEnabledproperty)
  • Remplacez "touchesBegan: withEvent:" Pour enregistrer le début de la position tactile .
  • Remplacez "touchesMoved: withEvent:" pour Vérifiez s'il existe un mouvement horizontal
  • Si OUI, définissez Activé sur NON et Sélectionné sur NON.

Exemple de code:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    UITouch *touch = [touches anyObject];

    [super touchesBegan:touches withEvent:event];
    [self setStartTouchPosition:[touch locationInView:self]];
}


//
// Helper Function
//
- (BOOL)isTouchMovingHorizontally:(UITouch *)touch 
{
    CGPoint currentTouchPosition = [touch locationInView:self];
    BOOL      rValue = NO;

    if (fabsf([self startTouchPosition].x - currentTouchPosition.x) >= 2.0) 
    {
        rValue = YES;
    }

    return (rValue);
}

//
// This is called when the finger is moved.  If the result is a left or right
// movement, the button will disable resulting in the UIScrollView being the
// next responder.  The parrent ScrollView will then re-enable the button
// when the finger event is ended of cancelled.
//
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
{
    [super touchesMoved:touches withEvent:event];
    if ([self isTouchMovingHorizontally:[touches anyObject]]) 
    {
        [self setEnabled:NO];
        [self setSelected:NO];
    } 
}

Cela activera UIScrollView. 

Sous-classe UIScrollView. (REMARQUE: appelez [super ....] au début de chaque substitution.

  • Remplacer les deux "touchesEnded: withEvent:" et "touchesAnnulées: withEvent:"
  • Dans le remplacement, réinitialisez l'indicateur activé de toutes les sous-vues (et de leurs sous-vues).
  • NOTE: Utilisez une catégorie et ajoutez la méthode à UIView:

.

- (void) restoreAllEnables
{
    NSArray   *views = [self subviews];

    for (UIView *aView in views)
    {
        if ([aView respondsToSelector:@selector(restoreEnable)])
        {
            [aView restoreEnable];
        }
    }
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    [self restoreAllEnables];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    [self restoreAllEnables];
}
  • Dans la catégorie:

.

-(void) restoreEnable
{
    NSArray   *views = [self subviews];

    if ([self respondsToSelector:@selector(enableToRestore)])
    {
        [self setEnabled:[self enableToRestore]];
    }

    for (UIView *aView in views)
    {
        if ([aView respondsToSelector:@selector(restoreEnable)])
        {
            [aView restoreEnable];
        }
    }
}

EDIT Remarque: Je n’ai jamais obtenu la réponse 3 au travail . boutons. La définition de setDelaysContentTouches: YES a un impact important (150 ms) sur le temps de réponse des boutons et rend impossible tout contact léger et rapide.

1
Steven Noyes

Selon mon expérience, la première réponse, c’est-à-dire le simple fait de placer delaysContentTouches à YES, ne change rien au problème. Les boutons ne transmettent toujours pas les résultats du suivi à la vue de défilement. La troisième réponse est à la fois simple et très utilisable. Merci sieroaoj!

Cependant, pour que la troisième réponse fonctionne, vous devez également définir delaysContentTouches sur YES. Sinon, la méthode touchesEnded sera également appelée pour le suivi dans la vue. Par conséquent, je pourrais résoudre le problème en:

  1. Remplacez le bouton par un simple personnalisé UIView
  2. Placez le drapeau "userInterationEnable = yes;" sur la méthode init
  3. Dans la vue, remplacez la méthode UIResponder "touchesEnded" ici vous pouvez déclencher l'action que vous

Quatrième. définir delaysContentTouches à YES

0
Captain Fim

Une autre façon est: 
1. Remplacer le bouton par un simple UIView personnalisé
2. Placez le drapeau "userInterationEnable = yes;" sur la méthode init
3. Dans la vue, remplacez la méthode UIResponder "touchesEnded". Vous pouvez alors déclencher l'action souhaitée sous forme de bouton.

0
user41806