J'essaie d'apprendre la mise en page automatique, j'ai finalement pensé que je pouvais me débrouiller quand cela s'est produit. Je joue avec des cellules prototypes et des étiquettes. J'essaie d'avoir un
titleLbl
- boîte bleue dans l'imagemoneyLbl
- case verte dans l'imagesubTitleLbl
- cadre rouge dans l'imageJusqu'ici j'ai ceci:
Ce que je veux réaliser, c'est:
Comme vous pouvez le constater, tout fonctionne presque, à l'exception de l'exemple de la dernière ligne. J'ai essayé de faire des recherches à ce sujet et d'après ce que je peux comprendre, cela a quelque chose à voir avec la taille du contenu intrinsèque. De nombreux articles en ligne recommandent de définir setPreferredMaxLayoutWidth
. Je l'ai déjà fait à plusieurs endroits pour titleLbl
et cela n'a eu aucun effet. J'ai obtenu les résultats uniquement lorsque je l'ai codé en dur en 180
, mais il a également ajouté des espaces/marges aux autres zones bleues situées en haut/en dessous du texte, augmentant ainsi considérablement l'espace entre les zones rouge/bleue.
Voici mes contraintes:
Titre:
Argent:
Sous-titre:
D'après les tests que j'ai effectués avec d'autres exemples de texte, il semble utiliser la largeur indiquée dans le storyboard, mais toute tentative de correction ne fonctionne pas.
J'ai créé un ivar de la cellule et je l'ai utilisé pour créer la hauteur de la cellule:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
[sizingCellTitleSubMoney.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
[sizingCellTitleSubMoney.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
[sizingCellTitleSubMoney.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];
[sizingCellTitleSubMoney layoutIfNeeded];
CGSize size = [sizingCellTitleSubMoney.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height+1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MATitleSubtitleMoneyTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifierTitleSubTitleMoney];
if(!cell)
{
cell = [[MATitleSubtitleMoneyTableViewCell alloc] init];
}
cell.titleLbl.layer.borderColor = [UIColor blueColor].CGColor;
cell.titleLbl.layer.borderWidth = 1;
cell.subtitleLbl.layer.borderColor = [UIColor redColor].CGColor;
cell.subtitleLbl.layer.borderWidth = 1;
cell.moneyLbl.layer.borderColor = [UIColor greenColor].CGColor;
cell.moneyLbl.layer.borderWidth = 1;
[cell.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
[cell.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
[cell.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];
return cell;
}
J'ai essayé de définir setPreferredMaxLayoutWidth
dans les sous-vues de présentation de la cellule:
- (void)layoutSubviews
{
[super layoutSubviews];
NSLog(@"Money lbl: %@", NSStringFromCGRect(self.moneyLbl.frame));
NSLog(@"prefferredMaxSize: %f \n\n", self.moneyLbl.frame.Origin.x-8);
[self.titleLbl setPreferredMaxLayoutWidth: self.moneyLbl.frame.Origin.x-8];
}
bûche:
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] Money lbl: {{209, 20}, {91, 61}}
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] prefferredMaxSize: 201.000000
C'est ce à quoi je m'attendrais car il y a 8px entre les 2 éléments et la console le vérifie. Cela fonctionne parfaitement sur la première ligne, peu importe la quantité de texte que j'ajoute à la zone bleue, qui est identique au storyboard, mais toute augmentation de la zone verte annule le calcul de la largeur. J'ai essayé de définir cela dans le tableView:willDisplayCell
et le tableView:heightForRowAtIndexPath:
également en fonction des réponses à d'autres questions. Mais peu importe ce que cela ne suffit pas mise en page.
Toute aide, idées ou commentaires seraient grandement appréciés
J'ai trouvé une réponse encore meilleure après avoir lu ceci: http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights
créer une sous-classe UILabel
pour écraser layoutSubviews
comme ceci:
- (void)layoutSubviews
{
[super layoutSubviews];
self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
[super layoutSubviews];
}
Cela garantit que la preferredMaxLayoutWidth
est toujours correcte.
Mettre à jour:
Après plusieurs autres versions d'iOS et des tests de plusieurs cas d'utilisation, j'ai trouvé que ce qui précède n'était pas suffisant pour tous les cas. À partir de iOS 8 (je crois), dans certaines circonstances, seule didSet
bounds
a été appelée à temps avant le chargement de l'écran.
Très récemment, dans iOS 9, je rencontrais un autre problème lorsqu’on utilisait une UIVIewController
modale avec une UITableView
, à savoir que layoutSubviews
et set bounds
étaient appelés aprèsheightForRowAtIndexPath
. Après beaucoup de débogage, la seule solution consistait à remplacer set frame
.
Le code ci-dessous semble maintenant nécessaire pour s’assurer qu’il fonctionne sur tous les iOS, contrôleurs, tailles d’écran, etc.
override public func layoutSubviews()
{
super.layoutSubviews()
self.preferredMaxLayoutWidth = self.bounds.width
super.layoutSubviews()
}
override public var bounds: CGRect
{
didSet
{
self.preferredMaxLayoutWidth = self.bounds.width
}
}
override public var frame: CGRect
{
didSet
{
self.preferredMaxLayoutWidth = self.frame.width
}
}
Je ne suis pas sûr de bien vous comprendre et ma solution est loin d'être la meilleure, mais la voici:
J'ai ajouté une contrainte de hauteur sur l'étiquette coupée, connectée à la sortie de la cellule. J'ai remplacé la méthode layoutSubview:
- (void) layoutSubviews {
[super layoutSubviews];
[self.badLabel setNeedsLayout];
[self.badLabel layoutIfNeeded];
self.badLabelHeightConstraint.constant = self.badLabel.intrinsicContentSize.height;
}
et alto! S'il vous plaît essayez cette méthode et dites-moi des résultats, merci.
J'ai passé du temps à emballer la cellule, ressemblant à ceci:
+--------------------------------------------------+
| This is my cell.contentView |
| +------+ +-----------------+ +--------+ +------+ |
| | | | multiline | | title2 | | | |
| | img | +-----------------+ +--------+ | img | |
| | | +-----------------+ +--------+ | | |
| | | | title3 | | title4 | | | |
| +------+ +-----------------+ +--------+ +------+ |
| |
+--------------------------------------------------+
Après plusieurs tentatives, je suis venu avec une solution pour utiliser une cellule prototype qui fonctionne réellement.
Le problème principal était que mes étiquettes pourraient être ou pourraient ne pas être là . Chaque élément devrait être facultatif.
Tout d’abord, notre cellule "prototype" ne doit disposer que des éléments de mise en page lorsque nous ne sommes pas dans un environnement "réel".
Par conséquent, j'ai créé l'indicateur "heightCalculationInProgress".
Lorsque quelqu'un (source de données de tableView) demande à calculer la hauteur, nous fournissons des données à la cellule prototype à l'aide du bloc: Nous demandons ensuite à notre cellule prototype de mettre en page le contenu et de la récupérer. Simple.
Afin d'éviter le bug iOS7 avec la récursion layoutSubview, il existe une variable "layoutingLock"
- (CGFloat)heightWithSetupBlock:(nonnull void (^)(__kindof UITableViewCell *_Nonnull cell))block {
block(self);
self.heightCalculationInProgress = YES;
[self setNeedsLayout];
[self layoutIfNeeded];
self.layoutingLock = NO;
self.heightCalculationInProgress = NO;
CGFloat calculatedHeight = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGFloat height = roundf(MAX(calculatedHeight, [[self class] defaultHeight]));
return height;
}
Ce "bloc" de l'extérieur pourrait ressembler à ceci:
[prototypeCell heightWithSetupBlock:^(__kindof UITableViewCell * _Nonnull cell) {
@strongify(self);
cell.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0);
cell.dataObject = dataObject;
cell.multilineLabel.text = @"Long text";
// Include any data here
}];
Et commence maintenant la partie la plus intéressante. Mise en page:
- (void)layoutSubviews {
if(self.layoutingLock){
return;
}
// We don't want to do this layouting things in 'real' cells
if (self.heightCalculationInProgress) {
// Because of:
// Support for constraint-based layout (auto layout)
// If nonzero, this is used when determining -intrinsicContentSize for multiline labels
// We're actually resetting internal constraints here. And then we could reuse this cell with new data to layout it correctly
_multilineLabel.preferredMaxLayoutWidth = 0.f;
_title2Label.preferredMaxLayoutWidth = 0.f;
_title3Label.preferredMaxLayoutWidth = 0.f;
_title4Label.preferredMaxLayoutWidth = 0.f;
}
[super layoutSubviews];
// We don't want to do this layouting things in 'real' cells
if (self.heightCalculationInProgress) {
// Make sure the contentView does a layout pass here so that its subviews have their frames set, which we
// need to use to set the preferredMaxLayoutWidth below.
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
// Set the preferredMaxLayoutWidth of the mutli-line bodyLabel based on the evaluated width of the label's frame,
// as this will allow the text to wrap correctly, and as a result allow the label to take on the correct height.
_multilineLabel.preferredMaxLayoutWidth = CGRectGetWidth(_multilineLabel.frame);
_title2Label.preferredMaxLayoutWidth = CGRectGetWidth(_title2Label.frame);
_title3Label.preferredMaxLayoutWidth = CGRectGetWidth(title3Label.frame);
_title4Label.preferredMaxLayoutWidth = CGRectGetWidth(_title4Label.frame);
}
if (self.heightCalculationInProgress) {
self.layoutingLock = YES;
}
}
Omg, viens d'avoir le même problème. La réponse de @Simon McLoughlin ne m'a pas aidé. Mais
titleLabel.adjustsFontSizeToFitWidth = true
fait une magie! Cela fonctionne, je ne sais pas pourquoi mais… .. Et oui, votre étiquette doit avoir une valeur explicite preferredMaxLayoutWidth.