Utilisation de la mise en page automatique dans UITableView pour la mise en page dynamique des cellules et les hauteurs variables des rangées

comment utilisez-vous Auto Layout dans UITableViewCell s dans une vue de tableau pour laisser le contenu de chaque cellule et des sous-vues déterminer la hauteur de la rangée (elle-même/automatiquement), tout en maintenant une bonne performance de défilement?

1371
demandé sur Krunal 2013-09-11 20:48:39
la source

25 ответов

TL; DR: vous n'aimez pas lire? Passez directement aux exemples de projets sur GitHub:

Description Conceptuelle

les 2 premières étapes ci-dessous sont applicables indépendamment des versions iOS que vous êtes le développement de.

1. Définir Et Ajouter Des Contraintes

dans votre sous-classe UITableViewCell , ajoutez des contraintes pour que les sous-vues de la cellule aient leurs bords épinglés aux bords de la cellule contentView (surtout aux bords supérieur et inférieur). NOTE: ne pas épingler les subviews à la cellule elle-même; seulement à la cellule contentView ! laisser la taille du contenu intrinsèque de ces subviews conduire la hauteur de la vue de contenu de la cellule de vue de table en s'assurant que les contraintes résistance de compression de contenu et câlin de contenu dans la dimension verticale de chaque sous-vue ne sont pas dépassées par des contraintes de priorité supérieure que vous avez ajoutées. ( Huh? Cliquez sur ici. )

rappelez - vous, l'idée est d'avoir les antennes de la cellule connectés verticalement à la vue de contenu de la cellule afin qu'ils puissent " exercer une pression" et faire en sorte que la vue de contenu s'étende pour les adapter. En utilisant une cellule d'exemple avec quelques sous-vues, voici une illustration visuelle de ce que certains (pas tous!) de vos contraintes aurait besoin de ressembler à:

Example illustration of constraints on a table view cell.

vous pouvez imaginer que plus de texte est ajouté à l'étiquette de corps multi-ligne dans la cellule d'exemple ci-dessus, il aura besoin de croître verticalement pour s'adapter au texte, qui sera forcer efficacement la cellule à grandir en hauteur. (Bien sûr, vous devez obtenir les bonnes contraintes pour que cela fonctionne correctement!)

L'obtention de vos contraintes est certainement la la partie la plus difficile et la plus importante d'obtenir des hauteurs de cellule dynamique de travail avec la mise en page automatique. Si vous faites une erreur ici, cela pourrait empêcher tout le reste de fonctionner -- alors prenez votre temps! Je vous recommande de configurer vos contraintes en code car vous savez exactement quelles contraintes sont ajoutées où, et il est beaucoup plus facile de déboguer quand les choses vont mal. Ajouter des contraintes dans le code peut être tout aussi facile et significativement plus puissant que Interface Builder en utilisant des ancrages de mise en page, ou l'une des API open source fantastiques disponibles sur GitHub.

  • si vous ajoutez des contraintes en code, vous devez le faire une fois à partir de la méthode updateConstraints de votre sous-classe Uitable Viewcell. Notez que updateConstraints peut être appelé plus d'une fois, donc pour éviter d'ajouter les mêmes contraintes plus d'une fois, assurez-vous d'envelopper votre code d'ajout de contrainte dans updateConstraints dans un chèque pour une propriété booléenne telle que didSetupConstraints (que vous définissez à YES après avoir lancé votre code d'ajout de contrainte une fois). D'un autre côté, si vous avez un code qui met à jour les contraintes existantes (comme ajuster la propriété constant sur certaines contraintes), placez-le dans updateConstraints mais en dehors du check pour didSetupConstraints afin qu'il puisse exécuter toutes les sont disposées dans une distinct de la mode.)

    par exemple, si vous affichiez un message de courriel dans chaque cellule, vous pourriez avoir 4 mises en page uniques: des messages avec juste un sujet, des messages avec un sujet et un corps, des messages avec un sujet et une pièce jointe photo, et des messages avec un sujet, un corps, et une pièce jointe photo. Chaque mise en page a des contraintes complètement différentes nécessaires pour l'atteindre, donc une fois que la cellule est initialisée et les contraintes sont ajoutées pour l'une de ces cellules types, la cellule doit obtenir un unique réutilisation identifiant spécifique à ce type de cellule. Cela signifie que lorsque vous définissez une cellule à réutiliser, les contraintes ont déjà été ajoutées et sont prêtes à être utilisées pour ce type de cellule.

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

    • ne pas ajouter de cellules avec des ensembles complètement différents de contraintes au même pool de réutilisation (c'est-à-dire utiliser le même identificateur de réutilisation), puis tenter d'éliminer les anciennes contraintes et de créer de nouvelles contraintes à partir de zéro après chaque commande. Le moteur de mise en page automatique interne n'est pas conçu pour gérer les changements à grande échelle dans les contraintes, et vous verrez des problèmes de performance massifs.

    pour iOS 8-cellules auto-Dimensionnantes

    3. Permettre À La Hauteur De La Ligne D'Estimation

    pour activer les cellules auto-dimensionnantes de vue de table, vous devez définir la vue de table rowHeight propriété à la Viewautomaticdimension Utilisable. Vous devez également attribuez une valeur à la propriété estimatedRowHeight. Dès que les deux ces propriétés sont définies, le système utilise la disposition automatique pour calculer le ligne, de hauteur réelle

    Apple: Travailler avec l'Auto-Dimensionnement de la Vue de la Table de Cellules

    avec iOS 8, Apple a internalisé une grande partie du travail qui auparavant devait être mis en œuvre par vous avant iOS 8. Pour permettre au mécanisme de la cellule à taille automatique de fonctionner, vous devez d'abord définir la propriété rowHeight sur la table vue à la constante UITableViewAutomaticDimension . Ensuite, vous avez simplement besoin d'activer l'estimation de la hauteur de ligne en mettant la table voir estimatedRowHeight propriété à une valeur non nulle, par exemple:

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

    ce que cela fait est de fournir la vue de table avec une estimation temporaire/espace réservé 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 rangée sera calculée. Pour déterminer la hauteur réelle pour chaque rangée, la vue de table demande automatiquement à chaque cellule quelle hauteur son contentView doit être basée sur le fixe connu largeur de la vue de contenu (qui est basée sur la largeur de la vue de table, moins toutes les choses supplémentaires comme un index de section ou une vue d'accessoire) et les contraintes de mise en page automatique que vous avez ajoutées à la vue de contenu et aux sous-vues de la cellule. Une fois que la hauteur réelle de la cellule a été déterminée, l'ancienne hauteur estimée pour la rangée est mise à jour avec la nouvelle hauteur réelle (et tous les ajustements à la contentSize/contentOffset de la vue de table sont faits comme nécessaire pour vous).

    D'une manière générale, le l'estimation que vous fournissez n'a pas à être très précise -- elle n'est utilisée que pour évaluer correctement l'indicateur de défilement dans la vue de la table, et la vue de la table fait un bon travail de réglage de l'indicateur de défilement pour les estimations incorrectes que vous faites défiler les cellules à l'écran. Vous devez définir la propriété estimatedRowHeight sur la vue de la table (dans viewDidLoad ou similaire) à une valeur constante qui est la hauteur" moyenne " de la rangée. uniquement si les hauteurs de vos rangs présentent une variabilité extrême (par exemple, elles diffèrent par un ordre de magnitude) et vous remarquez l'indicateur de défilement "saut" que vous faites défiler si vous vous embêtez à mettre en œuvre tableView:estimatedHeightForRowAtIndexPath: pour faire le calcul minimal requis pour retourner une estimation plus précise pour chaque ligne.

    Pour iOS 7 support de la mise en œuvre automatique de la cellule de dimensionnement vous-même)

    3. Faire une mise en page Pass & Get the Cell Height

    d'abord, instanciez une instance hors-écran d'une cellule de vue de table, une instance pour chaque identificateur de réutilisation , qui est utilisé strictement pour les calculs de hauteur. (Hors-écran signifiant que la référence de la cellule est stockée dans une propriété/ivar sur le contrôleur de vue et jamais retourné de tableView:cellForRowAtIndexPath: pour la vue de table à rendre réellement à l'écran.) Ensuite, la cellule doit être configurée avec le contenu exact (texte, images, etc.) qu'elle contiendrait si elle était affichée dans la vue de table.

    puis, forcer la cellule à immédiatement mettre en place ses subviews, et ensuite, utilisez la méthode systemLayoutSizeFittingSize: sur le UITableViewCell 's contentView pour savoir quelle est la hauteur requise de la cellule. Utiliser UILayoutFittingCompressedSize pour obtenir la plus petite taille pour s'adapter à tous le contenu de la cellule. La hauteur peut alors être retournée à partir de la méthode tableView:heightForRowAtIndexPath: .

    4. Utiliser Les Hauteurs De Rangée Estimées

    si votre vue de table a plus d'une douzaine de lignes en elle, vous trouverez que faire la contrainte de mise en page automatique résoudre peut rapidement glisser le long du fil principal lors du premier chargement de la vue de la table, comme tableView:heightForRowAtIndexPath: est appelé sur chaque ligne lors de la première charge (afin de calculer la taille de l'indicateur de défilement).

    à partir de iOS 7, vous pouvez (et devez absolument) utiliser la propriété estimatedRowHeight sur la vue de la table. Ce que cela fait est de fournir à la vue de tableau une estimation temporaire / espace réservé pour les hauteurs de ligne des cellules qui ne sont pas encore à l'écran. Puis, quand ces cellules sont sur le point de défiler à l'écran, la hauteur réelle de la rangée sera calculée (en appelant tableView:heightForRowAtIndexPath: ), et la hauteur estimée sera mise à jour avec la hauteur réelle.

    de façon générale, l'estimation que vous fournissez n'a pas à être très exacte -- elle n'est utilisée que pour évaluer correctement l'indicateur de défilement dans la vue de la table, et la vue de la table fait un bon travail de réglage de l'indicateur de défilement pour des estimations incorrectes que vous faites défiler les cellules à l'écran. Vous devez mettre la propriété estimatedRowHeight sur la table voir (dans viewDidLoad ou similaire) une valeur constante qui est la hauteur" moyenne " de la rangée. seulement si la hauteur de votre rangée présente une variabilité extrême (p. ex., une différence d'un ordre de grandeur) et que vous remarquez l'indicateur de défilement" sautant "au fur et à mesure que vous défilez, vous devriez prendre la peine de mettre en œuvre tableView:estimatedHeightForRowAtIndexPath: pour effectuer le calcul minimal requis afin de retourner une estimation plus précise pour chaque rangée.

    5. (Si Nécessaire) Ajouter La Hauteur De La Rangée Caching

    Si vous avez fait tout ce qui précède et que vous trouvez toujours que la performance est d'une lenteur inacceptable lors de la résolution de la contrainte dans tableView:heightForRowAtIndexPath: , vous aurez malheureusement besoin de mettre en place une mise en cache pour les hauteurs de cellules. (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. Le truc, bien sûr, est pour s'assurer que vous nettoyez la hauteur de cache pour une cellule quand n'importe quoi arrive qui pourrait causer la hauteur de la cellule de changer -- principalement, ce serait quand le contenu de cette cellule change ou quand d'autres événements importants se produisent (comme l'utilisateur ajustant le curseur de taille de texte de Type dynamique).

    iOS 7 Code D'échantillon Générique (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 multi-line 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 fonctionnels de vues de table avec des hauteurs de lignes variables en raison de cellules de vue de table contenant un contenu dynamique dans UILabels.

    Xamarin (C#/.NET)

    si vous utilisez Xamarin, consultez ce exemple de projet réalisé par @KentBoogaart .

2289
répondu smileyborg 2018-01-24 08:24:23
la source

Pour IOS8 c'est très simple:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

ou

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

mais pour IOS7, la clé est de calculer la hauteur après l'autolayout,

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

Important

  • si plusieurs lignes d'étiquettes, n'oubliez pas de définir le numberOfLines à 0 .

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

le code d'exemple complet est ici .

131
répondu William Hu 2017-08-16 16:35:18
la source

Swift exemple de variable de la hauteur de UITableViewCell

mise à jour pour Swift 3

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

Le projet fini devrait ressembler à ceci:

enter image description here

créer un nouveau projet

il peut être juste une Application de vue unique.

ajouter le code

ajoutez un nouveau fichier Swift à votre projet. Appelez-le MyCustomCell. Cette classe tiendra les sorties pour les vues que vous ajoutez à votre cellule dans le storyboard. Dans cette exemple nous n'aurons plus qu'une étiquette dans chaque cellule.

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

nous allons connecter cette prise plus tard.

Open ViewController.swift et assurez-vous que vous avez le 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).")
    }
}

Remarque Importante:

  • ce sont les deux lignes de code suivantes (avec la disposition automatique) qui font la hauteur de la cellule variable possible:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Installation de la table de montage séquentiel

ajoutez une vue de Table à votre contrôleur de vue et utilisez la disposition automatique pour l'épingler aux quatre côtés. Puis faites glisser une cellule de vue de Table sur la vue de Table. Et sur la cellule Prototype, glissez une étiquette. Utilisez la mise en page automatique pour épingler l'étiquette aux quatre bords de la vue de contenu de la cellule de vue de Table.

enter image description here

remarque Importante:

  • Auto layout fonctionne avec les deux lignes de code importantes que j'ai mentionnées ci-dessus. Si vous n'utilisez pas auto layout, ça ne marchera pas.

autres réglages IB

nom et Identificateur de classe personnalisé

sélectionnez la cellule de vue de Table et définissez la classe personnalisée à être MyCustomCell (le nom de la classe dans le fichier Swift que nous avons ajouté). Également définir l'Identifiant cell (la même chaîne que nous avons utilisé pour la cellReuseIdentifier dans le code ci-dessus.

enter image description here

Lignes de Zéro pour le Label

fixe le nombre de lignes à 0 dans votre étiquette. Cela signifie multi-ligne et permet à l'étiquette de se redimensionner basé sur de son contenu.

enter image description here

s'accrocher les points de vente

  • traînée de contrôle de la vue de la Table dans le storyboard à la variable tableView dans le code ViewController .
  • faites la même chose pour L'étiquette dans votre cellule Prototype à la variable myCellLabel dans la classe MyCustomCell .

fini

vous devriez être en mesure d'exécuter votre projet maintenant et obtenir des cellules avec des hauteurs variables.

Notes

  • cet exemple ne fonctionne que pour iOS 8 et après. Si vous avez toujours besoin de soutenir iOS 7 alors cela ne fonctionnera pas pour vous.
  • vos propres cellules personnalisées dans vos futurs projets auront probablement plus d'un label. Assurez-vous que vous obtenez tout épinglé droit ainsi cette disposition automatique peut déterminer la bonne hauteur à utiliser. Vous devrez peut-être aussi utiliser la résistance à la compression verticale et l'étreinte. Voir cet article pour plus d'informations à ce sujet.
  • si vous n'épinglez pas les bords d'attaque et de fuite (gauche et droite), vous devrez peut-être aussi régler l'étiquette preferredMaxLayoutWidth pour qu'elle sache quand aligner l'enveloppe. Par exemple, si vous aviez ajouté une contrainte horizontale centrale au label dans le projet ci-dessus plutôt que d'épingler les bords d'attaque et de fuite, vous devez ajouter cette ligne à la méthode tableView:cellForRowAtIndexPath :

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

voir aussi

75
répondu Suragch 2017-05-23 13:31:39
la source

j'ai emballé la solution iOS7 de @smileyborg dans une catégorie

j'ai décidé d'envelopper cette solution intelligente de @smileyborg dans une catégorie UICollectionViewCell+AutoLayoutDynamicHeightCalculation .

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

il ne tient pas compte de la mise en cache mais convient à mes besoins en ce moment. N'hésitez pas à copier, coller et pirater.

Uicollection Viewcell+Autolayoutdynamicheightcalcul.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 /q/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights-65418/"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 à iOS8, mais c'est pour le moment!

61
répondu Adam Waite 2017-03-20 20:28:24
la source

voici ma solution. Vous devez indiquer à TableView la hauteur estimée avant de charger la vue. Sinon, il ne pourra pas se comporter comme prévu.

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}
44
répondu eddwinpaz 2016-04-14 02:56:19
la source

la solution proposée par @smileyborg est presque parfaite. Si vous avez une cellule personnalisée et que vous voulez une ou plusieurs UILabel avec des hauteurs dynamiques, alors la méthode systemLayoutSizeFittingSize combinée avec autolayout activée renvoie une CGSizeZero à moins que vous ne déplaciez toutes les contraintes de votre cellule de la cellule à son contentView (comme suggéré par @TomSwift ici comment redimensionner superview pour s'adapter à tous les sous-versions avec autolayout? ).

pour ce faire, vous devez insérer le code suivant dans votre implémentation personnalisée de Viewcell (merci à @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];
    }
}

le mélange de la réponse de @smileyborg avec celle-ci devrait fonctionner.

43
répondu wildmonkey 2017-05-23 13:31:39
la source

un gotcha assez important que je viens de rencontrer pour poster comme une réponse.

la réponse de @smileyborg est en grande partie correcte. Cependant, si vous avez n'importe quel code dans la méthode layoutSubviews de votre classe de cellule personnalisée, par exemple en définissant le preferredMaxLayoutWidth , alors il ne sera pas exécuté avec ce code:

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

cela m'a un peu déconcerté. Puis j'ai réalisé que c'était parce que ça ne déclenchait que des 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 assez sûr que vous n'avez pas besoin d'appeler setNeedsLayout comme il devrait déjà être mis. Dans le cas où vous enregistrez une référence à une cellule, vous devriez probablement appeler. De toute façon, il ne faut pas mal quoi que ce soit.

une autre astuce si vous utilisez des sous-classes de cellules où vous définissez des choses comme preferredMaxLayoutWidth . Comme le mentionne @smileyborg, " votre table la cellule de vue n'a pas encore eu sa largeur fixée à la largeur de la vue de table". C'est vrai, et la difficulté, si vous faites votre travail dans votre sous-classe et non dans la vue du contrôleur. Toutefois, vous pouvez simplement définir le cadre de la cellule à ce point en utilisant la largeur de la table:

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 cache cette cellule particulière pour la réutiliser, mais c'est hors de propos).

23
répondu Bob Spryn 2013-12-03 10:03:00
la source

au cas où les gens auraient encore des problèmes avec ça. J'ai écrit un billet de blog rapide sur L'utilisation D'Autolayout avec Uitable views tirer parti D'Autolayout pour les hauteurs de cellules dynamiques ainsi que d'un composant open source pour aider à rendre ce plus abstrait et plus facile à mettre en œuvre. https://github.com/Raizlabs/RZCellSizeManager

20
répondu Alex Rouse 2014-03-05 19:53:36
la source

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

méfiez-vous de la question suivante dans le Xcode 7.x, qui pourrait être une source de confusion:

Interface Builder ne gère pas la configuration de la cellule de dimensionnement automatique correctement. Même si vos contraintes sont absolument valables, IB se plaindra et vous donnera des suggestions et des erreurs confuses. La raison en est que L'IB n'est pas disposé à changer la hauteur de la rangée comme vos contraintes le dictent (de sorte que la cellule s'adapte autour de votre contenu). Au lieu de cela, il maintient la hauteur de la rangée fixe et commence à vous suggérer de changer vos contraintes, qui vous devriez ignorer .

par exemple, imaginez que vous avez tout mis en place, aucun avertissement, Aucune Erreur, tout fonctionne.

enter image description here

maintenant si vous changez la taille de la police (dans cet exemple je change la description taille de l'étiquette de 17.0 à 18.0).

enter image description here

parce que la taille de la police a augmenté, le label veut maintenant occuper 3 lignes (avant cela il occupait 2 lignes).

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

enter image description here

vous devriez ignorer ces avertissements. Ce que vous pouvez* faire à la place est de changer manuellement la hauteur de la rangée dans (Sélectionnez la cellule > inspecteur de la taille > Hauteur de la rangée).

enter image description here

je changeais cette hauteur un clic à la fois (en utilisant le pas Haut/Bas) jusqu'à ce que le rouge flèche erreurs disparaissent! (vous obtiendrez en fait des avertissements jaunes, à ce moment-là allez-y et faites 'update frames', tout devrait fonctionner).

* notez que vous n'avez pas à résoudre ces erreurs rouges ou ces avertissements jaunes dans Interface Builder - à l'exécution, tout fonctionnera correctement (même si IB affiche des erreurs/avertissements). Assurez-vous simplement qu'à l'exécution dans le journal de bord de la console vous n'avez pas d'erreurs D'AutoLayout.



En fait, essayer de Toujours mettre à jour la hauteur des rangées dans IB est super ennuyeux et parfois proche de l'impossible (en raison de valeurs fractionnaires).



Pour éviter les avertissements/erreurs IB gênants, vous pouvez sélectionner les vues impliquées et dans Size Inspector pour la propriété Ambiguity choisir Verify Position Only

enter image description here


Xcode 8.X / Xcode 9.x semble (parfois) faire les choses différemment de Xcode 7.x, mais toujours incorrect. Par exemple, même lorsque compression resistance priority / hugging priority sont définis à required (1000), Interface Builder pourrait étirer ou clip une étiquette pour s'adapter à la cellule (au lieu de redimensionner la hauteur de la cellule pour s'adapter autour de l'étiquette). Et dans un tel cas, il pourrait même ne pas afficher d'avertissements ou d'erreurs de mise à jour automatique. Ou parfois il fait exactement ce que Xcode 7.x n'a été décrite ci-dessus.

18
répondu Nikolay Suvandzhiev 2017-07-25 11:47:55
la source

aussi longtemps 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.

17
répondu Chris Van Buskirk 2016-05-13 01:11:35
la source

comme @Bob-Spryn je suis tombé sur un gotcha assez important que je poste ceci comme une réponse.

j'ai lutté avec la réponse de @smileyborg pendant un certain temps. Le gotcha que j'ai rencontré est si vous avez défini votre cellule prototype en IB avec des éléments supplémentaires ( UILabels , UIButtons , etc.) en IB lorsque vous instanciez la cellule avec [ [YourTableViewCellClass alloc] init] il n'instanciera pas tous les autres éléments dans cette cellule à moins que vous avez écrit le code pour le faire. (J'ai eu une expérience similaire avec initWithStyle .)

pour avoir le storyboard instancier tous les éléments supplémentaires obtenir votre cellule avec [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (pas [tableView dequeueReusableCellWithIdentifier:forIndexPath:] car cela va causer des problèmes intéressants.) Lorsque vous faites cela, tous les éléments que vous avez définis dans IB seront instanciés.

15
répondu nickb 2017-05-23 15:18:33
la source

Dynamique de la Vue de la Table Hauteur de Cellule et Mise en page Automatique

une bonne façon de résoudre le problème avec storyboard Auto Layout:

- (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;
}
14
répondu Mohnasm 2014-11-05 12:34:38
la source

pour régler la dimension automatique de la hauteur de rangée et de la hauteur de rangée estimée, assurez-vous des étapes suivantes pour rendre la dimension automatique efficace pour la disposition de la hauteur de cellule/rangée.

  • Attribuer et de mettre en œuvre tableview source de données et délégué
  • attribuer UITableViewAutomaticDimension à rowHeight & estimatedRowHeight
  • mettre en œuvre les méthodes délégué/source de données (c.-à-d. heightForRowAt et retourner une valeur UITableViewAutomaticDimension à it)

-

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;
}

Swift:

@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
}

For label instance in Uitable Viewcell

  • Définir le nombre de lignes = 0 (& saut de ligne mode = tronquer la queue)
  • définit toutes les contraintes (en haut, en bas, à droite à gauche) en ce qui concerne son conteneur superview/ cell.
  • Optionnel : définir la hauteur minimale pour l'étiquette, si vous voulez que la surface verticale minimale soit couverte par l'étiquette, même s'il n'y a pas de données.

enter image description here

Note : si vous avez plus d'une étiquette (Éléments) de longueur dynamique, qui doit être ajustée en fonction de la taille de son contenu: Réglez la priorité "contenu câlin et résistance à la Compression" pour les étiquettes que vous souhaitez étendre/compresser avec priorité.

14
répondu Krunal 2018-06-05 17:23:22
la source

une autre "solution": sauter toute cette frustration et utiliser UIScrollView à la place pour obtenir un résultat qui ressemble et se sent identique à Uitablview.

qui était la" solution " douloureuse pour moi, après avoir mis dans littéralement 20+ heures très frustrant total essayer de construire quelque chose comme ce que smileyborg a suggéré et l'échec sur de nombreux mois et trois versions de sorties App Store.

mon point de vue est que si vous avez vraiment besoin de soutien iOS 7 (pour nous, c'est essentiel), la technologie est tout simplement trop fragile et vous tirez vos cheveux en essayant. Et que la vue UITableView est généralement complète sauf si vous utilisez certaines des fonctionnalités avancées d'édition de ligne et / ou avez vraiment besoin de supporter 1000+ "lignes" (dans notre application, il est réaliste jamais plus de 20 lignes).

le bonus ajouté est que le code devient incroyablement simple contre toutes les conneries de délégué et de va-et-vient qui viennent avec UITableView. C'est juste une seule boucle de code en viewOnLoad qui semble élégant et facile à gérer.

Voici quelques conseils sur la façon de le faire:

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

2) glissez un UIScrollView sur votre vue racine.

3) Ajouter des contraintes les contraintes de haut, de bas, de gauche et de droite à la vue de haut niveau pour que la vue UIScrollView remplisse l'ensemble de la vue racine.

4) Ajouter un uivi à l'intérieur de L'UIScrollView et l'appeler"conteneur". Ajouter des contraintes en haut, en bas, à gauche et à droite à L'UIScrollView (son parent). Astuce: ajouter également des contraintes" d'égale largeur " pour relier UIScrollView et UIView.

vous obtiendrez une erreur "la vue scroll a une hauteur de contenu scrollable ambiguë" et que votre conteneur UIView devrait avoir une hauteur de 0 pixels. Ni erreur semble à la matière 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 Non Uitable Viewcell.

5) dans votre Controller root ViewController, vous ajoutez essentiellement toutes les "lignes" à l'uivi du conteneur et ajoutez programmatiquement des contraintes reliant leurs bords gauche et droit à la vue du conteneur, leurs bords supérieurs à la vue du conteneur en haut (pour le premier élément) ou la cellule précédente. Puis relier la dernière cellule au fond du conteneur.

pour nous, chaque "rang" est une plume de fichier. 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" gotcha "que j'ai trouvé avec cette approche jusqu'à présent est que Uitablview a une belle caractéristique des en-têtes de section" flottant " au sommet de la vue pendant que 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 il disparaître.

si vous voulez des diviseurs entre vos cellules, il suffit d'ajouter un uivi de 1 pixel de haut au bas de votre" cellule " personnalisée qui ressemble à un diviseur.

assurez-vous d'allumer "rebonds" et "rebondir verticalement" pour que le contrôle de rafraîchissement fonctionne et il semble plus comme un tableview.

TableView affiche quelques lignes vides et des diviseurs sous votre contenu, s'il ne remplit pas l'écran complet où comme cette solution ne le fait pas. Mais personnellement, Je préfère si ces lignes vides n'étaient pas là de toute façon - avec la hauteur de la cellule variable il a toujours semblé "buggy" pour moi de toute façon d'avoir les lignes vides là-dedans.

voici espérer un autre programmeur lit mon poste avant de gaspiller plus de 20 heures à essayer de comprendre avec la vue de Table dans leur propre application. :)

12
répondu alpsystems.com 2015-06-15 01:17:20
la source
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

enter image description here

10
répondu Abo3atef 2016-10-30 15:43:50
la source

j'ai dû utiliser des vues dynamiques (setup views et contraintes par code) et quand j'ai voulu mettre preferredMaxLayoutWidth la largeur du label était 0. Si j'ai tort la hauteur de la cellule.

puis j'ai ajouté

[cell layoutSubviews];

avant l'exécution

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

après que la largeur de cette étiquette a été comme prévu et la hauteur dynamique a été calcul droit.

9
répondu Mansurov Ruslan 2015-09-02 01:37:52
la source

disons que vous avez une cellule avec un subview, et vous voulez que la hauteur de la cellule soit assez élevée pour englober le sous-Affichage + rembourrage.

1) Définissez la contrainte inférieure de subview égale à la cellule.contentView sans le rembourrage que vous voulez. Ne pas imposer de contraintes à la cellule ou à la cellule.contentView lui-même.

2) Définissez rowHeight de tableView ou tableView:heightForRowAtIndexPath: à UITableViewAutomaticDimension .

3) Régler soit le la propriété estimatedRowHeight de tableView ou tableView:estimatedHeightForRowAtIndexPath: pour une meilleure estimation de la hauteur.

C'est ça.

6
répondu Vadoff 2016-07-18 20:37:44
la source

si vous faites une mise en page programmatique, 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: Mettez ces deux propriétés de tableview sur viewDidLoad, le premier dit à tableview qu'il devrait s'attendre à des tailles dynamiques sur leurs cellules, le second est juste de laisser l'application calculer la taille de l'indicateur de barre de défilement, de sorte qu'il aide pour la performance.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

numéro 2: c'est important que vous ayez besoin d'ajouter les sous-vues au contentView de la cellule pas à la vue, et aussi d'utiliser son layoutsmarginguide pour ancrer les sous-vues en haut et en bas, c'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ée une méthode qui ajoutera les sous-vues et exécutera la mise en page, l'appellera dans la méthode init.

NUMÉRO 3: N'APPELEZ PAS LA MÉTHODE:

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

Si vous le faites, vous va annuler votre implémentation.

suivez ces 3 règles pour les cellules dynamiques dans tableviews.

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

3
répondu James Rochabrun 2017-04-28 18:59:47
la source

dans mon cas je dois créer une cellule personnalisée avec une image qui vient du serveur et peut être de n'importe quelle largeur et hauteur. Et deux UILabels de dimensions dynamiques(largeur et hauteur)

j'ai réalisé la même chose ici dans ma réponse avec autolayout et programmatically:

fondamentalement au-dessus de @smileyBorg réponse aidé mais systemLayoutSizeFittingSize n'a jamais fonctionné pour moi, dans mon approche:

1. Pas d'utilisation de l'automatique propriété de calcul de la hauteur de la rangée. 2.Pas d'utilisation de la hauteur estimée 3.Pas besoin de mises à jour inutiles. 4.Pas D'utilisation de la largeur de disposition Max préférée automatique. 5. Pas d'utilisation de systemLayoutSizeFittingSize (devrait avoir l'utilisation 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'il fait en interne.

est - il possible d'avoir des hauteurs différentes Cellule de visualisation utilisable lorsque j'utilise plusieurs façons différentes d'afficher la cellule?

0
répondu Alok 2017-05-23 13:31:39
la source

dans mon cas, le rembourrage était dû à la hauteur du profilé et du profilé, où le storyboard m'a permis de le changer au minimum 1. Ainsi, dans la méthode viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
0
répondu Rashid 2017-01-25 10:35:59
la source

je viens de faire un essai stupide et une erreur avec les 2 valeurs de rowHeight et estimatedRowHeight et j'ai juste pensé que cela pourrait fournir quelques idées de débogage:

si vous les définissez tous les deux ou seulement le estimatedRowHeight vous obtiendrez le comportement désiré:

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

il est suggéré que vous faites de votre mieux pour obtenir la bonne estimation, mais le résultat final n'est pas différent. Ça affectera ta performance.

enter image description here


Si vous ne réglez rowHeight c'est à dire uniquement:

tableView.rowHeight = UITableViewAutomaticDimension

votre résultat final ne serait pas comme il le souhaite:

enter image description here


si vous mettez le estimatedRowHeight à 1 ou plus petit, alors vous aurez crash indépendamment du rowHeight .

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

j'ai crashé 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
0
répondu Honey 2017-11-25 04:56:43
la source

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

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

d'être peu fiable dans certains cas où les contraintes sont ambiguës. Il est préférable de forcer le moteur de mise en page à calculer la hauteur dans une direction, en utilisant la catégorie helper 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
répondu railwayparade 2017-12-19 01:31:26
la source

Si vous avez une longue chaîne. par exemple, un qui n'a pas de rupture de ligne. Ensuite, vous vous risquez de rencontrer quelques problèmes.

le correctif 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 Suragh la réponse de le plus complet, tout en étant le moins déroutant.

Si non expliquer pourquoi . Faisons-le.

laisser tomber 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 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 pas ajouté de contraintes size . J'ai seulement ajouté des contraintes de centerx, centerY. Mais l'étiquette sera quand même dimensionnée correctement pourquoi? À cause de contentSize .

pour mieux traiter cela, garder d'abord l'étape 0, puis commenter les étapes 1 à 6. Laissez setupLayout() rester. Observer le comportement.

alors décrochez la première étape et observez.

puis décommentez l'étape 2 et observez.

faites ceci jusqu'à ce que vous ayez décompressé 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, alors votre largeur va augmenter
  2. rupture de ligne: si vous ajoutez \n , alors la largeur du contourintrinsèque 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 le numberOfLines à 0 sinon vous n'aurez pas de lignes multiples. Votre numberOfLines ajustera votre hauteur
  4. faire des ajustements: Imaginez que sur la base de votre texte, la largeur de votre conteneursintrinsèque était 200 et la hauteur était 100 , mais vous vouliez limiter la largeur au contenant de l'étiquette que comptez-vous faire? La solution est de définir une largeur désirée. Vous faites cela en mettant preferredMaxLayoutWidth à 130 alors votre nouvelle taille intrinsèque aura une largeur d'environ 130 . La hauteur serait évidemment plus que 100 parce que vous auriez besoin de plus de lignes. Cela étant dit, si vos contraintes sont définies correctement, alors vous n'aurez pas besoin d'utiliser cette à toutes! Pour plus de détails sur ce voir cette réponse et ses commentaires. Vous avez seulement besoin d'utiliser preferredMaxLayoutWidth si vous n'avez pas de contraintes limitant la largeur/hauteur comme on pourrait dire "ne pas envelopper le texte, à moins que il dépasse le preferredMaxLayoutWidth ". Mais avec 100% de certitude si vous mettez le premier/deuxième et numberOfLines à 0 alors vous êtes bon! longue histoire courte la plupart des réponses ici qui recommandent de l'utiliser sont fausses! Vous n'en avez pas besoin. En avoir besoin est un signe que vos contraintes ne sont pas définies correctement ou que vous n'avez tout simplement pas de contraintes

  5. Taille de la police: notez aussi que si vous augmentez votre fontSize alors le intrinsicContentSize hauteur va augmenter. Je ne l'ai pas montré dans mon code. Vous pouvez essayer sur votre propre.

donc retournez à votre exemple de tablewcell:

tout ce que vous devez faire est de mettre le numberOfLines à 0 et de mettre le preferredMaxLayoutWidth à votre tableView 's width .

0
répondu Honey 2018-10-09 17:32:30
la source

il suffit d'ajouter ces deux fonctions dans votre viewcontroller il résoudra votre problème. Ici, list est un tableau de chaînes de caractères 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
répondu Parth Barot 2018-05-16 09:08:31
la source

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
    }
}
-4
répondu djdance 2016-03-14 10:20:21
la source