Comment utilisez-vous la disposition automatique dans UITableViewCell
s dans une vue sous forme de tableau pour permettre au contenu et aux sous-vues de chaque cellule de déterminer la hauteur des lignes (elle-même/automatiquement), tout en maintenant des performances de défilement régulières?
TL; DR: Vous n'aimez pas lire? Passez directement aux exemples de projets sur GitHub:
Les 2 premières étapes ci-dessous sont applicables quelle que soit la version iOS pour laquelle vous développez.
Dans votre sous-classe UITableViewCell
, ajoutez des contraintes pour que les bords des sous-vues de la cellule soient épinglés aux bords du contenu de la cellule (plus important encore). vers le haut ET les bords inférieurs). REMARQUE: n'épinglez pas les vues secondaires dans la cellule elle-même; contentView
! de la cellule uniquement Laissez la taille de contenu intrinsèque de ces sous-vues piloter la hauteur de la vue du contenu de la cellule de la table en vous assurant que la compression du contenu Les contraintes de résistance et qui épousent le contenu dans la dimension verticale de chaque sous-vue ne sont pas remplacées par les contraintes de priorité supérieure que vous avez ajoutées. ( Hein? Cliquez ici. )
N'oubliez pas que l'idée est de connecter les sous-vues de la cellule verticalement à la vue de contenu de la cellule afin qu'elles puissent "exercer une pression" et faire en sorte que la vue du contenu soit agrandie. En utilisant un exemple de cellule avec quelques sous-vues, voici une illustration visuelle de ce que et certains (pas tous!) de vos contraintes devrait ressembler à ceci:
Vous pouvez imaginer qu'à mesure que du texte est ajouté à l'étiquette de corps multiligne dans la cellule exemple ci-dessus, il devra être agrandi verticalement pour s'adapter au texte, ce qui forcera effectivement la cellule à croître en hauteur. (Bien entendu, vous devez maîtriser les contraintes pour que cela fonctionne correctement!)
Définir correctement vos contraintes est certainement la partie la plus difficile et la plus importante pour obtenir des hauteurs de cellules dynamiques en travaillant avec la disposition 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 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 s'avérer aussi simple et nettement plus puissant qu'Interface Builder utilisant des ancres de présentation ou l'une des fantastiques API open source disponibles sur GitHub.
updateConstraints
de votre sous-classe UITableViewCell. Notez que updateConstraints
peut être appelé plus d'une fois. Par conséquent, pour éviter d'ajouter les mêmes contraintes plusieurs fois, veillez à envelopper le code d'ajout de contraintes dans updateConstraints
dans la recherche d'une propriété booléenne telle que didSetupConstraints
(que vous définissez sur OUI après avoir exécuté votre code d'ajout de contrainte une fois). Par contre, si vous avez un code qui met à jour les contraintes existantes (telles que le réglage de la propriété constant
sur certaines contraintes), placez-le dans updateConstraints
mais en dehors de la vérification de didSetupConstraints
peut être exécuté à chaque appel de la méthode.Pour chaque ensemble unique de contraintes dans la cellule, utilisez un identifiant unique de réutilisation de cellule. En d'autres termes, si vos cellules ont plus d'une disposition unique, chaque disposition unique doit recevoir son propre identifiant de réutilisation. (Il est judicieux d'utiliser un nouvel identifiant de réutilisation si votre variante de cellule comporte un nombre différent de sous-vues ou si les sous-vues sont organisées de manière distincte.)
Par exemple, si vous affichez un message électronique dans chaque cellule, vous pouvez avoir 4 présentations uniques: des messages avec uniquement un objet, des messages avec un sujet et un corps, des messages avec un objet et une pièce jointe avec photo, et des messages avec un objet, corps et pièce jointe photo. Chaque mise en page requiert des contraintes complètement différentes. Ainsi, une fois la cellule initialisée et les contraintes ajoutées pour l'un de ces types de cellules, la cellule doit obtenir un identifiant de réutilisation unique propre à ce type de cellule. Cela signifie que lorsque vous retirez une cellule de la file d'attente pour la réutiliser, les contraintes ont déjà été ajoutées et sont prêtes pour ce type de cellule.
Notez qu'en raison de différences de taille de contenu intrinsèque, les cellules ayant les mêmes contraintes (type) peuvent toujours avoir des hauteurs variables! Ne confondez pas des dispositions fondamentalement différentes (contraintes différentes) avec différents cadres de vue calculés (résolus à partir de contraintes identiques) en raison de différentes tailles de contenu.
Pour activer les cellules de la vue de table à taille automatique, vous devez définir la propriété rowHeight de la vue de table sur UITableViewAutomaticDimension. Vous devez également affecter une valeur à la propriété ratedRowHeight. 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: tilisation de cellules d'affichage de la table à redimensionnement automatique
Avec iOS 8, Apple a internalisé une grande partie du travail que vous deviez auparavant mettre en œuvre avant iOS 8. Pour que le mécanisme de cellule à dimensionnement automatique fonctionne, vous devez d'abord définir le paramètre rowHeight
propriété de la vue tabulaire à la constante UITableViewAutomaticDimension
. Ensuite, vous devez simplement activer l'estimation de la hauteur des lignes en définissant la propriété estimatedRowHeight
de la vue 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 tableau 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 est calculée. Pour déterminer la hauteur réelle de chaque ligne, la vue tabulaire demande automatiquement à chaque cellule quelle hauteur sa contentView
doit être basée sur la largeur fixe connue de la vue de contenu (qui est basée sur la largeur de la vue tabulaire, moins les valeurs supplémentaires). un index de section ou une vue accessoire) et les contraintes de présentation 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 de la ligne est mise à jour avec la nouvelle hauteur réelle (et tout ajustement de la propriété contentSize/contentOffset de la vue tableau est effectué selon vos besoins).
En règle générale, l'estimation que vous fournissez ne doit pas nécessairement être très précise. Elle sert uniquement à dimensionner correctement l'indicateur de défilement dans la vue tabulaire. Cette dernière permet également d'ajuster l'indicateur de défilement lorsque les estimations sont incorrectes. faire défiler les cellules à l'écran. Vous devez définir la propriété estimatedRowHeight
dans la vue tabulaire (dans viewDidLoad
ou similaire) sur une valeur constante correspondant à la hauteur de ligne "moyenne". Seulement si la hauteur de vos rangées est extrêmement variable (par exemple, si vous la différenciez d'un ordre de grandeur) et si vous remarquez l'indicateur de défilement "sauter" lorsque vous faites défiler si vous vous donnez la peine d'implémenter tableView:estimatedHeightForRowAtIndexPath:
pour effectuer le calcul minimal. nécessaire pour renvoyer une estimation plus précise pour chaque ligne.
Commencez par instancier une instance hors écran d'une cellule de vue tableau, , une instance pour chaque identificateur de réutilisation , utilisée strictement pour les calculs de hauteur. (Offscreen 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 renvoyée de tableView:cellForRowAtIndexPath:
pour que la vue de table soit rendue à l’écran.) Ensuite, la cellule doit être configurée avec le contenu exact (par exemple, le texte). , images, etc.) qu’il conserverait s’il était affiché dans la vue tableau.
Ensuite, forcez la cellule à immédiatement mettre en forme ses sous-vues, puis utilisez la méthode systemLayoutSizeFittingSize:
sur les UITableViewCell
's contentView
pour connaître la hauteur requise de la cellule. Utilisez UILayoutFittingCompressedSize
pour obtenir la plus petite taille requise pour tout le contenu de la cellule. La hauteur peut alors être renvoyée à partir de la méthode tableView:heightForRowAtIndexPath:
delegate.
Si votre vue tableau contient plus d'une vingtaine de lignes, vous constaterez que la résolution des contraintes de mise en forme automatique peut rapidement alourdir le thread principal lors du premier chargement de la vue tableau, car tableView:heightForRowAtIndexPath:
est appelé à chaque fois. rangée 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 propriété estimatedRowHeight
dans la vue Table. Cela permet de fournir à la vue tableau 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 est calculée (en appelant tableView:heightForRowAtIndexPath:
) et la hauteur estimée est mise à jour avec la taille réelle.
En règle générale, l'estimation que vous fournissez ne doit pas nécessairement être très précise. Elle sert uniquement à dimensionner correctement l'indicateur de défilement dans la vue tabulaire. Cette dernière permet également d'ajuster l'indicateur de défilement lorsque les estimations sont incorrectes. faire défiler les cellules à l'écran. Vous devez définir la propriété estimatedRowHeight
dans la vue tabulaire (dans viewDidLoad
ou similaire) sur une valeur constante correspondant à la hauteur de ligne "moyenne". Seulement si la hauteur de vos rangées est extrêmement variable (par exemple, si vous la différenciez d'un ordre de grandeur) et si vous remarquez l'indicateur de défilement "sauter" lorsque vous faites défiler si vous vous donnez la peine d'implémenter tableView:estimatedHeightForRowAtIndexPath:
pour effectuer le calcul minimal. nécessaire pour renvoyer une estimation plus précise pour chaque ligne.
Si vous avez fait tout ce qui est décrit ci-dessus et constatez toujours que les performances sont trop lentes lorsque vous résolvez la contrainte dans tableView:heightForRowAtIndexPath:
, vous devrez malheureusement implémenter une certaine mise en cache pour les hauteurs de cellules. (C'est l'approche proposée par les ingénieurs d'Apple.) L'idée générale est de laisser le moteur de présentation 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 relatives à la hauteur de cette cellule. Bien entendu, l’astuce consiste à vider la cellule de la hauteur mise en cache lorsque tout ce qui pourrait modifier la hauteur de la cellule - en particulier lorsque le contenu de cette cellule est modifié ou lorsque d’autres événements importants se produisent (par exemple, l’utilisateur le curseur de taille de texte de type dynamique).
- (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;
}
}
Ces projets sont des exemples fonctionnels de vues de tableau avec des hauteurs de ligne variables en raison des cellules de vue de tableau contenant un contenu dynamique dans les étiquettes UIL.
Si vous utilisez Xamarin, regardez ceci exemple de projet assemblé par @ KentBoogaart .
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 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 sont étiquetées, n'oubliez pas de définir numberOfLines
sur 0
.
N'oubliez pas label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
L'exemple de code complet est ici .
EDIT Swift 4.2 UITableViewAutomaticDimension
remplacé par UITableView.automaticDimension
_ {Mis à jour pour Swift 3} _
La réponse de William Hu à Swift est bonne, mais elle m'aide à suivre quelques étapes simples mais détaillées pour apprendre à faire quelque chose pour la première fois. L'exemple ci-dessous est mon projet test tout en apprenant à créer une variable UITableView
avec des hauteurs de cellules variables. Je me suis basé sur cet exemple UITableView de base pour Swift .
Le projet fini devrait ressembler à ceci:
Il peut s'agir simplement d'une application à vue unique.
Ajoutez un nouveau fichier Swift à votre projet. Nommez-le MyCustomCell. Cette classe contient les points de vente des vues que vous ajoutez à votre cellule dans le storyboard. Dans cet exemple de base, nous n’avons 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 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).")
}
}
Note importante:
Ce sont les deux lignes de code suivantes (avec la disposition automatique) qui rendent possible la hauteur de cellule variable:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
Ajoutez une vue Table à votre contrôleur de vue et utilisez la disposition automatique pour l'épingler aux quatre côtés. Faites ensuite glisser une cellule de la vue tableau dans la vue tableau. Et sur la cellule Prototype, faites glisser une étiquette. Utilisez la mise en page automatique pour épingler l'étiquette sur les quatre bords de la vue Contenu de la cellule de la vue Tableau.
Note importante:
Nom et identifiant de la classe personnalisée
Sélectionnez la cellule d'affichage de la table 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'identificateur sur cell
(la même chaîne que celle que nous avons utilisée pour cellReuseIdentifier
dans le code ci-dessus.
Zero Lines for Label
Définissez le nombre de lignes sur 0
dans votre étiquette. Cela signifie plusieurs lignes et permet à l'étiquette de se redimensionner en fonction de son contenu.
Branchez les prises
tableView
dans le code ViewController
. myCellLabel
de la classe MyCustomCell
.Vous devriez pouvoir exécuter votre projet maintenant et obtenir des cellules de hauteur variable.
Si vous n'épinglez pas les bords avant et arrière (gauche et droit), vous devrez peut-être également définir la variable preferredMaxLayoutWidth
de l'étiquette de sorte qu'elle sache quand le retour à la ligne est terminé. Par exemple, si vous aviez ajouté une contrainte de centrer horizontalement à l'étiquette dans le projet ci-dessus plutôt que d'épingler les arêtes avant et arrière, vous devrez alors ajouter cette ligne à la méthode tableView:cellForRowAtIndexPath
:
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
J'ai décidé de placer 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'une nib et systemLayoutSizeFittingSize:
retournant CGRectZero
).
Il ne prend en compte aucune mise en cache, mais répond à mes besoins pour le moment. N'hésitez pas à copier, coller et pirater.
#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
#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
- (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 sur iOS8, mais le voilà pour l'instant!
Voici ma solution. Vous devez indiquer à la TableView la hauteur estimée avant 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 de Swift 4.2
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
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, la méthode systemLayoutSizeFittingSize associée à AutoLayout activé renvoie une CGSizeZero
à moins que vous ne déplaciez toutes vos contraintes de cellule de la cellule à sa contentView (comme suggéré par @TomSwift here Comment redimensionner les super vues pour s’adapter à toutes les sous-vues avec autolayout? ).
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 @smileyborg avec cela devrait fonctionner.
Je suis tombé sur un message assez important que je viens de rencontrer pour répondre.
La réponse de @ smileyborg est généralement correcte. Toutefois, si vous avez du code dans la méthode layoutSubviews
de votre classe de cellule personnalisée, par exemple si vous définissez la variable preferredMaxLayoutWidth
, il ne sera pas exécuté avec ce code:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
Cela m'a confondu pendant un moment. Ensuite, j'ai réalisé que c'est parce qu'ils déclenchent uniquement layoutSubviews sur la variable 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 quasiment sûr que vous n’avez pas besoin d’appeler setNeedsLayout
car elle devrait déjà être définie. Dans les cas où vous sauvegardez une référence à une cellule, vous devriez probablement l'appeler. De toute façon, ça ne devrait pas faire de mal.
Un autre conseil si vous utilisez des sous-classes de cellules pour lesquelles vous définissez des éléments tels que preferredMaxLayoutWidth
. Comme @smileyborg le mentionne, "la largeur de la cellule de votre vue tableau n'a pas encore été fixée à la largeur de la vue tableau". Ceci est vrai et pose des problèmes 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 la 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 m'est arrivé de mettre cette cellule en cache pour la réutiliser, mais ce n'est pas pertinent).
Au cas où les gens ont toujours des problèmes avec cela. J'ai écrit un article de blog rapide sur l'utilisation de Autolayout avec UITableViews Exploitation de l'autolayout pour Dynamic Cell Heights , ainsi qu'un composant open source pour aider à rendre ceci plus abstrait et plus facile à mettre en œuvre . https: // github. com/Raizlabs/RZCellSizeManager
(pour Xcode 8.x/Xcode 9.x lu en bas)
Méfiez-vous du problème suivant dans Xcode 7.x, qui pourrait être source de confusion:
Interface Builder ne gère pas correctement la configuration de la cellule à dimensionnement automatique. Même si vos contraintes sont tout à fait valables, IB continuera à vous plaindre et à vous donner des suggestions confuses et des erreurs. La raison en est que IB ne souhaite pas modifier la hauteur de la ligne en fonction de vos contraintes (pour que la cellule s'adapte à votre contenu). Au lieu de cela, il garde la hauteur de la ligne fixe et commence à vous suggérer de modifier vos contraintes, que vous devez ignorer.
Par exemple, imaginons que tout soit correct, sans avertissements ni erreurs, tout fonctionne.
Maintenant, si vous modifiez la taille de la police (dans cet exemple, je modifie la taille de la police de l'étiquette de description de 17,0 à 18,0).
Etant donné que la taille de la police a augmenté, l'étiquette souhaite désormais occuper 3 lignes (auparavant, elle occupait 2 lignes).
Si Interface Builder fonctionnait comme prévu, la hauteur de la cellule serait redimensionnée pour s'adapter à la nouvelle hauteur de l'étiquette. Cependant, ce qui se produit réellement, c’est que IB affiche l’icône rouge d’erreur de mise en page automatique et vous suggère de modifier les priorités de calage/compression.
Vous devriez ignorer ces avertissements. Au lieu de cela, vous pouvez * modifier manuellement la hauteur de la ligne (sélectionnez Cellule> Inspecteur de taille> Hauteur de ligne).
Je changeais cette hauteur un clic à la fois (en utilisant le stepper) jusqu'à ce que les erreurs de la flèche rouge disparaissent! (vous obtiendrez des avertissements jaunes, à quel moment allez-vous simplement faire la mise à jour des cadres, tout devrait fonctionner).
* Notez que vous n'avez pas réellement besoin de résoudre ces erreurs rouges ou ces avertissements 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 ne recevez aucune erreur AutoLayout.
En fait, essayer de toujours mettre à jour la hauteur des lignes dans IB est extrêmement ennuyeux et parfois presque impossible (à cause des valeurs fractionnaires) .
Pour éviter les avertissements/erreurs gênants de l’IB, vous pouvez sélectionner les vues concernées et dans Size Inspector
pour la propriété Ambiguity
choisir Verify Position Only
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 priority
est défini sur required (1000), Interface Builder peut étirer ou couper une étiquette pour l'adapter à la cellule (au lieu de redimensionner la hauteur de la cellule pour l'adapter à l'étiquette). Et dans un tel cas, il se peut même que les avertissements ou les erreurs AutoLayout ne s'affichent pas. Ou parfois, il fait exactement ce que Xcode 7.x a fait, décrit ci-dessus.
Pour définir la dimension automatique pour la hauteur de ligne et la hauteur estimée de la ligne, assurez-vous que les étapes suivantes permettent de rendre la cote automatique effective pour la présentation de hauteur de cellule/ligne.
UITableViewAutomaticDimension
à rowHeight & installedRowHeightheightForRowAt
et 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
Remarque : Si vous avez plusieurs étiquettes (UIElements) avec une longueur dynamique, ajustez-les en fonction de la taille de son contenu. priorité plus élevée.
Tant que votre mise en page 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.
Comme @ Bob-Spryn J'ai rencontré un casse-tête assez important pour que je poste ceci comme une réponse.
J'ai eu du mal avec @ smileyborg répondre pendant un moment. Le piège que j’ai rencontré est que si vous définissez votre cellule prototype dans IB avec des éléments supplémentaires (UILabels
, UIButtons
, etc.) dans IB, lorsque vous instanciez la cellule avec [[YourTableViewCellClass alloc] init]
, elle n’instanciera pas tous les autres éléments de cette cellule, sauf si vous avez écrit du code pour le faire. (J'ai eu une expérience similaire avec initWithStyle
.)
Pour que le storyboard instancie tous les éléments supplémentaires, procurez-vous votre cellule avec [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"]
(pas [tableView dequeueReusableCellWithIdentifier:forIndexPath:]
car cela poserait des problèmes intéressants.) Lorsque vous faites cela, tous les éléments que vous avez définis dans IB seront instanciés.
Hauteur de cellule et mise en forme automatique du tableau dynamique
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;
}
Une autre "solution": ignorez toutes ces frustrations et utilisez plutôt UIScrollView pour obtenir un résultat identique à UITableView.
C’était la "solution" douloureuse pour moi, après avoir mis littéralement au moins 20 heures très frustrantes à essayer de construire quelque chose comme ce que suggérait smileyborg, échouant au fil de nombreux mois et de trois versions des versions de l’App Store.
À mon avis, si vous avez vraiment besoin de la prise en charge d’iOS 7 (pour nous, c’est essentiel), la technologie est tout simplement trop fragile et vous allez vous arracher les cheveux. Et UITableView est en général excessif, sauf si vous utilisez certaines des fonctionnalités avancées d’édition de lignes et/ou si vous avez réellement besoin de prendre en charge plus de 1 000 "lignes" (dans notre application, ce n’est en réalité jamais plus de 20 lignes).
L'avantage supplémentaire est que le code devient incroyablement simple par rapport à toutes les conneries des délégués et à celles qui sont fournies avec UITableView. Dans viewOnLoad, c'est une seule boucle de code qui a l'air élégant et qui est facile à gérer.
Voici quelques conseils sur la façon de 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 le curseur sur UIScrollView sur votre vue racine.
3) Ajoutez des contraintes de haut, de bas, de gauche et de droite à la vue de niveau supérieur afin que UIScrollView remplisse la vue racine complète.
4) Ajoutez un UIView dans UIScrollView et appelez-le "conteneur". Ajoutez des contraintes en haut, en bas, à gauche et à droite à UIScrollView (son parent). KEY TRICK: Ajoutez également une contrainte "Equal widths" pour lier UIScrollView et UIView.
Vous obtiendrez une erreur "La vue de défilement a une hauteur de contenu ambiguë ambiguë" et que votre conteneur UIView doit avoir une hauteur de 0 pixel. Aucune erreur 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 pas UITableViewCell.
5) Dans votre ViewController racine, vous ajoutez essentiellement toutes les "lignes" au conteneur UIView et ajoutez par programme des contraintes liant leurs bords gauche et droit à la vue Conteneur, leurs bords supérieurs à la vue Conteneur supérieure (pour le premier élément) ou la cellule précédente. Ensuite, liez la dernière cellule au fond du conteneur.
Pour nous, chaque "ligne" est dans un fichier nib. Donc, le code ressemble à 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 possède une fonctionnalité intéressante 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 dans notre cas particulier, cette fonctionnalité n'était pas indispensable à 100% et personne ne l'avait remarquée.
Si vous souhaitez des séparations entre vos cellules, ajoutez simplement un UIView de 1 pixel de haut au bas de votre "cellule" personnalisée qui ressemble à un diviseur.
Veillez à activer les "rebonds" et les "rebonds à la verticale" pour que le contrôle d'actualisation fonctionne et ressemble davantage à une vue de table.
TableView affiche des lignes et des séparateurs vides sous votre contenu, s'il ne remplit pas tout l'écran, contrairement à cette solution. Mais personnellement, je préfère que ces lignes vides ne soient pas présentes de toute façon - avec une hauteur de cellule variable, il m’a toujours semblé "bogué" pour 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. :)
Je devais utiliser des vues dynamiques (vues d'installation et contraintes par code) et lorsque je voulais définir la largeur de l'étiquette preferredMaxLayoutWidth, elle était 0. J'ai donc une hauteur de cellule incorrecte.
Puis j'ai ajouté
[cell layoutSubviews];
avant d'exécuter
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
Après cette étiquette, la largeur était celle attendue et la hauteur dynamique calculait correctement.
Supposons que vous avez une cellule avec une sous-vue et que vous voulez 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 sur cell.contentView lui-même.
2) Définissez la propriété rowHeight
ou tableView:heightForRowAtIndexPath:
de la tableView sur UITableViewAutomaticDimension
.
3) Définissez la propriété estimatedRowHeight
ou tableView:estimatedHeightForRowAtIndexPath:
de la tableView de manière à obtenir la meilleure estimation de la hauteur.
C'est tout.
Si vous faites la mise en page par programme, voici ce qu’il faut prendre en compte pour iOS 10 utilisant des ancres dans Swift.
Il y a trois règles/étapes
NUMÉRO 1: définissez ces deux propriétés de table view sur viewDidLoad, la première indique à la table qui doit attendre des tailles dynamiques pour leurs cellules, la seconde consiste simplement à laisser l'application calculer la taille de l'indicateur de barre de défilement performance.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
NUMÉRO 2: Il est important d'ajouter les sous-vues à la contentView de la cellule et non à la vue, mais également d'utiliser son layoutsmarginguide pour ancrer les sous-vues vers le haut et le bas. Voici un exemple concret de la procédure à suivre.
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 va ajouter les sous-vues et effectuer 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 annulerez 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
Si vous avez une chaîne long. par exemple. un qui n'a pas un saut de ligne. Ensuite, vous risquez de rencontrer des problèmes.
Le correctif est mentionné par la réponse acceptée et par quelques autres réponses. Vous avez juste besoin d'ajouter
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
Je trouve la réponse de Suragh la plus complète et la plus concise, donc peu source de confusion.
Bien que non expliquer pourquoi. 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 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 taille. J'ai seulement ajouté des contraintes centerX, centerY. Mais l'étiquette sera toujours correctement dimensionnée Pourquoi?
À cause de contentSize
.
Pour mieux traiter cela, conservez d'abord l'étape 0, puis commentez les étapes 1 à 6. Laissez setupLayout()
rester. Observez le comportement.
Puis décommentez step1 et observez.
Décommentez ensuite l'étape 2 et observez.
Faites ceci jusqu'à ce que vous ayez décommenté les 6 étapes et observé leur comportement.
contenSize
?\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, votre largeur sera calculée en fonction des 25 caractèresnumberOfLines
sur 0
sinon vous n'aurez pas plusieurs lignes. Votre numberOfLines
ajustera votre hauteur intrinsèque deContentSizeEffectuer des ajustements: Imaginez que selon votre texte, la largeur de votreContentSize intrinsèque était de 200
et de hauteur de 100
, mais que vous souhaitiez limiter la largeur au conteneur de l'étiquette, qu'allez-vous faire? La solution consiste à définir la largeur souhaitée. Pour ce faire, définissez preferredMaxLayoutWidth
sur 130
, puis votre nouvelle taille intrinsèqueContentSize aura une largeur d'environ 130
. La hauteur serait évidemment plus que 100
car vous auriez besoin de plus de lignes. Cela étant dit, si vos contraintes sont correctement définies, vous n’avez pas besoin de l’utiliser du tout! Pour plus d'informations, voir cette réponse et ses commentaires. Vous devez uniquement utiliser preferredMaxLayoutWidth
si vous n’avez pas de contraintes limitant la largeur/la hauteur, comme dans l’on pourrait dire "n’enroulez pas le texte sauf s'il dépasse la preferredMaxLayoutWidth
". Mais avec 100% de certitude, si vous définissez les valeurs de début/fin et numberOfLines
sur 0
, vous êtes bon! Longue histoire la plupart des réponses ici qui recommandent de l'utiliser sont FAUX! Tu n'en as pas besoin. En avoir besoin est un signe que vos contraintes ne sont pas définies correctement ou que vous n’avez simplement pas de contraintes
Taille de la police: Notez également que si vous augmentez votre fontSize, le hauteur de intrinsicContentSize augmentera. Je n'ai pas montré ça dans mon code. Vous pouvez essayer cela vous-même.
Revenons donc à votre exemple tableViewCell:
Tout ce que vous devez faire c'est:
numberOfLines
sur 0
preferredMaxLayoutWidth
.Dans mon cas, le rembourrage était dû aux hauteurs sectionHeader et sectionFooter, où le storyboard m'a permis de le modifier au minimum.
tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
Je viens de faire quelques essais stupides avec les deux valeurs de rowHeight
et estimatedRowHeight
et je pensais juste que cela pourrait fournir des informations de débogage:
Si vous définissez les deux OR uniquement la valeur estimatedRowHeight
, vous 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 l'estimation correcte, mais le résultat final n'est pas différent. Cela n'affectera que votre performance.
Si vous définissez uniquement rowHeight, c’est-à-dire:
tableView.rowHeight = UITableViewAutomaticDimension
votre résultat final ne serait pas comme souhaité:
Si vous définissez estimatedRowHeight
sur 1 ou moins, vous aurez crash sans tenir compte de rowHeight
.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1
Je me suis écrasé 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
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 étiquettes UIL à taille dynamique (largeur et hauteur)
j'ai réalisé la même chose ici dans ma réponse avec autolayout et par programme:
Au-dessus de @smileyBorg, la réponse a aidé, mais systemLayoutSizeFittingSize n'a jamais fonctionné pour moi. Dans mon approche:
1. Pas d'utilisation de la propriété de calcul automatique de hauteur de ligne. 2. Pas d'utilisation de hauteur estimée. 3. Pas besoin de mise à jour inutile de contraintes de mise à jour. 4.Aucune utilisation de la largeur de disposition maximale préférée. 5. Pas d'utilisation de systemLayoutSizeFittingSize (Je devrais utiliser mais ne travaille pas pour moi, je ne sais pas ce qu’il fait en interne), mais ma méthode - (float) getViewHeight fonctionne et je sais ce qu’elle fait en interne.
En ce qui concerne la réponse acceptée par @smileyborg, j’ai trouvé
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
être peu fiable dans certains cas où les contraintes sont ambiguës. Il est préférable de forcer le moteur de présentation à calculer la hauteur dans une direction, en utilisant la catégorie d’aide 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 tableview
Swift 4
@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var tableView: UITableView!
redéfinit func viewDidLoad () { super.viewDidLoad ()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
}
// Ajout de l'observateur pour ajuster la hauteur de la table en fonction du contenu
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
}
}
}
// Supprimer l'observateur deinit {
NotificationCenter.default.removeObserver(self)
}
Swift 4.2
Cela peut vous aider à réduire l’écriture du même code plusieurs fois dans l’application.
J'ai ajouté l'extension ci-dessous à UITableView:
extension UITableView {
func setDynamicRowHeight(withEstimatedHeight height : CGFloat){
self.estimatedRowHeight = height
self.rowHeight = UITableViewAutomaticDimension
}
}
et dans n'importe quelle classe j'ai besoin de définir la hauteur de ligne de la tableView dynamique
J'appelle seulement la fonction ci-dessus dans le rappel ViewDidLoad () aussi simple que cela:
override func viewDidLoad() {
super.viewDidLoad()
// enter your estimated row height.
self.tableView.setDynamicRowHeight(withEstimatedHeight: 88.0)
}
Ajoutez simplement ces deux fonctions à votre contrôleur de vue, 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
}