web-dev-qa-db-fra.com

UITableViewCell n'est pas désélectionné lors d'un balayage rapide

J'ai maintenant mis à jour trois de mes applications sur iOS 7, mais dans les trois cas, même si elles ne partagent aucun code, j'ai le problème suivant: si l'utilisateur glisse pour revenir en arrière dans le contrôleur de navigation (plutôt que d'appuyer sur le bouton Précédent) rapidement , la cellule restera dans l'état sélectionné.

Pour les trois applications, l'une utilise des cellules personnalisées créées par programme, une autre utilise des cellules personnalisées créées dans un storyboard et la troisième utilise des cellules par défaut dans une sous-classe très basique de UITableView, ainsi que dans un storyboard. Dans les trois cas, les cellules ne se désélectionnent pas. Si l'utilisateur glisse lentement ou appuie sur le bouton Précédent, il désélectionne normalement.

Cela ne se produit que dans mes applications iOS 7, les applications propres à Apple et les applications tierces mises à niveau pour iOS 7 semblent se comporter normalement (bien qu'avec de légères différences dans la rapidité avec laquelle les cellules sont désélectionnées).

Il doit y avoir quelque chose que je fais mal, mais je ne sais pas quoi?

59
Robert

Je traite le même problème en ce moment. L’échantillon UICatalog - d’Apple semble apporter la solution fâcheuse.

Cela ne me rend vraiment pas heureux du tout. Comme mentionné précédemment, il utilise [self.tableView deselectRowAtIndexPath:tableSelection animated:NO]; pour désélectionner la ligne actuellement sélectionnée.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // this UIViewController is about to re-appear, make sure we remove the current selection in our table view
    NSIndexPath *tableSelection = [self.tableView indexPathForSelectedRow];
    [self.tableView deselectRowAtIndexPath:tableSelection animated:NO];

    // some over view controller could have changed our nav bar tint color, so reset it here
    self.navigationController.navigationBar.tintColor = [UIColor darkGrayColor];
}

Je dois mentionner que l'exemple de code peut ne pas être iOS 7iOS 8iOS 9 Prêt pour iOS 10


Quelque chose qui me déroute vraiment est le UITableViewController Class Reference :

Lorsque la vue tabulaire est sur le point d’apparaître lors de son premier chargement, le fichier Le contrôleur de vue de table recharge les données de la vue de table. Il efface également sa sélection (avec ou sans animation, en fonction de la demande) chaque fois que la vue tableau est affichée. La UITableViewController La classe l'implémente dans la méthode de superclasse viewWillAppear:. Vous pouvez désactiver ce comportement en modifiant la valeur dans le clearsSelectionOnViewWillAppear propriété.

C’est exactement le comportement que j’attends… mais cela ne semble pas fonctionner. Ni pour vous ni pour moi. Nous devons vraiment utiliser la solution "sale" et le faire nous-mêmes.

36
Fabio Poloni

Cela a fonctionné mieux pour moi:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
}

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated];
}

J'ai même eu une bien meilleure disparition de la sélection alors que je glissais lentement dans le dos.

54
emreberge

La réponse de Fabio fonctionne bien mais ne donne pas le bon aspect si l'utilisateur glisse un petit peu puis change d'avis. Pour résoudre ce problème, vous devez enregistrer le chemin d'index sélectionné et le réinitialiser si nécessaire.

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.savedSelectedIndexPath = nil;
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.savedSelectedIndexPath) {
        [self.tableView selectRowAtIndexPath:self.savedSelectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.savedSelectedIndexPath = self.tableView.indexPathForSelectedRow;

    if (self.savedSelectedIndexPath) {
        [self.tableView deselectRowAtIndexPath:self.savedSelectedIndexPath animated:YES];
    }
}

Si vous utilisez un UITableViewController, veillez à désactiver la suppression intégrée: 

self.clearsSelectionOnViewWillAppear = NO;

et ajoutez la propriété pour savedSelectedIndexPath:

@property(strong, nonatomic) NSIndexPath *savedSelectedIndexPath;

Si vous avez besoin de faire cela dans quelques classes différentes, il peut être judicieux de le séparer en un assistant, par exemple comme je l'ai fait dans cet article: https://Gist.github.com/rhult/46ee6c4e8a862a8e66d4

19
Rhult

Cette solution anime la désélection de ligne avec le coordinateur de la transition (pour un VC rejet dirigé par l'utilisateur) et réapplique la sélection si l'utilisateur annule la transition. Adapté d'une solution de Caleb Davenport à Swift. Testé uniquement sur iOS 9. Testé comme fonctionnant à la fois avec la transition pilotée par l'utilisateur et avec le robinet à l'ancienne du bouton "Retour".

Dans la sous-classe UITableViewController:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // Workaround. clearsSelectionOnViewWillAppear is unreliable for user-driven (swipe) VC dismiss
    NSIndexPath *indexPath = self.tableView.indexPathForSelectedRow;
    if (indexPath && self.transitionCoordinator) {
        [self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
            [self.tableView deselectRowAtIndexPath:indexPath animated:animated];
        } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
            if ([context isCancelled]) {
                [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
            }
        }];
    }
}
9
nevan king

Après avoir rencontré ce problème moi-même aujourd’hui, j’ai découvert qu’il s’agissait apparemment d’un problème assez connu avec UITableView, sa prise en charge des transitions de navigation interactive est légèrement cassée. Les personnes derrière Castro ont publié une excellente analyse et solution à ce problème: http://blog.supertop.co/post/80781694515/viewmightappear

J'ai décidé d'utiliser leur solution qui prend également en compte les transitions annulées:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSIndexPath *selectedRowIndexPath = [self.tableView indexPathForSelectedRow];
    if (selectedRowIndexPath) {
        [self.tableView deselectRowAtIndexPath:selectedRowIndexPath animated:YES];
        [[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
            if ([context isCancelled]) {
                [self.tableView selectRowAtIndexPath:selectedRowIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
            }
        }];
    }
}
7
CodeStage

Vous pouvez essayer de définir 

self.clearsSelectionOnViewWillAppear = YES;

dans un UITableViewController ou

[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO];

in viewWillAppear, peut-être avant d'appeler [super viewWillAppear: animated]; Si votre UItableView est pas dans un UITableViewController, vous devez désélectionner les cellules manuellement:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES]; 
}
5
falsecrypt

Simple Swift 3/4 Réponse:

override func viewWillAppear(_ animated: Bool) {
    if tableView.indexPathForSelectedRow != nil {
        self.tableView.deselectRow(at: tableView.indexPathForSelectedRow! as IndexPath, animated: true)
    }
}
2
SteffenK

J'utilise

[tableView deselectRowAtIndexPath:indexPath animated:YES];

en fin de méthode

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

Comme ça:

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

{

    //doing something according to selected cell...

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
2
Evgeny

J'ai trouvé une solution très simple à ce problème qui permet simplement au comportement par défaut de fonctionner correctement. Je n’étais pas satisfait des solutions impliquant deselectRowAtIndexPath car l’effet visuel résultant était légèrement différent.

Tout ce que vous avez à faire pour empêcher ce comportement étrange est de recharger la table lorsque la vue est affichée:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.tableView reloadData];
}
1
quentez

Codestage answer , dans Swift 3. notifyWhenInteractionEnds est obsolète.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(true)

    if let indexPath = self.tableView.indexPathForSelectedRow {
        self.tableView.deselectRow(at: indexPath, animated: true)
        self.transitionCoordinator?.notifyWhenInteractionChanges { (context) in
            if context.isCancelled {
                self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
            }
        }
    }
}
1
Mike

Utilisation

[tableView deselectRowAtIndexPath:indexPath animated:YES];

code dans

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method

1
Pradhyuman sinh

Pour Swift 

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    guard let indexPath = tableView.indexPathForSelectedRow else{
        return
    }
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
0
Sofeda

La solution Rhult fonctionne parfaitement sur iOS 9.2. Voici l'implémentation dans Swift:

Déclarez une variable dans votre MasterViewController pour enregistrer le chemin IndexPath:

var savedSelectedIndexPath: NSIndexPath?

Ensuite, vous pouvez mettre le code dans une extension pour plus de clarté:

extension MasterViewController {
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.savedSelectedIndexPath = nil
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        if let indexPath = self.savedSelectedIndexPath {
            self.tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None)
        }
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.savedSelectedIndexPath = tableView.indexPathForSelectedRow
        if let indexPath = self.savedSelectedIndexPath {
            self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
        }
    }
}
0
Kymer

Vous n'appelez probablement pas la méthode viewWillAppear de la super vue ([super viewWillAppear: animated];) . Lorsque vous faites cela et que le paramètre UITableViewController clearsSelectionOnViewWillAppear vaut YES, les cellules sont désélectionnées dans viewWillAppear. 

0
Bess

Basé sur le code de Rhult , j'ai apporté quelques modifications. 

Cette implémentation permet à l’utilisateur d’annuler le glissement en arrière tout en conservant la sélection pour un autre glissement en arrière

@property(strong, nonatomic) NSIndexPath *savedSelectedIndexPath;


- (void)viewDidLoad {
   [super viewDidLoad];
   self.clearsSelectionOnViewWillAppear = NO;
}

-(void) viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
   self.savedSelectedIndexPath = nil;
}

-(void) viewWillDisappear:(BOOL)animated {
   [super viewWillDisappear:animated];
   if (self.savedSelectedIndexPath && ![self.tableView indexPathForSelectedRow]) {
       [self.tableView selectRowAtIndexPath:self.savedSelectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
   } else {
      self.savedSelectedIndexPath = [self.tableView indexPathForSelectedRow];
   }
}
0
Simon

Codestage fourni de loin la meilleure réponse , alors j'ai décidé de le convertir en Swift 2.

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)

        let selectedRowIndexPath = self.tableView.indexPathForSelectedRow
        if ((selectedRowIndexPath) != nil) {
            self.tableView.deselectRowAtIndexPath(selectedRowIndexPath!, animated: true)
            self.transitionCoordinator()?.notifyWhenInteractionEndsUsingBlock({ context in
                if (context.isCancelled()) {
                    self.tableView.selectRowAtIndexPath(selectedRowIndexPath, animated: false, scrollPosition: UITableViewScrollPosition.None)
                }
            })
        }
    }
0
AppreciateIt