J'essaie d'apprendre la layout automatique, j'ai finalement pensé que je m'attendais à ce que ça se passe. Je joue avec des prototypes de cellules et d'labels. J'essaie d'avoir un
titleLbl
– boîte bleue dans l'image moneyLbl
– boîte verte dans l'image subTitleLbl
– boîte rouge dans l'image Jusqu'à présent j'ai ceci:
Ce que je veux accomplir est:
Comme vous pouvez le voir, cela fonctionne presque tous, à l'exception de l'exemple de la dernière rangée. J'ai essayé de faire des searchs sur ce point et, d'après ce que je peux comprendre, cela a quelque chose à voir avec la taille insortingnsèque du contenu. De nombreux messages en ligne recommandnt de définir setPreferredMaxLayoutWidth
, je l'ai fait à plusieurs endroits pour le titleLbl
et il n'a eu aucun effet. Ce n'est que lorsque je l'ai codé en dur à 180
que j'ai obtenu les résultats, mais que j'ai également ajouté des espaces / padding aux autres cases bleues en haut / en dessous du text, augmentant considérablement l'espace entre les boîtes rouges / bleues.
Voici mes contraintes:
Titre:
Argent:
Sous-titre:
Basé sur des tests que j'ai exécutés avec d'autres exemples de text, il semble utiliser la largeur comme indiqué dans le storyboard, mais toute tentative de correction ne fonctionne pas.
J'ai créé un ivar de la cellule et 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 layout de la cellule:
- (void)layoutSubviews { [super layoutSubviews]; NSLog(@"Money lbl: %@", NSSsortingngFromCGRect(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
Ce qui est ce que j'attendais car il y a 8px entre les 2 éléments et la console vérifie cela. Cela fonctionne parfaitement sur la première rangée, peu importe la quantité de text que j'ajoute à la boîte bleue, qui est la même que celle du storyboard, mais toute augmentation de la boîte verte entraîne le calcul de la largeur. J'ai essayé de mettre ceci dans la tableView:willDisplayCell
et le tableView:heightForRowAtIndexPath:
aussi basé sur les réponses d'autres questions. Mais peu importe ce que ça ne fait pas.
Toute aide, idée ou commentaire serait grandement apprécié
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-class UILabel
pour replace layoutSubviews
comme ceci:
- (void)layoutSubviews { [super layoutSubviews]; self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds); [super layoutSubviews]; }
Cela garantit que la valeur preferredMaxLayoutWidth
est toujours correcte.
Mettre à jour:
Après plusieurs autres versions d'iOS et de tester plus d'usecase j'ai trouvé que ce qui précède n'est pas suffisant pour chaque cas. Depuis iOS 8 (je crois), dans certaines circonstances, seules les bounds
didSet
été appelées à time avant le chargement de l'écran.
Dans iOS 9 très récemment, je suis tombé sur un autre problème lors de l'utilisation d'un UIVIewController
modal avec un UITableView
, que les deux layoutSubviews
et set bounds
ont été appelés après heightForRowAtIndexPath
. Après beaucoup de debugging, la seule solution consistait à replace set frame
.
Le code ci-dessous semble maintenant être nécessaire pour s'assurer qu'il fonctionne sur tous les iOS, les controllers, les 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 vraiment loin d'être la meilleure, mais voici:
J'ai ajouté une contrainte de hauteur sur l'label qui a été coupée, connectant cette contrainte à la sortie de la cellule. J'ai surchargé la méthode layoutSubview:
- (void) layoutSubviews { [super layoutSubviews]; [self.badLabel setNeedsLayout]; [self.badLabel layoutIfNeeded]; self.badLabelHeightConstraint.constant = self.badLabel.insortingnsicContentSize.height; }
et alto! S'il vous plaît essayez cette méthode et dites-moi les résultats, merci.
Omg, juste eu le même problème. La réponse de @Simon McLoughlin ne m'a pas aidé. Mais
titleLabel.adjustsFontSizeToFitWidth = true
fait une magie! Ça marche, je ne sais pas pourquoi mais ça le fait. Et oui, votre label doit avoir une valeur explicitement preferredMaxLayoutWidth non plus.
J'ai passé un certain time à emballer la cellule, ressemblant à ceci:
+--------------------------------------------------+ | This is my cell.contentView | | +------+ +-----------------+ +--------+ +------+ | | | | | multiline | | title2 | | | | | | img | +-----------------+ +--------+ | img | | | | | +-----------------+ +--------+ | | | | | | | title3 | | title4 | | | | | +------+ +-----------------+ +--------+ +------+ | | | +--------------------------------------------------+
Après de nombreuses tentatives, je suis venu avec une solution comment utiliser la cellule prototype qui fonctionne réellement.
Le problème principal était que mes labels pourraient être ou pourraient ne pas être là. Chaque élément devrait être facultatif.
Tout d'abord, notre cellule «prototype» ne doit être layout que lorsque nous ne sums pas dans un environnement «réel».
Par conséquent, j'ai créé le drapeau 'heightCalculationInProgress'.
Quand quelqu'un (dataSource de tableView) request de calculer la hauteur, nous fournissons des données à la cellule prototype en utilisant block: Et ensuite nous demandons à notre cellule prototype de mettre en forme le contenu et d'en saisir la hauteur. Simple.
Afin d'éviter iOS7 bug avec la recursion layoutSubview il y a 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 maintenant commence 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 -insortingnsicContentSize 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; } }