web-dev-qa-db-fra.com

Comment définir la hauteur de tableHeaderView (UITableView) avec autolayout?

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.

enter image description here

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;
}
83
Andrew Cross

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?

35
TomSwift

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
        }
    }
22
Daniel Galasko

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

AutoResizingHeader

17
p-sun

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];
3
Kim André Sand

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

3
petrsyn

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

}
2
Ravi Randeria

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
    }
}
0
Sai kumar Reddy

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
    }
}
0
malex

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.

0
meow2x

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];
    }
}
0
wzbozon