Je me suis cogné la tête contre le mur avec cela pendant 3 ou 4 heures et je n'arrive pas à comprendre. J'ai un UIViewController avec un UITableView en plein écran à l'intérieur (il y a d'autres choses à l'écran, c'est pourquoi je ne peux pas utiliser un UITableViewController) et je veux redimensionner mon tableHeaderView avec autolayout. Inutile de dire que ce n'est pas coopérer.
Voir la capture d'écran ci-dessous.
Parce que le overviewLabel (par exemple, le texte "Liste globale des informations ici.") A un contenu dynamique, j'utilise autolayout pour le redimensionner et son aperçu. J'ai tout redimensionné bien, sauf pour la tableHeaderView, qui est juste en dessous de la vue Table Paralax dans la hiérarchie.
La seule façon que j'ai trouvée de redimensionner cette vue en-tête est par programme, avec le code suivant:
CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];
Dans ce cas, headerFrameHeight est un calcul manuel de la hauteur de tableViewHeader comme suit (innerHeaderView est la zone blanche ou le second "View", headerView est la tableHeaderView):
CGFloat startingY = self.innerHeaderView.frame.Origin.y + self.overviewLabel.frame.Origin.y;
CGRect overviewSize = [self.overviewLabel.text
boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: self.overviewLabel.font}
context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);
Le calcul manuel fonctionne, mais il est maladroit et sujet aux erreurs si les choses changent à l'avenir. Ce que je veux pouvoir faire, c'est que le redimensionnement automatique de tableHeaderView soit basé sur les contraintes fournies, comme vous pouvez le faire n'importe où ailleurs. Mais pour ma vie, je ne peux pas le comprendre.
Il y a plusieurs articles sur SO à ce sujet, mais aucun n'est clair et a fini par me rendre plus confus. En voici quelques-uns:
Cela n'a pas vraiment de sens de changer la propriété translatesAutoresizingMaskIntoConstraints en NO, car cela me cause simplement des erreurs et n'a aucun sens conceptuel de toute façon.
Toute aide serait vraiment appréciée!
EDIT 1: Grâce à la suggestion de TomSwift, j'ai été en mesure de le comprendre. Au lieu de calculer manuellement la hauteur de la vue d'ensemble, je peux la faire calculer comme suit, puis redéfinir la tableHeaderView comme auparavant.
[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.Origin.y; // adding the Origin because innerHeaderView starts partway down headerView.
CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];
Edit 2: Comme d'autres l'ont noté, la solution publiée dans Edit 1 ne semble pas fonctionner dans viewDidLoad. Cela semble cependant fonctionner dans viewWillLayoutSubviews. Exemple de code ci-dessous:
// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[_headerWrapper setNeedsLayout];
[_headerWrapper layoutIfNeeded];
CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = _headerWrapper.frame;
headerFrame.size.height = height;
_headerWrapper.frame = headerFrame;
_tableView.tableHeaderView = _headerWrapper;
}
Vous devez utiliser le UIView systemLayoutSizeFittingSize:
méthode pour obtenir la taille minimale de délimitation de votre vue en-tête.
Je fournis plus de discussion sur l'utilisation de cette API dans cette Q/A:
Comment redimensionner les super vues pour s’adapter à toutes les sous-vues avec autolayout?
Je me suis vraiment battu avec celui-ci et ma configuration dans viewDidLoad ne fonctionnait pas pour moi car le cadre n'était pas défini dans viewDidLoad. .J'ai seulement remarqué le problème sur iPad lors de la présentation d'une tableView dans une présentation de formulaire.
Ce qui a résolu le problème pour moi, c'est de définir tableViewHeader dans viewWillLayoutSubviews plutôt que dans viewDidLoad.
func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if tableView.tableViewHeaderView == nil {
let header: MyHeaderView = MyHeaderView.createHeaderView()
header.setNeedsUpdateConstraints()
header.updateConstraintsIfNeeded()
header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
var newFrame = header.frame
header.setNeedsLayout()
header.layoutIfNeeded()
let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
newFrame.size.height = newSize.height
header.frame = newFrame
self.tableView.tableHeaderView = header
}
}
J'ai trouvé un moyen élégant d'utiliser la disposition automatique pour redimensionner les en-têtes de tableau, avec ou sans animation.
Ajoutez simplement ceci à votre contrôleur de vue.
func sizeHeaderToFit(tableView: UITableView) {
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
}
}
Pour redimensionner selon un libellé changeant dynamiquement:
@IBAction func addMoreText(sender: AnyObject) {
self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}
override func viewDidLayoutSubviews() {
// viewDidLayoutSubviews is called when labels change.
super.viewDidLayoutSubviews()
sizeHeaderToFit(tableView)
}
Pour animer un redimensionnement en fonction d'une modification d'une contrainte:
@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!
@IBAction func makeThisTaller(sender: AnyObject) {
UIView.animateWithDuration(0.3) {
self.tableView.beginUpdates()
self.makeThisTallerHeight.constant += 20
self.sizeHeaderToFit(self.tableView)
self.tableView.endUpdates()
}
}
Voir le projet AutoResizingHeader pour voir cela en action. https://github.com/p-Sun/Swift2-iOS9-UI
Votre solution utilisant systemLayoutSizeFittingSize: fonctionne si la vue d'en-tête est simplement mise à jour une fois à chaque apparence de vue. Dans mon cas, la vue en-tête a été mise à jour plusieurs fois pour refléter les changements d'état. Mais systemLayoutSizeFittingSize: toujours signalé la même taille. C'est-à-dire la taille correspondant à la première mise à jour.
Pour obtenir systemLayoutSizeFittingSize: pour retourner la taille correcte après chaque mise à jour, je devais d'abord supprimer la vue d'en-tête de table avant de la mettre à jour et de la rajouter:
self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];
Cette solution redimensionne la tableHeaderView et évite la boucle infinie dans la méthode viewDidLayoutSubviews()
que j'avais avec d'autres réponses ici:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
// comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
}
Voir aussi ce post: https://stackoverflow.com/a/34689293/1245231
Cela a fonctionné pour moi sur ios10 et Xcode 8
func layoutTableHeaderView() {
guard let headerView = tableView.tableHeaderView else { return }
headerView.translatesAutoresizingMaskIntoConstraints = false
let headerWidth = headerView.bounds.size.width;
let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])
headerView.addConstraints(temporaryWidthConstraints)
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
let height = headerSize.height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
self.tableView.tableHeaderView = headerView
headerView.removeConstraints(temporaryWidthConstraints)
headerView.translatesAutoresizingMaskIntoConstraints = true
}
Cela fonctionne pour la vue en-tête et le pied de page, il suffit de remplacer l'en-tête par le pied de page
func sizeHeaderToFit() {
if let headerView = tableView.tableHeaderView {
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
}
}
Il est tout à fait possible d'utiliser UIView
générique basé sur AutoLayout avec n'importe quelle structure de sous-vue interne d'AL en tant que tableHeaderView
.
La seule chose dont vous avez besoin est de définir un simple tableFooterView
avant!
Laisser self.headerView
est un certain UIView
basé sur des contraintes.
- (void)viewDidLoad {
........................
self.tableView.tableFooterView = [UIView new];
[self.headerView layoutIfNeeded]; // to set initial size
self.tableView.tableHeaderView = self.headerView;
[self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
[self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
[self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;
// and the key constraint
[self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}
Si self.headerView
change la hauteur sous la rotation de l'interface utilisateur, il faut l'implémenter
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
// needed to resize header height
self.tableView.tableHeaderView = self.headerView;
} completion: NULL];
}
On peut utiliser ObjC catégorie à cette fin
@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end
@implementation UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView {
NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");
[headerView layoutIfNeeded];
self.tableHeaderView = headerView;
[self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
[self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
[self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;
[self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end
Et aussi Swift extension
extension UITableView {
func am_insertHeaderView2(_ headerView: UIView) {
assert(tableFooterView != nil, "Need to define tableFooterView first!")
headerView.layoutIfNeeded()
tableHeaderView = headerView
leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
}
}
Cette solution fonctionne parfaitement pour moi:
https://spin.atomicobject.com/2017/08/11/Swift-extending-uitableviewcontroller/
Il étend le UITableViewController. Mais si vous utilisez simplement un UITableView, cela fonctionnera toujours, développez simplement UITableView au lieu de UITableViewController. Appelez les méthodes sizeHeaderToFit()
ou sizeFooterToFit()
chaque fois qu'un événement modifie la hauteur tableViewHeader
.
Dans mon cas, viewDidLayoutSubviews
a mieux fonctionné. viewWillLayoutSubviews
fait apparaître les lignes blanches d'un tableView
. J'ai également ajouté en vérifiant si mon objet headerView
existe déjà.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if ( ! self.userHeaderView ) {
// Setup HeaderView
self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
[self.userHeaderView setNeedsLayout];
[self.userHeaderView layoutIfNeeded];
CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = self.userHeaderView.frame;
headerFrame.size.height = height;
self.userHeaderView.frame = headerFrame;
self.tableView.tableHeaderView = self.userHeaderView;
// Update HeaderView with data
[self.userHeaderView updateWithProfileData];
}
}