web-dev-qa-db-fra.com

UITableView dans UIScrollView ne reçoit pas le premier tap après le défilement

Bref

J'ai un problème avec un UITableView dans un UIScrollView. Lorsque je fais défiler la scrollView externe, la table ne reçoit pas l'événement willSelect/didSelect au premier toucher, mais au second. Ce qui est encore plus étrange, c'est que la cellule elle-même reçoit les contacts et l'état mis en surbrillance, même lorsque la variable delegate ne le fait pas.

Explication détaillée

Ma hiérarchie de vues:

UIView
  - UIScrollView  (outerscroll)
      - Some other views and buttons
      - UITableView (tableView)

À l'intérieur de la vue de défilement, j'ai quelques vues supplémentaires qui sont développées/fermées dynamiquement. La vue sous forme de tableau doit être "réparée" par-dessus, avec quelques autres éléments de la vue. C’est pourquoi j’ai créé cette présentation, qui me permet de déplacer facilement les éléments de la même manière que celle recommandée par Apple en utilisant des transformations lorsque le parchemin arrive.

La vue de table est transformée avec un effet de traduction lorsque le défilement extérieur se déplace comme suit:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.outerScrollView) {

        CGFloat tableOffset = scrollView.contentOffset.y - self.fixedHeaderFrame.Origin.y;
        if (tableOffset > 0) {
            self.tableView.contentOffset = CGPointMake(0, tableOffset);
            self.tableView.transform = CGAffineTransformMakeTranslation(0, tableOffset);
        }
        else {
            self.tableView.contentOffset = CGPointMake(0, 0);
            self.tableView.transform = CGAffineTransformIdentity;
        }

        // Other similar transformations are done here, but not involving the table

}

Dans ma cellule, si j'implémente ces méthodes:

- (void)setSelected:(BOOL)selected {
    [super setSelected:selected];
    if (selected) {
        NSLog(@"selected");
    }
}

- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
    [super setHighlighted:highlighted animated:animated];
    if (highlighted) {
        NSLog(@"highlighted");
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    NSLog(@"touchesBegan");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    NSLog(@"touchesEnded");
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    NSLog(@"touchesCancelled");
}

Y peut voir cette sortie en cas d'échec (première frappe):

2014-02-10 13:04:40.940 MyOrderApp[5588:70b] highlighted 
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] touchesBegan 
2014-02-10 13:04:40.978 MyOrderApp[5588:70b] touchesEnded

Et celui-ci quand ça marche (deuxième tap):

2014-02-10 13:05:30.359 MyOrderApp[5588:70b] highlighted 
2014-02-10 13:05:30.360 MyOrderApp[5588:70b] touchesBegan 
2014-02-10 13:05:30.487 MyOrderApp[5588:70b] touchesEnded 
2014-02-10 13:05:30.498 MyOrderApp[5588:70b] expanded

Aucun autre changement d'image, animation ou autre interaction de vue n'est effectué entre le premier et le deuxième tapotement. De plus, le bogue n'apparaît que lorsque le défilement est important, mais avec un défilement de quelques pixels seulement, tout continue à fonctionner correctement.

J'ai également essayé de changer certaines propriétés, mais sans succès. Certaines des choses que j'ai faites:

  • Supprimer userInteractionEnabled des vues autres que le défilement et la table
  • Ajoutez un appel à setNeedsLayout sur la table, le défilement et la vue principale lorsque scrollViewDidScroll apparaît.
  • Supprimer les transformations de la table (cela se produit toujours)

J'ai vu des commentaires sur le comportement inattendu de l'intégration de UITableViews dans UIScrollViews, mais je ne vois pas un tel avertissement dans la documentation officielle d'Apple. Je m'attends donc à ce que cela fonctionne. 

L'application est uniquement iOS7 +.

Des questions

Quelqu'un at-il rencontré des problèmes similaires? Pourquoi est-ce et comment puis-je le résoudre? Je pense pouvoir intercepter le geste de tapoter sur la cellule et le transmettre à un délégué personnalisé ou similaire, mais j'aimerais que la table reçoive les événements appropriés} et ainsi ma UITableViewDelegate le reçoit comme prévu .

Mises à jour

  • J'ai essayé de désactiver la réutilisation des cellules comme suggéré dans un commentaire, mais cela se passe toujours de la même manière.
30
Angel G. Olloqui

Dans Documentation Apple , vous ne devez pas incorporer une UITableViewinformations à une UIScrollView.

Important: vous ne devez pas incorporer d'objets UIWebView ou UITableView dans Objets UIScrollView. Si vous le faites, un comportement inattendu peut en résulter parce que les événements tactiles pour les deux objets peuvent être mélangés et erronés traité.

Votre problème est vraiment lié à ce que votre UIScrollView fait.

Mais s'il ne s'agit que de masquer la table si nécessaire (c'était mon cas), vous pouvez simplement déplacer UITableView dans sa vue d'ensemble.

J'ai écrit un petit exemple ici: https://github.com/rvirin/SoundCloud/

22
Rémy Virin

laissez la propriété scrollEnabled de la UITableView intérieure définie sur YES. Ceci permet à la UITableView intérieure de gérer correctement les touches de défilement associées à la UIScrollView.

19
Tom Abraham

J'ai rencontré ce même problème et trouvé une solution! 

Vous devez définir la valeur delaysTouchesBegan sur true dans votre vue de défilement pour que celle-ci envoie son geste de défilement ayant échoué (c’est-à-dire le toucher) à ses enfants. 

var delaysTouchesBegan: Bool -Une valeur booléenne déterminant si le destinataire retarde l'envoi des contacts dans une phase de début de sa vue.

Lorsque la valeur de la propriété est YES, la fenêtre suspend la livraison de touchez les objets de la phase UITouchPhaseBegan dans la vue. Si la reconnaissance de geste reconnaît ensuite son geste, ces contacts les objets sont jetés. Si la reconnaissance de geste, cependant, ne le fait pas reconnaît son geste, la fenêtre livre ces objets à la vue dans une toucheBegan: withEvent: message (et éventuellement un suivi touchesMoved: withEvent: message pour l’informer des emplacements actuels de la touche ).

https://developer.Apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/index.html#//Apple_ref/occ/instp/UIGestureRecognizer/delaysTouchesBegan

Mais il y a un piège ... ça ne marche pas si vous le faites directement sur le scrollview!

// Does NOT work
self.myScrollview.delaysTouchesBegan = true

Apparemment, il s’agit d’un bogue iOS dans lequel la définition de cette propriété ne fonctionne pas (merci Apple). Cependant, il existe une solution simple: définissez la propriété directement sur le geste panoramique de la vue de défilement. Effectivement, cela a parfaitement fonctionné pour moi:

// This works!!
self.myScrollview.panGestureRecognizer.delaysTouchesBegan = true
13
Oren

Il semble que votre UiTableView ne reconnaisse pas votre tap. Avez-vous essayé d'utiliser cela:

- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer             *)otherGestureRecognizer
{
    if ([otherGestureRecognizer.view isKindOfClass:[UITableView class]]) {
        return YES;
    }
    return NO;
}

Note d'Apple: 

appelé lorsque la reconnaissance de l'un des gestureRecognizer ou otherGestureRecognizer serait bloquée par l'autre. renvoyer YES pour permettre aux deux de se reconnaître simultanément. l'implémentation par défaut renvoie NO (par défaut, deux gestes ne peuvent être reconnus simultanément)

remarque: le retour de YES est garanti pour permettre la reconnaissance simultanée. renvoyer NON n'est pas garanti pour empêcher la reconnaissance simultanée, car le délégué de l'autre geste peut renvoyer OUI

J'espère que ça va aider.

3
Nicolas Bonnet

Les identificateurs de mouvements ne fonctionneront pas correctement pour deux vues ou sous-classes de défilement incorporées.

Essayez une solution de contournement:

  1. Utilisez les éléments transparents, personnalisés et superposés de la cellule UIButton avec la balise ou la sous-classe UIButton appropriée, ajoutez une propriété de chemin d'index et remplacez-la chaque fois dans la cellule réutilisée. 

  2. Ajoutez ce bouton en tant que propriété à votre cellule personnalisée.

  3. Ajoutez une cible pour la UIControlEvent souhaitée (une ou plusieurs) qui pointe vers votre classe d’adoption de protocole UITableViewDelegate.

  4. Désactiver la sélection dans IB et gérer manuellement la sélection à partir du code.

Cette solution nécessite une attention particulière dans les cas de sélection unique/multiple.

3
Wladek Surala

J'ai rencontré un UITableView avec scrollEnabled non dans un code UIScrollView. Je n'ai pas été en mesure de changer facilement la hiérarchie existante, ni d'activer le défilement, mais j'ai trouvé la solution de contournement suivante pour le problème du premier contact:

@interface YourOwnTableView : UITableView
@end

@implementation YourOwnTableView

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

    [super touchesCancelled:touches withEvent:event];

    // Note that this is a hack and it can stop working at some point.
    // Looks like a table view with scrollEnabled being NO does not handle cancellation cleanly,
    // so let's repeat begin/end touch sequence here hoping it'll reset its own internal state properly
    // but won't trigger cell selection (the touch passed is in its cancelled phase, perhaps there is a part
    // of code inside which actually checks it)
    [super touchesBegan:touches withEvent:event];
    [super touchesEnded:touches withEvent:event];
}

@end

Encore une fois, ceci est juste une solution de contournement qui fonctionne dans mon cas spécifique. Avoir une vue de tableau dans une vue de défilement est toujours une mauvaise chose.

2
aleh

J'ai le même problème avec UITableView imbriqué et j'ai trouvé une solution pour cela:

innerTableView.scrollEnabled = YES;
innerTableView.alwaysBounceVertical = NO;

Vous devez définir la hauteur de la vue de table interne pour qu'elle corresponde à la hauteur totale de ses cellules afin qu'elle ne défile pas lorsque l'utilisateur fait défiler la vue externe.

J'espère que cela t'aides.

1
Duc Minh Trinh

Mon erreur a été d'implémenter la méthode des délégués:

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath

au lieu de celui que je voulais implémenter: 

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

Par conséquent, l'appel n'est effectué que sur la deuxième cellule sélectionnée, car c'est à ce moment-là que la première cellule serait sélectionnée de. Super erreur commise à l'aide de la saisie semi-automatique. Juste une pensée pour ceux d'entre vous qui peuvent errer ici sans se rendre compte que vous avez aussi fait la même erreur.

1
LunaCodeGirl

Je vous recommande de rechercher des options telles que ne pas laisser votre cellule en surbrillance lorsque vous faites défiler la vue de défilement extérieure, qui est très facile à gérer et qui est recommandée. Vous pouvez le faire simplement en prenant un booléen et en le basculant dans la méthode ci-dessous

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
1
Rajesh

Scrollview tente de déterminer si l'intention de l'utilisateur est de faire défiler l'écran ou non, ce qui retarde donc le contact initial. Vous pouvez désactiver cette option en définissant delaysContentTouches sur NO.

1
Stepan Hruda
  1. Déposez un bouton UIB sur votre UITableViewCell et créez le point de vente en tant que "btnRowSelect".
  2. Dans votre contrôleur de vue, mettez ce code dans cellForRowAtIndexPath

    cell.btnRowSelect.tag = indexPath.row
    cell.btnRowSelect.addTarget(self, action: Selector("rowSelect:"), forControlEvents: .TouchUpInside)
    
  3. Ajoutez cette fonction à votre viewController ainsi

    func rowSelect (sender:UIButton) {
    // "sendet.tag" give you the selected row 
    // do whatever you want to do in didSelectRowAtIndexPath
    }
    

Cette fonction "rowSelect" fonctionnera comme didSelectRowAtIndexPath où vous obtenez la ligne "indexPath.row" comme "sender.tag"

0
shubham

Que ça, si vue table tactile ça va fonctionner correctement. également avec vue de défilement dans le même contrôleur de vue aussi.

tableview.scrollEnabled = true;
0
Ravi H Malviya

Comme d'autres réponses le disent, vous ne devriez pas placer une tableview dans une scrollview. Une UITableView hérite de toute façon de UIScrollView, alors je suppose que c'est là que les choses deviennent confuses. Ce que je fais toujours dans cette situation est:

1) Sous-classe UITableViewController et incluez une propriété UIView * headView.

2) Dans le parent UIViewController, créez tous les éléments principaux d’un conteneur UIView.

3) Initialisez votre UITableView personnalisé et ajoutez la vue de la tableView au contrôleur de vue en taille réelle

[self.view addSubview: self.myTableView.view];

4) Définissez le headView pour qu’il soit votre gubbins UIView

self.tableView.headView = myHeadViewGubbins.

5) Dans la méthode tableViewController

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger *)section;

Faire:

if ( section == 0 ) {
    return self.headView;
}

Maintenant, vous avez une vue de table avec un tas d'autres shizzle au sommet.

Prendre plaisir!

0
Martin

J'ai le même problème, puis référez-vous à "Imbrication des vues de défilement" comme l'a dit lxx.

https://developer.Apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html

Vous trouverez un exemple de défilement transversal dans l'application Stocks. La vue de dessus est une vue de tableau, mais la vue de dessous est une vue de défilement horizontal configurée en utilisant le mode de pagination. Tandis que deux de ses trois sous-vues sont des vues personnalisées, la troisième vue (qui contient les articles de presse) est une UITableView (une sous-classe de UIScrollView) qui est une sous-vue de la vue de défilement horizontal. Une fois que vous avez fait défiler horizontalement la vue des actualités, vous pouvez faire défiler son contenu verticalement.

C'est du travail

0
Davin Kao