La sous-class UITableViewHeaderFooterView avec layout automatique et rechargement de section ne fonctionnera pas bien set

J'essaye d'incorporer la disposition automatique dans ma sous-class UITableViewHeaderFooterView. La class est assez basique, juste deux labels. C'est la sous-class complète:

@implementation MBTableDetailStyleFooterView static void MBTableDetailStyleFooterViewCommonSetup(MBTableDetailStyleFooterView *_self) { UILabel *rightLabel = [[UILabel alloc] init]; _self.rightLabel = rightLabel; rightLabel.translatesAutoresizingMaskIntoConstraints = NO; [_self.contentView addSubview:rightLabel]; UILabel *leftLabel = [[UILabel alloc] init]; _self.leftLabel = leftLabel; leftLabel.translatesAutoresizingMaskIntoConstraints = NO; [_self.contentView addSubview:leftLabel]; NSDictionary *views = NSDictionaryOfVariableBindings(rightLabel, leftLabel); NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-10-[leftLabel]-(>=10)-[rightLabel]-10-|" options:0 mesortingcs:nil views:views]; [_self.contentView addConstraints:horizontalConstraints]; // center views vertically in super view NSLayoutConstraint *leftCenterYConstraint = [NSLayoutConstraint constraintWithItem:leftLabel atsortingbute:NSLayoutAtsortingbuteCenterY relatedBy:NSLayoutRelationEqual toItem:_self.contentView atsortingbute:NSLayoutAtsortingbuteCenterY multiplier:1 constant:0]; [_self.contentView addConstraint:leftCenterYConstraint]; NSLayoutConstraint *rightCenterYConstraint = [NSLayoutConstraint constraintWithItem:rightLabel atsortingbute:NSLayoutAtsortingbuteCenterY relatedBy:NSLayoutRelationEqual toItem:_self.contentView atsortingbute:NSLayoutAtsortingbuteCenterY multiplier:1 constant:0]; [_self.contentView addConstraint:rightCenterYConstraint]; // same height for both labels NSLayoutConstraint *sameHeightConstraint = [NSLayoutConstraint constraintWithItem:leftLabel atsortingbute:NSLayoutAtsortingbuteHeight relatedBy:NSLayoutRelationEqual toItem:rightLabel atsortingbute:NSLayoutAtsortingbuteHeight multiplier:1 constant:0]; [_self.contentView addConstraint:sameHeightConstraint]; } + (BOOL)requiresConstraintBasedLayout { return YES; } - (id)initWithReuseIdentifier:(NSSsortingng *)reuseIdentifier { self = [super initWithReuseIdentifier:reuseIdentifier]; MBTableDetailStyleFooterViewCommonSetup(self); return self; } @end 

Cette class est utilisée comme pied de page dans la première section d'une tableView avec 2 sections. La première section contient des éléments dynamics. La deuxième section a seulement une ligne, qui est utilisée pour append de nouveaux éléments à la première section.

S'il n'y a aucun élément dans la première section, je cache le pied de page. Donc, quand j'ajoute le premier nouvel item, je dois recharger la section pour que le footerView apparaisse. Le code qui fait tout cela ressemble à ceci:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; if (indexPath.section == 1) { BOOL sectionNeedsReload = ([self.data count] == 0); // reload section when no data (and therefor no footer) was present before the add [self.data addObject:[NSDate date]]; NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[self.data count]-1 inSection:0]; if (sectionNeedsReload) { [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic]; } else { [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } [self configureFooter:(MBTableDetailStyleFooterView *)[tableView footerViewForSection:0] forSection:0]; } } - (void)configureFooter:(MBTableDetailStyleFooterView *)footer forSection:(NSInteger)section { footer.leftLabel.text = @"Total"; footer.rightLabel.text = [NSSsortingng ssortingngWithFormat:@"%d", [self.data count]]; } - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { MBTableDetailStyleFooterView *footer = nil; if (section == 0 && [self.data count]) { footer = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Footer"]; [self configureFooter:footer forSection:section]; } return footer; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { CGFloat height = 0; if (section == 0 && [self.data count]) { height = 20.0f; } return height; } 

Rien de vraiment chic. Cependant, dès que reloadSections:withRowAnimations: est appelé sur ma tableView, il triggers une exception car il est "Impossible de satisfaire simultanément les contraintes.".

Quelque part, la tableView a ajouté une contrainte de masque de redimensionnement automatique traduite à mon pied de page.

 ( "<NSLayoutConstraint:0x718a1f0 H:[UILabel:0x7189130]-(10)-| (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>", "<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>", "<NSLayoutConstraint:0x718a0a0 H:|-(10)-[UILabel:0x71892c0] (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>", "<NSAutoresizingMaskLayoutConstraint:0x7591ab0 h=--& v=--& H:[_UITableViewHeaderFooterContentView:0x7188df0(0)]>" ) 

Quand je remplace reloadSections:withRowAnimations: avec un appel à reloadData aucune contrainte de masque autoresizing n'est ajoutée et tout fonctionne correctement.

La chose intéressante est que l'exception me dit qu'il essaie de casser la contrainte <NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>

Mais quand je consignerai les contraintes dans les appels suivants à configureFooter:forSection: cette contrainte existe toujours, mais la contrainte de masque de redimensionnement automatique a disparu

Les contraintes sont exactement celles que j'ai établies.

 ( "<NSLayoutConstraint:0x718a0a0 H:|-(10)-[UILabel:0x71892c0] (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>", "<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>", "<NSLayoutConstraint:0x718a1f0 H:[UILabel:0x7189130]-(10)-| (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>", "<NSLayoutConstraint:0x718a3f0 UILabel:0x71892c0.centerY == _UITableViewHeaderFooterContentView:0x7188df0.centerY>", "<NSLayoutConstraint:0x718a430 UILabel:0x7189130.centerY == _UITableViewHeaderFooterContentView:0x7188df0.centerY>", "<NSLayoutConstraint:0x718a4b0 UILabel:0x71892c0.height == UILabel:0x7189130.height>" ) 

D'où vient cette contrainte de masque de redimensionnement automatique? Où est-ce que ça va?

Est-ce que je manque quelque chose? La première fois que j'ai regardé la layout automatique était comme il y a une semaine, donc c'est totalement possible.

Solution de travail à partir d'iOS 9

Dans votre sous-class UITableViewHeaderFooterView, placez le code suivant.

 - (void)setFrame:(CGRect)frame { if (frame.size.width == 0) { return; } [super setFrame:frame]; } 

Explication:

La vue de table gère la disposition des vues d'en-tête et elle le fait en manipulant manuellement les frameworks (oui même avec l'autolayout activé).

Si vous inspectez les contraintes de largeur qui figurent sur les vues en-tête / pied de page, il y en a deux, l'une contenue dans la vue supérieure (la vue tabulaire) et l'autre dans la vue en-tête / pied de page.

La contrainte contenue dans la super vue est un NSAutoresizingMaskLayoutConstraint qui est le cadeau que la tableview dépend des frameworks pour manipuler les en-têtes. Passer le translatesAutoresizingMaskIntoConstraints à NO sur la vue d'en-tête brise son apparence, ce qui est un autre don.

Il semble que dans certaines circonstances ces vues d'en-tête / bas de page auront leurs frameworks changer à une largeur de zéro, pour moi c'était quand des rangées ont été insérées et les vues d'en-tête ont été réutilisées. Je suppose que quelque part dans le code UITableView une préparation pour une animation est faite en commençant le cadre à la largeur zéro, même si vous n'utilisez pas une animation.

Cette solution devrait fonctionner correctement et ne devrait pas avoir d'impact sur les performances du défilement.

J'ai couru à ça la semaine dernière.

La façon dont j'ai éliminé les avertissements est de changer mes contraintes requirejses pour avoir une priorité de 999 . C'est un travail plutôt qu'un correctif, mais il contourne les exceptions qui sont lancées, interceptées et enregistrées pendant la layout.

Les choses qui n'ont pas fonctionné pour moi.

Une suggestion consiste à définir estimatedRowHeight . J'ai essayé de définir le estimatedSectionHeaderHeight , mais cela n'a pas aidé. Définir un estimatedSectionFooterHeight créé des pieds de page vides là où je ne les voulais pas, ce qui était un peu étrange.

J'ai également essayé de placer translatesAutoresizingMaskIntoConstraints = NO; sur la vue de pied d'en-tête et sur sa vue de contenu. Ni l'un ni l'autre ne s'est débarrassé de l'avertissement et l'un d'entre eux a complètement détruit la disposition.

J'ai eu un problème similaire avec juste une label supplémentaire dans contentView. Essayez d'insert

 static void MBTableDetailStyleFooterViewCommonSetup(MBTableDetailStyleFooterView *_self) { _self.contentView.translatesAutoresizingMaskIntoConstraints = NO [...] } 

à la première ligne de votre fonction MBTableDetailStyleFooterViewCommonSetup . Pour moi, cela fonctionne en conjonction avec reloadSections:withRowAnimations:

Mettre à jour:

J'ai également ajouté une nouvelle contrainte pour que contentView utilise toute la largeur:

 NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 mesortingcs:nil views:@{@"contentView" : _self.contentView}]; [_self.contentView.superview addConstraints:horizontalConstraints]; 

Je ne vois pas où vous appelez translatesAutoresizingMaskIntoConstraints = NO sur le pied de page lui-même. Devriez-vous le faire lorsque vous le créez?

 - (id)initWithReuseIdentifier:(NSSsortingng *)reuseIdentifier { self = [super initWithReuseIdentifier:reuseIdentifier]; self.translatesAutoresizingMaskIntoConstraints = NO; MBTableDetailStyleFooterViewCommonSetup(self); return self; } 

J'ai eu un problème similaire, un seul UILabel dans contentView, et j'ai utilisé maçonnerie:

 _titleLabel = ({ UILabel *label = [MLBUIFactory labelWithType:MALabelTypeB]; [self.contentView addSubview:label]; [label mas_makeConstraints:^(MASConstraintMaker */make) { make.centerY.equalTo(self.contentView); make.left.equalTo(self.contentView).offset(8); make.right.equalTo(self.contentView).offset(-8); }]; label; }); 

alors j'ai eu l'avertissement:

 ( "<MASLayoutConstraint:0x1742b2c60 UILabel:0x124557ee0.left == _UITableViewHeaderFooterContentView:0x12454c640.left + 8>", "<MASLayoutConstraint:0x1742b3080 UILabel:0x124557ee0.right == _UITableViewHeaderFooterContentView:0x12454c640.right - 8>", "<NSLayoutConstraint:0x174280780 _UITableViewHeaderFooterContentView:0x12454c640.width == 0>" ) Will attempt to recover by breaking constraint <MASLayoutConstraint:0x1742b3080 UILabel:0x124557ee0.right == _UITableViewHeaderFooterContentView:0x12454c640.right - 8> 

donc j'ai essayé de mettre self.translatesAutoresizingMaskIntoConstraints = NO; , mais cela n'a pas fonctionné pour moi, donc je me suis concentré sur le message d'avertissement, et je suis confus, pourquoi la contrainte de gauche fonctionne mais juste, quand j'ai vu _UITableViewHeaderFooterContentView:0x12454c640.width == 0 , j'ai compris, peut-être c'est pourquoi la bonne contrainte est la contrainte de rupture, alors j'ai changé de code:

 _titleLabel = ({ UILabel *label = [MLBUIFactory labelWithType:MALabelTypeB]; [self.contentView addSubview:label]; [label mas_makeConstraints:^(MASConstraintMaker */make) { make.centerY.equalTo(self.contentView); make.left.equalTo(self.contentView).offset(8); make.width.equalTo(@(SCREEN_WIDTH - 16)); }]; label; }); 

J'ai remplacé la contrainte de droite par une contrainte de largeur, et l'avertissement a disparu.

Il y a un autre moyen de laisser partir l'avertissement: self.contentView.translatesAutoresizingMaskIntoConstraints = NO; , MAIS le cadre de l'label est faux.

De manière gênante, il semble que UITableViewHeaderFooterView n'aime pas les contraintes constantes, par exemple

| -10- [voir] -10- |

Je suppose que, à la création, la vue a une largeur nulle et ne peut pas satisfaire la contrainte, ce qui nécessiterait au less une largeur de 20 pixels dans ce cas.

Pour moi, le moyen de contourner cela était de changer mes contraintes constantes en quelque chose comme

| – (<= 10) – [voir] – (<= 10) – |

Ce qui devrait satisfaire un contenu de largeur nulle et devrait donner la marge désirée lors du redimensionnement.

Besoin de mettre à jour le contentView frame , et les avertissements ont disparu. Cela peut être similaire au problème de contraintes de disposition automatique sur iOS7 dans UITableViewCell

 public class MyHeaderview: UITableViewHeaderFooterView { // Add views to contentView public override func layoutSubviews() { super.layoutSubviews() contentView.frame = bounds } } 

Dans iOS 11, je n'ai pas UITableViewHeaderFooterView à afficher UITableViewHeaderFooterView correctement.

Le problème était que je UITableViewHeaderFooterView mes contraintes quand le UITableViewHeaderFooterView pensait que ses limites étaient vides, donc cela causerait toujours des conflits.

Voici la solution

 override func layoutSubviews() { super.layoutSubviews() guard bounds != CGRect.zero else { return } makeConstraints() }