Utilisation de la disposition automatique dans UITableView pour des dispositions de cellule dynamiques et des hauteurs de ligne variables


1501

Comment utilisez-vous la disposition automatique dans les UITableViewCells dans une vue de tableau pour laisser le contenu et les sous-vues de chaque cellule déterminer la hauteur de ligne (elle-même / automatiquement), tout en conservant des performances de défilement fluides?


Voici l'exemple de code dans swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

Pour comprendre la plupart des réponses concernant le fait d'avoir un UILabel multiligne, vous avez besoin d'une compréhension complète de la façon contentSizeet du preferredMaxLayoutWidthtravail. Voyez ici . Cela étant dit, si vous configurez correctement vos contraintes, vous ne devriez pas en avoir besoin preferredMaxLayoutWidthet, en fait, cela peut créer des résultats inattendus.
Honey

Réponses:


2389

TL; DR: Vous n'aimez pas lire? Accédez directement aux exemples de projets sur GitHub:

Description conceptuelle

Les 2 premières étapes ci-dessous sont applicables quelles que soient les versions d'iOS pour lesquelles vous développez.

1. Configurer et ajouter des contraintes

Dans votre UITableViewCellsous-classe, ajoutez des contraintes de sorte que les sous-vues de la cellule aient leurs bords épinglés aux bords de contentView de la cellule (surtout aux bords supérieur ET inférieur). REMARQUE: n'épinglez pas les sous-vues à la cellule elle-même; seulement aux cellules contentView! Laissez la taille de contenu intrinsèque de ces sous-vues déterminer la hauteur de la vue de contenu de la cellule de vue tableau en vous assurant que la résistance à la compression de contenu et les contraintes de contournement de contenu dans la dimension verticale de chaque sous-vue ne sont pas remplacées par les contraintes de priorité plus élevée que vous avez ajoutées. ( Hein? Cliquez ici. )

N'oubliez pas que l'idée est d'avoir les sous-vues de la cellule connectées verticalement à la vue de contenu de la cellule afin qu'elles puissent «exercer une pression» et étendre la vue de contenu pour les adapter. En utilisant un exemple de cellule avec quelques sous-vues, voici une illustration visuelle de ce à quoi certaines (pas toutes!) De vos contraintes devraient ressembler:

Exemple d'illustration des contraintes sur une cellule de vue de table.

Vous pouvez imaginer que plus de texte est ajouté à l'étiquette de corps multiligne dans l'exemple de cellule ci-dessus, il devra s'agrandir verticalement pour s'adapter au texte, ce qui forcera effectivement la cellule à augmenter en hauteur. (Bien sûr, vous devez obtenir les bonnes contraintes pour que cela fonctionne correctement!)

Obtenir vos bonnes contraintes est certainement la partie la plus difficile et la plus importante pour obtenir des hauteurs de cellule dynamiques avec Auto Layout. Si vous faites une erreur ici, cela pourrait empêcher tout le reste de fonctionner - alors prenez votre temps! Je recommande de configurer vos contraintes dans le code car vous savez exactement quelles contraintes sont ajoutées où, et il est beaucoup plus facile de déboguer en cas de problème. L'ajout de contraintes dans le code peut être aussi simple et nettement plus puissant que Interface Builder utilisant des ancres de mise en page ou l'une des fantastiques API open source disponibles sur GitHub.

  • Si vous ajoutez des contraintes dans le code, vous devez le faire une fois depuis la updateConstraintsméthode de votre sous-classe UITableViewCell. Notez que cela updateConstraintspeut être appelé plus d'une fois, donc pour éviter d'ajouter les mêmes contraintes plus d'une fois, assurez-vous d'encapsuler votre code d'ajout de contrainte updateConstraintsdans une vérification pour une propriété booléenne telle que didSetupConstraints(que vous définissez sur YES après avoir exécuté votre contrainte -ajout de code une fois). En revanche, si vous disposez d'un code qui met à jour les contraintes existantes (telles que l'ajustement de la constantpropriété sur certaines contraintes), placez-le dans updateConstraintsmais en dehors de la vérification pour didSetupConstraintsqu'il puisse s'exécuter à chaque appel de la méthode.

2. Déterminer les identificateurs uniques de réutilisation des cellules de vue tabulaire

Pour chaque ensemble unique de contraintes dans la cellule, utilisez un identifiant de réutilisation de cellule unique. En d'autres termes, si vos cellules ont plus d'une disposition unique, chaque disposition unique doit recevoir son propre identifiant de réutilisation. (Un bon indice que vous devez utiliser un nouvel identifiant de réutilisation est lorsque votre variante de cellule a un nombre différent de sous-vues ou que les sous-vues sont organisées de manière distincte.)

Par exemple, si vous affichez un e-mail dans chaque cellule, vous pouvez avoir 4 mises en page uniques: messages avec uniquement un objet, messages avec un objet et un corps, messages avec un objet et une pièce jointe photo et messages avec un objet, corps et photo. Chaque disposition a des contraintes complètement différentes requises pour y parvenir, donc une fois que la cellule est initialisée et que les contraintes sont ajoutées pour l'un de ces types de cellules, la cellule doit obtenir un identifiant de réutilisation unique spécifique à ce type de cellule. Cela signifie que lorsque vous retirez une cellule de la réutilisation, les contraintes ont déjà été ajoutées et sont prêtes à être utilisées pour ce type de cellule.

Notez qu'en raison des différences de taille de contenu intrinsèque, les cellules avec les mêmes contraintes (type) peuvent toujours avoir des hauteurs différentes! Ne confondez pas des dispositions fondamentalement différentes (différentes contraintes) avec différents cadres de vue calculés (résolus à partir de contraintes identiques) en raison de différentes tailles de contenu.

  • N'ajoutez pas de cellules avec des ensembles de contraintes complètement différents au même pool de réutilisation (c'est-à-dire utilisez le même identifiant de réutilisation), puis essayez de supprimer les anciennes contraintes et de configurer de nouvelles contraintes à partir de zéro après chaque retrait de file d'attente. Le moteur de mise en page automatique interne n'est pas conçu pour gérer les changements de contraintes à grande échelle, et vous verrez d'énormes problèmes de performances.

Pour iOS 8 - Cellules à dimensionnement automatique

3. Activer l'estimation de la hauteur de ligne

Pour activer les cellules de vue de tableau à dimensionnement automatique, vous devez définir la propriété rowHeight de la vue de tableau sur UITableViewAutomaticDimension. Vous devez également affecter une valeur à la propriété estimationRowHeight. Dès que ces deux propriétés sont définies, le système utilise la disposition automatique pour calculer la hauteur réelle de la ligne

Apple: Utilisation de cellules d'affichage de table à dimensionnement automatique

Avec iOS 8, Apple a internalisé une grande partie du travail que vous deviez auparavant implémenter avant iOS 8. Pour permettre au mécanisme de cellule à dimensionnement automatique de fonctionner, vous devez d'abord définir la rowHeightpropriété de la vue de table sur la constante UITableViewAutomaticDimension. Ensuite, vous devez simplement activer l'estimation de la hauteur de ligne en définissant la estimatedRowHeightpropriété de la vue de table sur une valeur différente de zéro, par exemple:

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is

Cela permet de fournir à la vue de table une estimation / espace réservé temporaire pour les hauteurs de ligne des cellules qui ne sont pas encore à l'écran. Ensuite, lorsque ces cellules sont sur le point de défiler à l'écran, la hauteur réelle de la ligne sera calculée. Pour déterminer la hauteur réelle de chaque ligne, la vue de tableau demande automatiquement à chaque cellule quelle hauteur elle contentViewdoit être basée sur la largeur fixe connue de la vue de contenu (qui est basée sur la largeur de la vue de table, moins tout élément supplémentaire comme un index de section ou vue accessoire) et les contraintes de disposition automatique que vous avez ajoutées à la vue de contenu et aux sous-vues de la cellule. Une fois cette hauteur de cellule réelle déterminée, l'ancienne hauteur estimée de la ligne est mise à jour avec la nouvelle hauteur réelle (et tous les ajustements apportés à contentSize / contentOffset de la vue tableau sont effectués selon vos besoins).

D'une manière générale, l'estimation que vous fournissez n'a pas besoin d'être très précise - elle est uniquement utilisée pour dimensionner correctement l'indicateur de défilement dans la vue tableau, et la vue tableau fait un bon travail d'ajustement de l'indicateur de défilement pour les estimations incorrectes lorsque vous faites défiler les cellules à l'écran. Vous devez définir la estimatedRowHeightpropriété sur la vue de table (dans viewDidLoadou similaire) sur une valeur constante qui est la hauteur de ligne "moyenne". Ce n'est que si la hauteur de vos lignes présente une variabilité extrême (par exemple, diffère d'un ordre de grandeur) et que vous remarquez l'indicateur de défilement "sauter" lorsque vous faites défiler si vous vous donnez la peine de mettre tableView:estimatedHeightForRowAtIndexPath:en œuvre le calcul minimal requis pour renvoyer une estimation plus précise pour chaque ligne.

Pour la prise en charge iOS 7 (implémentation du dimensionnement automatique des cellules vous-même)

3. Faites une passe de mise en page et obtenez la hauteur de la cellule

Tout d'abord, instanciez une instance hors écran d'une cellule de vue de table, une instance pour chaque identifiant de réutilisation , qui est utilisée strictement pour les calculs de hauteur. (Hors écran, ce qui signifie que la référence de cellule est stockée dans une propriété / ivar sur le contrôleur de vue et n'est jamais retournée tableView:cellForRowAtIndexPath:pour que la vue de table s'affiche réellement à l'écran.) Ensuite, la cellule doit être configurée avec le contenu exact (par exemple, texte, images, etc.) qu'il tiendrait s'il devait être affiché dans la vue de table.

Ensuite, la force de la cellule immédiatement présentation de ses sous - vues, puis utilisez la systemLayoutSizeFittingSize:méthode sur le UITableViewCell« s contentViewpour savoir ce que la hauteur requise de la cellule est. Utilisez UILayoutFittingCompressedSizepour obtenir la plus petite taille requise pour s'adapter à tout le contenu de la cellule. La hauteur peut ensuite être renvoyée par la tableView:heightForRowAtIndexPath:méthode déléguée.

4. Utiliser les hauteurs de ligne estimées

Si votre vue de table contient plus de quelques dizaines de lignes, vous constaterez que l'exécution de la résolution automatique des contraintes de mise en page peut rapidement embourber le thread principal lors du premier chargement de la vue de table, comme cela tableView:heightForRowAtIndexPath:est appelé sur chaque ligne lors du premier chargement ( afin de calculer la taille de l'indicateur de défilement).

Depuis iOS 7, vous pouvez (et devez absolument) utiliser la estimatedRowHeightpropriété dans la vue de table. Cela permet de fournir à la vue de table une estimation / un espace réservé temporaire pour les hauteurs de ligne des cellules qui ne sont pas encore à l'écran. Ensuite, lorsque ces cellules sont sur le point de défiler à l'écran, la hauteur réelle de la ligne sera calculée (en appelant tableView:heightForRowAtIndexPath:) et la hauteur estimée mise à jour avec celle réelle.

D'une manière générale, l'estimation que vous fournissez n'a pas besoin d'être très précise - elle est uniquement utilisée pour dimensionner correctement l'indicateur de défilement dans la vue tableau, et la vue tableau fait un bon travail d'ajustement de l'indicateur de défilement pour les estimations incorrectes lorsque vous faites défiler les cellules à l'écran. Vous devez définir la estimatedRowHeightpropriété sur la vue de table (dans viewDidLoadou similaire) sur une valeur constante qui est la hauteur de ligne "moyenne". Ce n'est que si la hauteur de vos lignes présente une variabilité extrême (par exemple, diffère d'un ordre de grandeur) et que vous remarquez l'indicateur de défilement "sauter" lorsque vous faites défiler si vous vous donnez la peine de mettre tableView:estimatedHeightForRowAtIndexPath:en œuvre le calcul minimal requis pour renvoyer une estimation plus précise pour chaque ligne.

5. (Si nécessaire) Ajouter un cache de hauteur de ligne

Si vous avez fait tout ce qui précède et que vous trouvez toujours que les performances sont inacceptablement lentes lors de la résolution des contraintes tableView:heightForRowAtIndexPath:, vous devrez malheureusement implémenter une mise en cache pour les hauteurs de cellule. (C'est l'approche suggérée par les ingénieurs d'Apple.) L'idée générale est de laisser le moteur de mise en page automatique résoudre les contraintes la première fois, puis de mettre en cache la hauteur calculée pour cette cellule et d'utiliser la valeur mise en cache pour toutes les demandes futures pour la hauteur de cette cellule. L'astuce est bien sûr de vous assurer que vous effacez la hauteur mise en cache pour une cellule lorsque quelque chose se produit qui pourrait modifier la hauteur de la cellule - principalement, ce serait lorsque le contenu de cette cellule change ou lorsque d'autres événements importants se produisent (comme l'ajustement de l'utilisateur le curseur de taille de texte Dynamic Type).

Exemple de code générique iOS 7 (avec beaucoup de commentaires juteux)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path, depending on the particular layout required (you may have
    // just one, or may have many).
    NSString *reuseIdentifier = ...;

    // Dequeue a cell for the reuse identifier.
    // Note that this method will init and return a new cell if there isn't
    // one available in the reuse pool, so either way after this line of 
    // code you will have a cell with the correct constraints ready to go.
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell's 
    // updateConstraints method:
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    // If you are using multi-line UILabels, don't forget that the 
    // preferredMaxLayoutWidth needs to be set correctly. Do it at this 
    // point if you are NOT doing it within the UITableViewCell subclass 
    // -[layoutSubviews] method. For example: 
    // cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path.
    NSString *reuseIdentifier = ...;

    // Use a dictionary of offscreen cells to get a cell for the reuse 
    // identifier, creating a cell and storing it in the dictionary if one 
    // hasn't already been added for the reuse identifier. WARNING: Don't 
    // call the table view's dequeueReusableCellWithIdentifier: method here 
    // because this will result in a memory leak as the cell is created but 
    // never returned from the tableView:cellForRowAtIndexPath: method!
    UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
    if (!cell) {
        cell = [[YourTableViewCellClass alloc] init];
        [self.offscreenCells setObject:cell forKey:reuseIdentifier];
    }

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell's 
    // updateConstraints method:
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    // Set the width of the cell to match the width of the table view. This
    // is important so that we'll get the correct cell height for different
    // table view widths if the cell's height depends on its width (due to 
    // multi-line UILabels word wrapping, etc). We don't need to do this 
    // above in -[tableView:cellForRowAtIndexPath] because it happens 
    // automatically when the cell is used in the table view. Also note, 
    // the final width of the cell may not be the width of the table view in
    // some cases, for example when a section index is displayed along 
    // the right side of the table view. You must account for the reduced 
    // cell width.
    cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

    // Do the layout pass on the cell, which will calculate the frames for 
    // all the views based on the constraints. (Note that you must set the 
    // preferredMaxLayoutWidth on multiline UILabels inside the 
    // -[layoutSubviews] method of the UITableViewCell subclass, or do it 
    // manually at this point before the below 2 lines!)
    [cell setNeedsLayout];
    [cell layoutIfNeeded];

    // Get the actual height required for the cell's contentView
    CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    // Add an extra point to the height to account for the cell separator, 
    // which is added between the bottom of the cell's contentView and the 
    // bottom of the table view cell.
    height += 1.0;

    return height;
}

// NOTE: Set the table view's estimatedRowHeight property instead of 
// implementing the below method, UNLESS you have extreme variability in 
// your row heights and you notice the scroll indicator "jumping" 
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Do the minimal calculations required to be able to return an 
    // estimated row height that's within an order of magnitude of the 
    // actual height. For example:
    if ([self isTallCellAtIndexPath:indexPath]) {
        return 350.0;
    } else {
        return 40.0;
    }
}

Exemples de projets

Ces projets sont des exemples pleinement opérationnels de vues de table avec des hauteurs de ligne variables en raison de cellules de vue de table contenant du contenu dynamique dans UILabels.

Xamarin (C # /. NET)

Si vous utilisez Xamarin, consultez cet exemple de projet mis en place par @KentBoogaart .


1
Bien que cela fonctionne bien, je le trouve sous-estimé légèrement les tailles requises (peut-être en raison de divers problèmes d'arrondi) et je dois ajouter quelques points à la hauteur finale pour que tout mon texte tienne à l'intérieur des étiquettes
DBD

5
@ Alex311 Très intéressant, merci d'avoir fourni cet exemple. J'ai fait un petit test de ma part et j'ai écrit quelques commentaires ici: github.com/Alex311/TableCellWithAutoLayout/commit/…
smileyborg

3
Je recommanderais FORTEMENT la mise en cache de la cellule pour chaque type d'identifiant de réutilisation de cellule. Chaque fois que vous retirez de la file d'attente pour la hauteur, vous retirez une cellule de la file d'attente qui n'est pas rajoutée. La mise en cache peut réduire considérablement le nombre d'appels de l'initialiseur pour vos cellules de tableau. Pour une raison quelconque, lorsque je ne mettais pas en cache, la quantité de mémoire utilisée continuait à augmenter au fur et à mesure que je défilais.
Alex311

1
Storyboards, en fait. Je ne pense pas que cela fonctionne pour les cellules prototypes (au moins sans ré-instancier l'ensemble du VC). Il pourrait être possible d'éviter complètement la fuite en retirant la file d'attente dans le heightForRowAtIndexPath, en gardant la cellule et en la renvoyant la prochaine fois cellForRowAtIndexPath.
nschum

2
La iOS8mise en œuvre est-elle toujours recommandée iOS9et iOS10, ou existe-t-il de nouvelles approches depuis la publication de cette réponse?
koen

175

Pour iOS 8 ci-dessus, c'est vraiment simple:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableView.automaticDimension
}

ou

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

Mais pour iOS 7, la clé est de calculer la hauteur après la mise en page automatique:

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

Important

  • Si plusieurs étiquettes de lignes, n'oubliez pas de définir le numberOfLinessur 0.

  • N'oublie pas label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

L'exemple de code complet est ici .


7
Je pense que nous n'avons pas besoin d'implémenter la fonction heightForRowAtIndexPath dans le cas
OS8

Comme @eddwinpaz le note sur une autre réponse, il est important que les contraintes utilisées pour verrouiller la hauteur de ligne n'incluent PAS la marge.
Richard

1
+1 pour "Si plusieurs libellés de lignes, n'oubliez pas de définir le nombre de lignes à 0", cela faisait que mes cellules n'étaient pas de taille dynamique.
Ian-Fogelman

Appelez-moi un monstre de contrôle, mais même à l'époque d'iOS 11, je n'aime toujours pas utiliser UITableViewAutomaticDimension. J'ai eu de mauvaises expériences avec ça dans le passé. En tant que tel, j'utilise généralement les solutions iOS7 répertoriées ici. La note de William pour ne pas oublier label.preferredMaxLayoutWidth m'a sauvé ici.
Brian Sachetta

Lorsque nous faisons défiler, l'étiquette commence à apparaître sur plusieurs lignes et la hauteur de la ligne n'augmente pas non plus
Karanveer Singh

94

Exemple rapide d'un UITableViewCell à hauteur variable

Mis à jour pour Swift 3

La réponse Swift de William Hu est bonne, mais elle m'aide à avoir des étapes simples mais détaillées lorsque j'apprends à faire quelque chose pour la première fois. L'exemple ci-dessous est mon projet de test tout en apprenant à faire un UITableViewavec des hauteurs de cellule variables. Je l'ai basé sur cet exemple de base UITableView pour Swift .

Le projet terminé devrait ressembler à ceci:

entrez la description de l'image ici

Créer un nouveau projet

Il peut s'agir simplement d'une application à vue unique.

Ajoutez le code

Ajoutez un nouveau fichier Swift à votre projet. Nommez-le MyCustomCell. Cette classe contiendra les sorties pour les vues que vous ajoutez à votre cellule dans le storyboard. Dans cet exemple de base, nous n'aurons qu'une seule étiquette dans chaque cellule.

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

Nous connecterons cette prise plus tard.

Ouvrez ViewController.swift et assurez-vous que vous disposez du contenu suivant:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

Note importante:

  • Ce sont les deux lignes de code suivantes (ainsi que la disposition automatique) qui rendent possible la hauteur de cellule variable:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension

Configurer le storyboard

Ajoutez une vue tabulaire à votre contrôleur de vue et utilisez la disposition automatique pour l'épingler sur les quatre côtés. Faites ensuite glisser une cellule de vue de tableau sur la vue de tableau. Et sur la cellule Prototype, faites glisser une étiquette. Utilisez la disposition automatique pour épingler l'étiquette sur les quatre bords de la vue de contenu de la cellule de vue de tableau.

entrez la description de l'image ici

Note importante:

  • La mise en page automatique fonctionne avec les deux lignes de code importantes que j'ai mentionnées ci-dessus. Si vous n'utilisez pas la mise en page automatique, cela ne fonctionnera pas.

Autres paramètres IB

Nom et identificateur de classe personnalisés

Sélectionnez la cellule d'affichage de tableau et définissez la classe personnalisée sur MyCustomCell(le nom de la classe dans le fichier Swift que nous avons ajouté). Définissez également l'identifiant sur cell(la même chaîne que celle utilisée cellReuseIdentifierdans le code ci-dessus).

entrez la description de l'image ici

Zéro lignes pour l'étiquette

Définissez le nombre de lignes 0dans votre étiquette. Cela signifie plusieurs lignes et permet à l'étiquette de se redimensionner en fonction de son contenu.

entrez la description de l'image ici

Branchez les prises

  • Contrôlez le glissement de la vue Table dans le storyboard vers la tableViewvariable dans le ViewControllercode.
  • Faites de même pour le libellé de votre cellule Prototype avec la myCellLabelvariable de la MyCustomCellclasse.

Fini

Vous devriez pouvoir exécuter votre projet maintenant et obtenir des cellules de hauteurs variables.

Remarques

  • Cet exemple ne fonctionne que pour iOS 8 et versions ultérieures. Si vous devez toujours prendre en charge iOS 7, cela ne fonctionnera pas pour vous.
  • Vos propres cellules personnalisées dans vos futurs projets auront probablement plus d'une seule étiquette. Assurez-vous que tout est bien épinglé afin que la mise en page automatique puisse déterminer la bonne hauteur à utiliser. Vous devrez peut-être également utiliser une résistance à la compression verticale et des étreintes. Consultez cet article pour en savoir plus.
  • Si vous n'épinglez pas les bords d'attaque et de fuite (gauche et droite), vous devrez peut-être également définir les libellés de preferredMaxLayoutWidthsorte qu'il sache à quel moment du retour à la ligne. Par exemple, si vous avez ajouté une contrainte Centrer horizontalement à l'étiquette dans le projet ci-dessus plutôt que d'épingler les bords d'attaque et de fuite, vous devez ajouter cette ligne à la tableView:cellForRowAtIndexPathméthode:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Voir également


N'est-il pas préférable de mettre le preferredMaxLayoutWidthcontre celui de la cellule contentSize? De cette façon, si vous avez un accessoireView ou qu'il a été glissé pour modification, il serait toujours pris en considération?
Honey

@Honey, vous avez très bien raison. Je n'ai pas suivi iOS depuis environ un an maintenant et je suis trop rouillé pour bien vous répondre en ce moment.
Suragch

65

J'ai encapsulé la solution iOS7 de smileyborg dans une catégorie

J'ai décidé d'envelopper cette solution intelligente de @smileyborg dans une UICollectionViewCell+AutoLayoutDynamicHeightCalculationcatégorie.

La catégorie corrige également les problèmes décrits dans la réponse de @ wildmonkey (chargement d'une cellule à partir d'une pointe et systemLayoutSizeFittingSize:retour CGRectZero)

Il ne prend en compte aucune mise en cache mais convient à mes besoins en ce moment. N'hésitez pas à le copier, le coller et le pirater.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Exemple d'utilisation:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

Heureusement, nous n'aurons pas à faire ce jazz dans iOS8, mais c'est pour le moment!


2
Vous devriez simplement pouvoir utiliser a: [YourCell new]et l'utiliser comme mannequin. Tant que le code de construction du code de contrainte est déclenché dans votre instance et que vous déclenchez une passe de mise en page par programme, vous devriez être prêt à partir.
Adam Waite

1
Merci! Cela marche. Votre catégorie est géniale. C'est ce qui m'a fait réaliser que cette technique fonctionne UICollectionViewségalement.
Ricardo Sanchez-Saez

2
Comment feriez-vous cela en utilisant une cellule prototype définie dans un storyboard?
David Potter

57

Voici ma solution:

Vous devez dire TableViewl' estimatedHeightavant de charger la vue. Sinon, il ne pourra pas se comporter comme prévu.

Objectif c

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

Mise à jour vers Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}

2
La configuration correcte de la mise en page automatique, avec l'ajout de ce code dans viewDidLoad, a fait l'affaire.
Bruce

1
mais que se passe-t-il si estimatedRowHeightvarie ligne par ligne? dois-je surestimer ou sous-estimer? utiliser le minimum ou le maximum de la hauteur dans laquelle j'utilise tableView?
János

1
@ János c'est le point de rowHeight. Pour ce faire, vous devez utiliser les contraintes sans marges et aligner les objets sur TableViewCell. et je suppose que vous utilisez un UITextView donc vous devez toujours supprimer autoscroll = false sinon il maintiendra la hauteur et la hauteur relative n'agira pas comme prévu.
Eddwin Paz

1
C'est de loin la solution la plus robuste. Ne vous inquiétez pas estimatedRowHeight, cela affecte principalement la taille de la barre de défilement, jamais la hauteur des cellules réelles. Soyez audacieux dans la hauteur que vous choisissez: cela affectera l'animation d'insertion / suppression.
SwiftArchitect

Voici l'exemple de code dans swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

La solution proposée par @smileyborg est presque parfaite. Si vous avez une cellule personnalisée et que vous en voulez une ou plusieurs UILabelavec des hauteurs dynamiques, la méthode systemLayoutSizeFittingSize combinée avec AutoLayout activé renvoie un CGSizeZerosauf si vous déplacez toutes vos contraintes de cellule de la cellule vers son contentView (comme suggéré par @TomSwift ici Comment redimensionner la vue d'ensemble pour adapter toutes les sous-vues avec la mise en page automatique? ).

Pour ce faire, vous devez insérer le code suivant dans votre implémentation UITableViewCell personnalisée (grâce à @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Mélanger la réponse @smileyborg avec cela devrait fonctionner.


systemLayoutSizeFittingSizedoit être appelé sur contentView, pas sur cell
onmyway133

25

Un problème assez important que je viens de rencontrer pour répondre.

La réponse de @ smileyborg est généralement correcte. Cependant, si vous avez du code dans la layoutSubviewsméthode de votre classe de cellule personnalisée, par exemple en définissant le preferredMaxLayoutWidth, il ne sera pas exécuté avec ce code:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

Cela m'a dérouté pendant un certain temps. Ensuite, je me suis rendu compte que c'est parce que ceux-ci ne déclenchent que layoutSubviews sur le contentView, pas la cellule elle-même.

Mon code de travail ressemble à ceci:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

Notez que si vous créez une nouvelle cellule, je suis sûr que vous n'avez pas besoin d'appeler setNeedsLayoutcar elle devrait déjà être définie. Dans les cas où vous enregistrez une référence à une cellule, vous devriez probablement l'appeler. Quoi qu'il en soit, cela ne devrait rien faire de mal.

Une autre astuce si vous utilisez des sous-classes de cellules où vous définissez des choses comme preferredMaxLayoutWidth. Comme le mentionne @smileyborg, "votre cellule de vue de table n'a pas encore fixé sa largeur à la largeur de la vue de table". Cela est vrai et pose problème si vous effectuez votre travail dans votre sous-classe et non dans le contrôleur de vue. Cependant, vous pouvez simplement définir le cadre de cellule à ce stade en utilisant la largeur du tableau:

Par exemple, dans le calcul de la hauteur:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(Il se trouve que je mets en cache cette cellule particulière pour la réutiliser, mais ce n'est pas pertinent).



19

Tant que votre disposition dans votre cellule est bonne.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Mise à jour: vous devez utiliser le redimensionnement dynamique introduit dans iOS 8.


Cela fonctionne pour moi sur iOS 7, est maintenant OK appeler tableView:cellForRowAtIndexPath:en ce tableView:heightForRowAtIndexPath:moment?
Fourmis

D'accord, cela ne fonctionne pas, mais lorsque j'appelle et mets systemLayoutSizeFittingSize:en tableView:cellForRowAtIndexPath:cache le résultat, puis que tableView:heightForRowAtIndexPath:je l' utilise, cela fonctionne bien tant que les contraintes sont correctement configurées, bien sûr!
Fourmis

Cela ne fonctionne que si vous utilisez dequeueReusableCellWithIdentifier: au lieu de dequeueReusableCellWithIdentifier: forIndexPath:
Antoine

1
Je ne pense vraiment pas que l'appel à tableView: cellForRowAtIndexPath: directement soit un bon moyen.
Itachi

19

(pour Xcode 8.x / Xcode 9.x lu en bas)

Méfiez-vous du problème suivant dans Xcode 7.x, qui pourrait être une source de confusion:

Interface Builder ne gère pas correctement la configuration automatique des cellules. Même si vos contraintes sont absolument valables, IB se plaindra toujours et vous donnera des suggestions et des erreurs déroutantes. La raison en est qu'IB n'est pas disposé à modifier la hauteur de la ligne selon vos contraintes (afin que la cellule s'adapte autour de votre contenu). Au lieu de cela, il maintient la hauteur de la ligne fixe et commence à vous suggérer de modifier vos contraintes, que vous devez ignorer .

Par exemple, imaginez que vous avez tout configuré correctement, aucun avertissement, aucune erreur, tout fonctionne.

entrez la description de l'image ici

Maintenant, si vous changez la taille de la police (dans cet exemple, je change la taille de la police de l'étiquette de description de 17,0 à 18,0).

entrez la description de l'image ici

Étant donné que la taille de la police a augmenté, l'étiquette souhaite désormais occuper 3 lignes (avant, elle occupait 2 lignes).

Si Interface Builder fonctionnait comme prévu, il redimensionnerait la hauteur de la cellule pour s'adapter à la nouvelle hauteur d'étiquette. Cependant, ce qui se passe réellement, c'est qu'IB affiche l'icône d'erreur de mise en page automatique rouge et vous suggère de modifier les priorités de compression / étreinte.

entrez la description de l'image ici

Vous devez ignorer ces avertissements. Ce que vous pouvez * faire à la place est de modifier manuellement la hauteur de la ligne dans (sélectionnez Cellule> Inspecteur de taille> Hauteur de ligne).

entrez la description de l'image ici

Je modifiais cette hauteur un clic à la fois (en utilisant le stepper haut / bas) jusqu'à ce que les erreurs de la flèche rouge disparaissent! (vous obtiendrez en fait des avertissements jaunes, à ce moment, allez-y et faites des `` mises à jour des cadres '', tout devrait fonctionner).

* Notez que vous n'avez pas réellement à résoudre ces erreurs rouges ou jaunes dans Interface Builder - au moment de l'exécution, tout fonctionnera correctement (même si IB affiche des erreurs / avertissements). Assurez-vous simplement qu'au moment de l'exécution dans le journal de la console, vous n'obtenez aucune erreur de mise en page automatique.

En fait, essayer de toujours mettre à jour la hauteur de ligne dans IB est super ennuyeux et parfois presque impossible (en raison de valeurs fractionnaires).

Pour éviter les avertissements / erreurs gênants de l'IB, vous pouvez sélectionner les vues concernées et, Size Inspectorpour la propriété, AmbiguitychoisirVerify Position Only

entrez la description de l'image ici


Xcode 8.x / Xcode 9.x semble (parfois) faire les choses différemment de Xcode 7.x, mais toujours de manière incorrecte. Par exemple, même lorsque compression resistance priority/ hugging priorityest défini sur requis (1000), Interface Builder peut étirer ou découper une étiquette pour l'adapter à la cellule (au lieu de redimensionner la hauteur de la cellule pour qu'elle s'adapte autour de l'étiquette). Et dans un tel cas, il pourrait même ne pas afficher d'alerte ou d'erreur AutoLayout. Ou parfois, il fait exactement ce que Xcode 7.x a fait, décrit ci-dessus.


salut est-il possible de donner une hauteur dynamique pour une cellule, celle ayant une table avec une cellule avec un contenu de cellule dynamique.?
Jignesh B

18

Pour définir la dimension automatique pour la hauteur de ligne et la hauteur de ligne estimée, assurez-vous que les étapes suivantes sont effectives, la dimension automatique est efficace pour la disposition de hauteur de cellule / ligne.

  • Attribuer et implémenter dataSource tableview et déléguer
  • Attribuer UITableViewAutomaticDimensionà rowHeight et estimeRowHeight
  • Implémenter les méthodes de délégué / dataSource (ie heightForRowAtet lui renvoyer une valeur UITableViewAutomaticDimension)

-

Objectif c:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

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

    return UITableViewAutomaticDimension;
}

Rapide:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

Pour l'instance d'étiquette dans UITableviewCell

  • Définir le nombre de lignes = 0 (& mode de saut de ligne = queue tronquée)
  • Définissez toutes les contraintes (haut, bas, droite gauche) par rapport à son conteneur superview / cell.
  • Facultatif : définissez la hauteur minimale de l'étiquette, si vous souhaitez que la zone verticale minimale soit couverte par l'étiquette, même en l'absence de données.

entrez la description de l'image ici

Remarque : Si vous avez plusieurs étiquettes (UIElements) avec une longueur dynamique, qui doit être ajustée en fonction de la taille de son contenu: ajustez la `` Priorité à l'étirement du contenu et à la résistance à la compression '' pour les étiquettes que vous souhaitez développer / compresser avec une priorité plus élevée.


1
Merci semble être une solution claire et simple, mais j'ai un problème, je n'utilise pas d'étiquettes mais des vues de texte, j'ai donc besoin que la hauteur de la ligne augmente lorsque des données sont ajoutées. Mon problème est alors de transmettre des informations à heightForRowAt. Je peux mesurer la hauteur de mes vues de texte changeantes, mais je dois maintenant changer la hauteur de la ligne. J'apprécierais de l'aide s'il vous plaît
Jeremy Andrews

@JeremyAndrews Sure vous aidera. Posez votre question avec le code source que vous avez essayé et détaillez le problème.
Krunal

Cela fonctionne pour moi sans implémenter de tableView: heightForRowsource de données.
Hemang

@Hemang - Depuis iOS 11+, il fonctionne sans tableView: heightForRow. (pour iOS 10- tableView: heightForRowest requis)
Krunal

1
@Hemang - La solution dépend de la définition exacte de la requête. Ici, j'ai une solution commune générale pour votre requête. Mettez la condition à l'intérieur tableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Krunal

16

Comme @ Bob-Spryn, j'ai rencontré un problème assez important pour que je poste cela comme une réponse.

J'ai lutté pendant un moment avec la réponse de @ smileyborg . La chasse aux sorcières que je suis tombé est si vous avez défini votre cellule prototype en IB avec des éléments supplémentaires ( UILabels, UIButtons, etc.) dans IB lorsque vous instancier la cellule avec [ [YourTableViewCellClass alloc] init]il ne sera pas instancier tous les autres éléments à l' intérieur de cette cellule , sauf si vous avez un code écrit pour le faire. (J'ai eu une expérience similaire avec initWithStyle.)

Pour que le storyboard instancie tous les éléments supplémentaires, obtenez votre cellule avec [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](Pas [tableView dequeueReusableCellWithIdentifier:forIndexPath:]car cela causera des problèmes intéressants.) Lorsque vous faites cela, tous les éléments que vous avez définis dans IB seront instanciés.


15

Hauteur dynamique des cellules de la vue tabulaire et disposition automatique

Un bon moyen de résoudre le problème avec la mise en page automatique du storyboard:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
  static RWImageCell *sizingCell = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
  });

  [sizingCell setNeedsLayout];
  [sizingCell layoutIfNeeded];

  CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  return size.height;
}

1
Cela a déjà été largement traité dans la réponse acceptée à cette question.
smileyborg

2
Oui, je sais ... mais je ne voulais pas utiliser PureLayout et le 'trick' dispatch_once m'a beaucoup aidé, à ne travailler qu'avec le Storyboard.
Mohnasm

13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

entrez la description de l'image ici


13

Une autre «solution»: évitez toute cette frustration et utilisez plutôt UIScrollView pour obtenir un résultat qui ressemble et se sent identique à UITableView.

Ce fut la douloureuse "solution" pour moi, après avoir mis au total plus de 20 heures très frustrantes à essayer de construire quelque chose comme ce que smileyborg a suggéré et échoué pendant plusieurs mois et trois versions des versions de l'App Store.

Mon avis est que si vous avez vraiment besoin du support iOS 7 (pour nous, c'est essentiel), la technologie est tout simplement trop fragile et vous vous arracherez les cheveux en essayant. Et que UITableView est complètement exagéré en général à moins que vous n'utilisiez certaines des fonctionnalités avancées d'édition de lignes et / ou que vous n'ayez vraiment besoin de prendre en charge plus de 1000 "lignes" (dans notre application, il n'y a en réalité jamais plus de 20 lignes).

Le bonus supplémentaire est que le code devient incroyablement simple par rapport à toute la merde de délégué et de va-et-vient qui vient avec UITableView. Il s'agit d'une seule boucle de code dans viewOnLoad qui a l'air élégante et facile à gérer.

Voici quelques conseils pour le faire:

  1. À l'aide de Storyboard ou d'un fichier nib, créez un ViewController et la vue racine associée.

  2. Faites glisser un UIScrollView sur votre vue racine.

  3. Ajoutez des contraintes en haut, en bas, à gauche et à droite à la vue de niveau supérieur afin que UIScrollView remplisse la vue racine entière.

  4. Ajoutez une UIView à l'intérieur de l'UIScrollView et appelez-la "conteneur". Ajoutez des contraintes supérieure, inférieure, gauche et droite à UIScrollView (son parent). ASTUCE CLÉ: Ajoutez également des contraintes de «largeurs égales» pour lier UIScrollView et UIView.

    REMARQUE: vous obtiendrez une erreur «la vue de défilement a une hauteur de contenu défilable ambiguë» et votre UIView de conteneur doit avoir une hauteur de 0 pixel. Aucune de ces erreurs ne semble avoir d'importance lorsque l'application est en cours d'exécution.

  5. Créez des fichiers nib et des contrôleurs pour chacune de vos "cellules". Utilisez UIView et non UITableViewCell.

  6. Dans votre ViewController racine, vous ajoutez essentiellement toutes les «lignes» à l'UIView du conteneur et ajoutez par programme des contraintes liant leurs bords gauche et droit à la vue du conteneur, leurs bords supérieurs soit à la vue du conteneur (pour le premier élément), soit à la précédente. cellule. Reliez ensuite la dernière cellule au fond du conteneur.

Pour nous, chaque "ligne" est dans un fichier nib. Le code ressemble donc à ceci:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

Et voici le code pour UITools.addViewToTop:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)

        //Set left and right constraints so fills full horz width:

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))

        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

Le seul "piège" que j'ai trouvé avec cette approche jusqu'à présent est que UITableView a une belle fonctionnalité d'en-têtes de section "flottants" en haut de la vue lorsque vous faites défiler. La solution ci-dessus ne le fera que si vous ajoutez plus de programmation, mais pour notre cas particulier, cette fonctionnalité n'était pas 100% essentielle et personne n'a remarqué quand elle est partie.

Si vous voulez des séparateurs entre vos cellules, ajoutez simplement une UIView de 1 pixel de haut au bas de votre "cellule" personnalisée qui ressemble à un séparateur.

Assurez-vous d'activer "rebonds" et "rebondir verticalement" pour que le contrôle de rafraîchissement fonctionne et il ressemble donc plus à une table.

TableView affiche des lignes et des séparateurs vides sous votre contenu, s'il ne remplit pas le plein écran, contrairement à cette solution. Mais personnellement, je préfère que ces lignes vides ne soient pas là de toute façon - avec une hauteur de cellule variable, il m'a toujours semblé "buggé" de toute façon d'y avoir les lignes vides.

En espérant qu'un autre programmeur lit mon message AVANT de perdre plus de 20 heures à essayer de le comprendre avec Table View dans leur propre application. :)


11

J'ai dû utiliser des vues dynamiques (configurer les vues et les contraintes par code) et quand j'ai voulu définir la largeur de l'étiquette favoriteMaxLayoutWidth était 0. J'ai donc une mauvaise hauteur de cellule.

J'ai ensuite ajouté

[cell layoutSubviews];

avant d'exécuter

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

Après cette largeur d'étiquette était comme prévu et la hauteur dynamique calculait à droite.


9

Supposons que vous ayez une cellule avec une sous-vue et que vous souhaitiez que la hauteur de la cellule soit suffisamment élevée pour englober la sous-vue + le remplissage.

1) Définissez la contrainte inférieure de la sous-vue égale à cell.contentView moins le remplissage souhaité. Ne définissez pas de contraintes sur la cellule ou cell.contentView elle-même.

2) Sélectionnez l' un de l'tableView rowHeightpropriété ou tableView:heightForRowAtIndexPath:àUITableViewAutomaticDimension .

3) Définissez la estimatedRowHeightpropriété de tableView outableView:estimatedHeightForRowAtIndexPath: une meilleure estimation de la hauteur.

C'est ça.


7

Si vous faites la mise en page par programme, voici ce qu'il faut considérer pour iOS 10 en utilisant des ancres dans Swift.

Il y a trois règles / étapes

NUMÉRO 1: définissez ces deux propriétés de tableview sur viewDidLoad, la première indique à la tableview qui devrait s'attendre à des tailles dynamiques sur leurs cellules, la seconde consiste simplement à laisser l'application calculer la taille de l'indicateur de la barre de défilement, ce qui aide à performance.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMÉRO 2: Il est important que vous ayez besoin d'ajouter les sous-vues au contentView de la cellule et non à la vue, et d'utiliser également ses présentationsmarginguide pour ancrer les sous-vues en haut et en bas, ceci est un exemple pratique de la façon de le faire.

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

Créez une méthode qui ajoutera les sous-vues et effectuerez la mise en page, appelez-la dans la méthode init.

NUMÉRO 3: NE PAS APPELER LA MÉTHODE:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

Si vous le faites, vous remplacerez votre implémentation.

Suivez ces 3 règles pour les cellules dynamiques dans les vues de table.

voici une implémentation fonctionnelle https://github.com/jamesrochabrun/MinimalViewController


dans Swift 5 UITableViewAutomaticDimension renommé UITableView.automaticDimension
James Rochabrun

4

Si vous avez une longue chaîne. par exemple, qui n'a pas de saut de ligne. Ensuite, vous pourriez rencontrer des problèmes.

Le correctif «présumé» est mentionné par la réponse acceptée et quelques autres réponses. Vous avez juste besoin d'ajouter

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Je trouve que la réponse de Suragh est la plus complète et la plus concise , donc sans confusion.

Bien que non expliquer pourquoi ces changements sont nécessaires. Faisons cela.

Déposez le code suivant dans un projet.

import UIKit

class ViewController: UIViewController {

    lazy var label : UILabel = {
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.backgroundColor = .red
        lbl.textColor = .black
        return lbl
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // step0: (0.0, 0.0)
        print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step1: (29.0, 20.5)
        label.text = "hiiiii"
        print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step2: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints"
        print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step3: (992.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
        print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step4: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
        print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step5: (328.0, 61.0)
        label.numberOfLines = 0
        print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step6: (98.5, 243.5)
        label.preferredMaxLayoutWidth = 100
        print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")

        setupLayout()
    }
    func setupLayout(){
        view.addSubview(label)
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

Notez que je n'ai ajouté aucune contrainte de taille . Je n'ai ajouté que des contraintes centerX, centerY. Mais l'étiquette sera toujours dimensionnée correctement Pourquoi?

À cause de contentSize.

Pour mieux traiter cela, gardez d'abord l'étape 0, puis commentez les étapes 1 à 6. Restons setupLayout(). Observez le comportement.

Décommentez ensuite l'étape 1 et observez.

Décommentez ensuite l'étape 2 et observez.

Faites-le jusqu'à ce que vous ayez décommenté les 6 étapes et observé leurs comportements.

Que peut-on conclure de tout cela? Quels facteurs peuvent changer le contenSize?

  1. Longueur du texte: si vous avez un texte plus long, la largeur de votre intrinsicContentSize augmentera
  2. Sauts de ligne: si vous ajoutez\n la largeur de intrinsicContentSize sera la largeur maximale de toutes les lignes. Si une ligne a 25 caractères, une autre a 2 caractères et une autre a 21 caractères alors votre largeur sera calculée sur la base des 25 caractères
  3. Nombre de lignes autorisées: vous devez définir la numberOfLinessur 0sinon vous n'aurez pas plusieurs lignes. Votre numberOfLinesajustera votre intrinsicContentSize hauteur
  4. Faire des ajustements: imaginez qu'en fonction de votre texte, la largeur 200et la hauteur de votre intrinsicContentSize étaient 100, mais vous vouliez limiter la largeur au conteneur de l'étiquette, qu'allez-vous faire? La solution consiste à le régler à la largeur souhaitée. Pour ce faire, définissez preferredMaxLayoutWidthsur 130alors votre nouveau intrinsicContentSize aura une largeur d'environ 130. La hauteur serait évidemment plus que 100parce que vous auriez besoin de plus de lignes. Cela étant dit, si vos contraintes sont correctement définies, vous n'aurez pas besoin de l'utiliser du tout! Pour en savoir plus, voir cette réponse et ses commentaires. Vous ne devez l'utiliser que preferredMaxLayoutWidthsi vous n'avez pas de contraintes limitant la largeur / hauteur comme dans l'un pourrait dire "ne pas envelopper le texte à moins qu'il dépasse lapreferredMaxLayoutWidth ». Mais avec 100% de certitude si vous définissez le premier / arrière et numberOfLinesà 0alors vous êtes bon! Longue histoire courte la plupart des réponses ici qui recommande de l' utiliser sont MAUVAIS! Vous n'avez pas besoin. Il est un ayant besoin de signe que vos contraintes ne sont pas définis correctement ou que vous n'avez tout simplement pas de contraintes

  5. Taille de police: Notez également que si vous augmentez votre fontSize, la hauteur de intrinsicContentSize augmentera. Je ne l'ai pas montré dans mon code. Vous pouvez l'essayer par vous-même.

Revenons donc à votre exemple tableViewCell:

Il vous suffit de:

  • définir le numberOfLinesà0
  • contraindre correctement l'étiquette aux marges / bords
  • Il n'est pas nécessaire de régler preferredMaxLayoutWidth.

1

Dans mon cas, je dois créer une cellule personnalisée avec une image provenant du serveur et pouvant avoir n'importe quelle largeur et hauteur. Et deux UILabels avec taille dynamique (largeur et hauteur)

j'ai obtenu la même chose ici dans ma réponse avec la mise en page automatique et par programme:

Fondamentalement, la réponse @smileyBorg a aidé, mais systemLayoutSizeFittingSize n'a jamais fonctionné pour moi, dans mon approche:

1. Aucune utilisation de la propriété de calcul automatique de la hauteur de ligne. 2. Pas d'utilisation de la hauteur estimée 3. Pas besoin de mise à jour inutile Contraintes. 4.Aucune utilisation de la largeur de présentation maximale préférée automatique. 5. Aucune utilisation de systemLayoutSizeFittingSize (devrait avoir une utilité mais ne fonctionne pas pour moi, je ne sais pas ce qu'il fait en interne), mais à la place ma méthode - (float) getViewHeight fonctionne et je sais ce qu'elle fait en interne.

Est-il possible d'avoir des hauteurs différentes dans une cellule UITableView lorsque j'utilise plusieurs façons différentes d'afficher la cellule?


1

Dans mon cas, le remplissage était dû aux hauteurs sectionHeader et sectionFooter, où le storyboard m'a permis de le changer au minimum 1. Donc, dans la méthode viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

Je viens de faire une stupide tentative d'erreur avec les 2 valeurs de rowHeightet j'ai juste estimatedRowHeightpensé que cela pourrait fournir des informations de débogage:

Si vous les définissez tous les deux OU seulement définissez le, estimatedRowHeightvous obtiendrez le comportement souhaité:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

Nous vous suggérons de faire de votre mieux pour obtenir une estimation correcte, mais le résultat final n'est pas différent. Cela affectera simplement vos performances.

entrez la description de l'image ici


Si vous définissez uniquement rowHeight, c'est-à-dire uniquement:

tableView.rowHeight = UITableViewAutomaticDimension

votre résultat final ne serait pas comme souhaité:

entrez la description de l'image ici


Si vous définissez le estimatedRowHeightsur 1 ou plus petit, vous vous planterez indépendamment du rowHeight.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

Je suis tombé en panne avec le message d'erreur suivant:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException

1

En ce qui concerne la réponse acceptée par @smileyborg, j'ai trouvé

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

ne pas être fiable dans certains cas où les contraintes sont ambiguës. Mieux vaut forcer le moteur de disposition à calculer la hauteur dans une direction, en utilisant la catégorie d'assistance sur UIView ci-dessous:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

Où w: est la largeur de la table


0

Ajoutez simplement ces deux fonctions dans votre viewcontroller, cela résoudra votre problème. Ici, la liste est un tableau de chaînes qui contient votre chaîne de chaque ligne.

 func tableView(_ tableView: UITableView, 
   estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])

    return (tableView.rowHeight) 
}

func calculateHeight(inString:String) -> CGFloat
{
    let messageString = input.text
    let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]

    let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)

    let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)

    let requredSize:CGRect = rect
    return requredSize.height
}

-1
swift 4

    @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var tableView: UITableView!
    private var context = 1
 override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
    }
  // Added observer to adjust tableview height based on the content

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &self.context{
            if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
                print("-----")
                print(size.height)
                tableViewHeightConstraint.constant = size.height + 50
            }
        }
    }

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }

-1

Si la hauteur de la cellule est dynamique par le contenu, vous devez la compter précisément, puis renvoyer la valeur de la hauteur avant le rendu de la cellule. Un moyen simple consiste à définir la méthode de comptage dans le code de cellule de vue de table pour que le contrôleur appelle à la méthode de délégué de hauteur de cellule de table. N'oubliez pas de compter la largeur réelle du cadre de cellule (320 par défaut) si la hauteur dépend de la largeur du tableau ou de l'écran. Autrement dit, dans la méthode déléguée de hauteur de cellule de tableau, utilisez cell.frame pour corriger d'abord la largeur de cellule, puis appelez la méthode de hauteur de comptage définie dans la cellule pour obtenir la valeur appropriée et la renvoyer. .

PS. Le code pour générer l'objet de cellule pourrait être défini dans une autre méthode pour que la méthode de délégué de cellule de vue de table différente puisse appeler.


-3

UITableView.automaticDimension peut être défini via Interface Builder:

Xcode> Storyboard> Inspecteur de taille

Cellule de vue Tableau> Hauteur de ligne> Automatique

Inspecteur de taille


-4

encore une autre solution iOs7 + iOs8 dans Swift

var cell2height:CGFloat=44

override func viewDidLoad() {
    super.viewDidLoad()
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    cell2height=cell.contentView.height
    return cell
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}

note: systemLayoutSizeFittingSize ne fonctionne pas dans mon cas
djdance

la hauteur de la cellule n'est pas correcte cellForRowAtIndexPath, la cellule n'est pas encore disposée à ce stade.
Andrii Chernenko

dans iOs7, il s'agit d'une valeur fixe, c'est-à-dire que cela fonctionne. Vous pouvez définir en dehors cellForRowAtIndexPath si vous voulez
djdance
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.