Pouvez-vous animer un changement de hauteur sur un UITableViewCell lorsqu'il est sélectionné?

393

J'utilise un UITableViewdans mon application iPhone et j'ai une liste de personnes qui appartiennent à un groupe. Je voudrais que, lorsque l'utilisateur clique sur une personne particulière (sélectionnant ainsi la cellule), la cellule s'agrandisse pour afficher plusieurs contrôles d'interface utilisateur pour modifier les propriétés de cette personne.

Est-ce possible?

Mark Amery
la source

Réponses:

879

J'ai trouvé une solution VRAIMENT SIMPLE à cela comme un effet secondaire à un que UITableViewje travaillais .....

Stockez la hauteur des cellules dans une variable qui rapporte normalement la hauteur d'origine via le tableView: heightForRowAtIndexPath:, puis lorsque vous souhaitez animer un changement de hauteur, changez simplement la valeur de la variable et appelez cela ...

[tableView beginUpdates];
[tableView endUpdates];

Vous constaterez qu'il ne fait pas un rechargement complet mais suffit pour UITableViewsavoir qu'il doit redessiner les cellules, en saisissant la nouvelle valeur de hauteur pour la cellule .... et devinez quoi? Il anime le changement pour vous. Doux.

J'ai une explication plus détaillée et des exemples de code complets sur mon blog ... Animer UITableView Cell Height Change

Simon Lee
la source
6
C'est génial. Mais existe-t-il un moyen de contrôler la vitesse d'animation de la table?
Josh Kahane
7
Cela fonctionne, mais si je fais une cellule plus grande, disons de 44 à 74, puis que je la rajeunisse à 44, la ligne de séparation agit de manière totalement bizarre. Quelqu'un peut-il confirmer cela?
plaetzchen
46
C'est une solution bizarre, mais c'est ce que Apple recommande également dans la session "Mastering Table View" de la WWDC 2010. Je vais déposer un rapport de bug sur l'ajout de cela à la documentation car je viens de passer environ 2 heures à faire des recherches.
bpapa
5
J'ai essayé cette solution et cela ne fonctionne que parfois, lorsque vous appuyez sur une cellule, il y a 50% de chances de fonctionner. Quelqu'un a le même bug? C'est parce que iOS7?
Joan Cardona
7
Maintenant, il est également écrit dans la documentation officielle: You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell. developer.apple.com/library/ios/documentation/UIKit/Reference/…
Jaroslav
63

J'aime la réponse de Simon Lee. Je n'ai pas réellement essayé cette méthode, mais il semble que cela changerait la taille de toutes les cellules de la liste. J'espérais un changement de la cellule qui est tapée. Je l'ai fait un peu comme Simon mais avec juste une petite différence. Cela changera l'apparence d'une cellule lorsqu'elle sera sélectionnée. Et ça anime. Juste une autre façon de le faire.

Créez un int pour contenir une valeur pour l'index de cellule actuellement sélectionné:

int currentSelection;

Alors:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    selectedNumber = row;
    [tableView beginUpdates];
    [tableView endUpdates];
}

Alors:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    if ([indexPath row] == currentSelection) {
        return  80;
    }
    else return 40;


}

Je suis sûr que vous pouvez apporter des modifications similaires dans tableView: cellForRowAtIndexPath: pour changer le type de cellule ou même charger un fichier xib pour la cellule.

Comme ceci, le CurrentSelection commencera à 0. Vous auriez besoin de faire des ajustements si vous ne vouliez pas que la première cellule de la liste (à l'index 0) soit sélectionnée par défaut.

Mark A.
la source
2
Vérifiez le code attaché à mon message, j'ai fait exactement cela, doublez la hauteur d'une cellule lorsqu'elle est sélectionnée. :)
Simon Lee
8
"Je n'ai pas vraiment essayé cette méthode mais il semble que cela changerait la taille de toutes les cellules de la liste" - alors vous n'avez pas eu l'air très dur.
jamie
13
La sélection actuelle est déjà stockée dans tableView.indexPathForSelectedRow.
Nick
22

Ajouter une propriété pour garder une trace de la cellule sélectionnée

@property (nonatomic) int currentSelection;

Réglez-le sur une valeur sentinelle dans (par exemple) viewDidLoad, pour vous assurer que les UITableViewdéparts en position «normale»

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //sentinel
    self.currentSelection = -1;
}

Dans, heightForRowAtIndexPathvous pouvez définir la hauteur souhaitée pour la cellule sélectionnée

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    int rowHeight;
    if ([indexPath row] == self.currentSelection) {
        rowHeight = self.newCellHeight;
    } else rowHeight = 57.0f;
    return rowHeight;
}

Dans didSelectRowAtIndexPathvous enregistrez la sélection actuelle et enregistrez une hauteur dynamique, si nécessaire

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        // do things with your cell here

        // set selection
        self.currentSelection = indexPath.row;
        // save height for full text label
        self.newCellHeight = cell.titleLbl.frame.size.height + cell.descriptionLbl.frame.size.height + 10;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}

Dans didDeselectRowAtIndexPathremettre l'index de sélection à la valeur sentinelle et animer la cellule à sa forme normale

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {       
        // do things with your cell here

        // sentinel
        self.currentSelection = -1;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}
Joie
la source
Merci merci merci! J'ai ajouté un peu de code pour faire basculer la cellule. J'ai ajouté le code ci-dessous.
Septronic
14

reloadData n'est pas bon car il n'y a pas d'animation ...

Voici ce que j'essaie actuellement:

NSArray* paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];

Cela fonctionne presque bien. Presque. J'augmente la hauteur de la cellule, et parfois il y a un petit "hoquet" dans la vue du tableau lorsque la cellule est remplacée, comme si une position de défilement dans la vue du tableau était préservée, la nouvelle cellule (qui est la première cellule dans le tableau) se retrouve avec son décalage trop élevé et la vue de défilement rebondit pour le repositionner.

Lawrence
la source
Personnellement, j'ai trouvé que l'utilisation de cette méthode mais avec UITableViewRowAnimationNone fournit un résultat plus fluide mais toujours pas parfait.
Ron Srebro
11

Je ne sais pas ce que sont toutes ces choses sur l'appel successif de beginUpdates / endUpdates, vous pouvez simplement utiliser -[UITableView reloadRowsAtIndexPaths:withAnimation:]. Voici un exemple de projet .

axiixc
la source
Excellent, cela n'allonge pas mes vues de texte dans ma cellule de mise en page automatique. Mais il doit avoir un scintillement dans l'animation car l'option d'animation Aucune semble glitch lors de la mise à jour de la taille de la cellule.
h3dkandi du
10

J'ai résolu avec reloadRowsAtIndexPaths.

didSelectRowAtIndexPathJ'enregistre dans l'indexPath de la cellule sélectionnée et j'appelle reloadRowsAtIndexPathsà la fin (vous pouvez envoyer NSMutableArray pour la liste des éléments que vous souhaitez recharger).

Dans, heightForRowAtIndexPathvous pouvez vérifier si indexPath est dans la liste ou non des cellules expandIndexPath et envoyer la hauteur.

Vous pouvez vérifier cet exemple de base: https://github.com/ferminhg/iOS-Examples/tree/master/iOS-UITableView-Cell-Height-Change/celdascambiadetam C'est une solution simple.

j'ajoute une sorte de code si vous aide

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath
{
    if ([indexPath isEqual:_expandIndexPath])
        return 80;

    return 40;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Celda";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [cell.textLabel setText:@"wopwop"];

    return cell;
}

#pragma mark -
#pragma mark Tableview Delegate Methods

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSMutableArray *modifiedRows = [NSMutableArray array];
    // Deselect cell
    [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
    _expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];

    // This will animate updating the row sizes
    [tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
}
fermin
la source
3

Essayez ceci pour développer la ligne d'index:

@property (nonatomic) NSIndexPath *expandIndexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([indexPath isEqual:self.expandedIndexPath])
    return 100;

return 44;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandIndexPath]) {
    [modifiedRows addObject:self.expandIndexPath];
    self.expandIndexPath = nil;
} else {
    if (self.expandedIndexPath)
        [modifiedRows addObject:self.expandIndexPath];

    self.expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];
}

// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];

// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ViewControllerCellReuseIdentifier];
    cell.textLabel.text = [NSString stringWithFormat:@"I'm cell %ld:%ld", (long)indexPath.section, (long)indexPath.row];

return cell;
}
Nagarjun
la source
3
BOOL flag;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    flag = !flag;
    [tableView beginUpdates];
    [tableView reloadRowsAtIndexPaths:@[indexPath] 
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView endUpdates];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES == flag ? 20 : 40;
}
Roman Solodyashkin
la source
2

juste une note pour quelqu'un comme moi qui cherche à ajouter "Plus de détails" sur la cellule personnalisée.

[tableView beginUpdates];
[tableView endUpdates];

A fait un excellent travail, mais n'oubliez pas de "recadrer" la vue des cellules. Dans Interface Builder, sélectionnez votre cellule -> Affichage du contenu -> dans l'inspecteur des propriétés, sélectionnez " Clip subview "

Anthony Marchenko
la source
2

Voici une version plus courte de la réponse de Simons pour Swift 3. Permet également de basculer la sélection de la cellule

var cellIsSelected: IndexPath?


  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    cellIsSelected = cellIsSelected == indexPath ? nil : indexPath
    tableView.beginUpdates()
    tableView.endUpdates()
  }


  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if cellIsSelected == indexPath {
      return 250
    }
    return 65
  }
aBikis
la source
2

Swift 4 et au-dessus

ajoutez le code ci-dessous dans la méthode de délégué de ligne didselect de votre tableview

tableView.beginUpdates()
tableView.setNeedsLayout()
tableView.endUpdates()
midhun p
la source
1

Version rapide de la réponse de Simon Lee.

// MARK: - Variables 
  var isCcBccSelected = false // To toggle Bcc.



    // MARK: UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    // Hide the Bcc Text Field , until CC gets focused in didSelectRowAtIndexPath()
    if self.cellTypes[indexPath.row] == CellType.Bcc {
        if (isCcBccSelected) {
            return 44
        } else {
            return 0
        }
    }

    return 44.0
}

Puis dans didSelectRowAtIndexPath ()

  func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.tableView.deselectRowAtIndexPath(indexPath, animated: true)

    // To Get the Focus of CC, so that we can expand Bcc
    if self.cellTypes[indexPath.row] == CellType.Cc {

        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? RecipientTableViewCell {

            if cell.tag == 1 {
                cell.recipientTypeLabel.text = "Cc:"
                cell.recipientTextField.userInteractionEnabled = true
                cell.recipientTextField.becomeFirstResponder()

                isCcBccSelected = true

                tableView.beginUpdates()
                tableView.endUpdates()
            }
        }
    }
}
ioopl
la source
1

Oui c'est possible.

UITableView a une méthode déléguée didSelectRowAtIndexPath

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [UIView animateWithDuration:.6
                          delay:0
         usingSpringWithDamping:UIViewAnimationOptionBeginFromCurrentState
          initialSpringVelocity:0
                        options:UIViewAnimationOptionBeginFromCurrentState animations:^{

                            cellindex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
                            NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
                            [violatedTableView beginUpdates];
                            [violatedTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
                            [violatedTableView endUpdates];
                        }
                     completion:^(BOOL finished) {
    }];
}

Mais dans votre cas, si l'utilisateur fait défiler et sélectionne une cellule différente, vous devez avoir la dernière cellule sélectionnée pour réduire et développer les reloadRowsAtIndexPaths:appels de cellule actuellement sélectionnés, heightForRowAtIndexPath:alors gérez-les en conséquence.

Koushik
la source
0

Voici mon code de UITableViewsous-classe personnalisée , qui se développe UITextViewau niveau de la cellule du tableau, sans rechargement (et perte du focus clavier):

- (void)textViewDidChange:(UITextView *)textView {
    CGFloat textHeight = [textView sizeThatFits:CGSizeMake(self.width, MAXFLOAT)].height;
    // Check, if text height changed
    if (self.previousTextHeight != textHeight && self.previousTextHeight > 0) {
        [self beginUpdates];

        // Calculate difference in height
        CGFloat difference = textHeight - self.previousTextHeight;

        // Update currently editing cell's height
        CGRect editingCellFrame = self.editingCell.frame;
        editingCellFrame.size.height += difference;
        self.editingCell.frame = editingCellFrame;

        // Update UITableView contentSize
        self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height + difference);

        // Scroll to bottom if cell is at the end of the table
        if (self.editingNoteInEndOfTable) {
            self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y + difference);
        } else {
            // Update all next to editing cells
            NSInteger editingCellIndex = [self.visibleCells indexOfObject:self.editingCell];
            for (NSInteger i = editingCellIndex; i < self.visibleCells.count; i++) {
                UITableViewCell *cell = self.visibleCells[i];
                CGRect cellFrame = cell.frame;
                cellFrame.origin.y += difference;
                cell.frame = cellFrame;
            }
        }
        [self endUpdates];
    }
    self.previousTextHeight = textHeight;
}
Vitaliy Gozhenko
la source
0

J'ai utilisé la réponse impressionnante de @ Joy, et cela a parfaitement fonctionné avec ios 8.4 et XCode 7.1.1.

Dans le cas où vous cherchez à faire basculer votre cellule, j'ai changé le -tableViewDidSelect comme suit:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//This is the bit I changed, so that if tapped once on the cell, 
//cell is expanded. If tapped again on the same cell, 
//cell is collapsed. 
    if (self.currentSelection==indexPath.row) {
        self.currentSelection = -1;
    }else{
        self.currentSelection = indexPath.row;
    }
        // animate
        [tableView beginUpdates];
        [tableView endUpdates];

}

J'espère que tout cela vous a aidé.

Septronic
la source
0

Vérifiez cette méthode après iOS 7 et versions ultérieures.

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewAutomaticDimension;
}

Des améliorations ont été apportées à cela dans iOS 8. Nous pouvons le définir comme propriété de la vue de table elle-même.

Govind
la source
0

Version rapide de la réponse de Simon Lee :

tableView.beginUpdates()
tableView.endUpdates()

N'oubliez pas que vous devez modifier les propriétés de hauteur AVANT endUpdates() .

Tamás Sengel
la source
0

Contributions -

tableView.beginUpdates () tableView.endUpdates () ces fonctions n'appelleront pas

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}

Mais, si vous le faites, tableView.reloadRows (à: [selectedIndexPath! As IndexPath], avec: .none)

Il appellera func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} cette fonction.

sRoy
la source
-1

Je viens de résoudre ce problème avec un petit hack:

static int s_CellHeight = 30;
static int s_CellHeightEditing = 60;

- (void)onTimer {
    cellHeight++;
    [tableView reloadData];
    if (cellHeight < s_CellHeightEditing)
        heightAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(onTimer) userInfo:nil repeats:NO] retain];
}

- (CGFloat)tableView:(UITableView *)_tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
        if (isInEdit) {
            return cellHeight;
        }
        cellHeight = s_CellHeight;
        return s_CellHeight;
}

Lorsque j'ai besoin d'augmenter la hauteur des cellules, je définis isInEdit = YESet appelle la méthode [self onTimer]et elle anime la croissance des cellules jusqu'à ce qu'elle atteigne la valeur s_CellHeightEditing :-)

Dzamir
la source
Dans le simulateur fonctionne très bien, mais dans le matériel iPhone est en retard. Avec un retard de 0,05 et avec une augmentation de 5 unités de cellHeight, c'est beaucoup mieux, mais rien de tel que CoreAnimation
Dzamir
1
Confirmez, avant de poster..la prochaine fois.
ajay_nasa
-2

Récupère le chemin d'index de la ligne sélectionnée. Rechargez la table. Dans la méthode heightForRowAtIndexPath de UITableViewDelegate, définissez la hauteur de la ligne sélectionnée sur une hauteur différente et pour les autres, retournez la hauteur de ligne normale

lostInTransit
la source
2
-1, ne fonctionne pas. L'appel [table reloadData]entraîne un changement instantané de la hauteur, plutôt qu'une animation.
Mark Amery