Est-il possible d'utiliser AutoLayout avec tableHeaderView d'UITableView?

97

Depuis que j'ai découvert que AutoLayoutje l'utilise partout, maintenant j'essaye de l'utiliser avec un tableHeaderView.

J'ai fait subclassde UIViewtout ajouté (étiquettes , etc ...) Je voulais avec leurs contraintes, alors j'ajouté ce CustomViewau UITableView' tableHeaderView.

Tout fonctionne très bien sauf que le UITableViews'affiche toujours au - dessus du CustomView, par dessus, je veux dire qu'il CustomViewest sous le UITableViewdonc il ne peut pas être vu!

Il semble que peu importe ce que je fais, le heightde UITableView' tableHeaderViewest toujours 0 (de même que la largeur, x et y).

Ma question: est-il possible du tout d'accomplir cela sans régler le cadre manuellement ?

EDIT: Le CustomView' subviewque j'utilise a ces contraintes:

_title = [[UILabel alloc]init];
_title.text = @"Title";
[self addSubview:_title];
[_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top
[_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]];

J'utilise une bibliothèque pratique 'KeepLayout' car l'écriture manuelle des contraintes prend une éternité et beaucoup trop de lignes pour une seule contrainte, mais les méthodes s'expliquent d'elles-mêmes.

Et le UITableViewa ces contraintes:

_tableView = [[UITableView alloc]init];
_tableView.translatesAutoresizingMaskIntoConstraints = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.backgroundColor = [UIColor clearColor];
[self.view addSubview:_tableView];
[_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom.
[_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]];

_detailsView = [[CustomView alloc]init];
_tableView.tableHeaderView = _detailsView;

Je ne sais pas si je dois définir des contraintes directement sur le CustomView, je pense que la hauteur du CustomView est déterminée par les contraintes sur le UILabel"titre" qu'il contient.

EDIT 2: Après une autre enquête, il semble que la hauteur et la largeur du CustomView sont correctement calculées, mais le haut du CustomView est toujours au même niveau que le haut de l'UITableView et ils se déplacent ensemble lorsque je fais défiler.

C'est un secret
la source
Oui c'est possible. Pouvez-vous montrer le code que vous utilisez? Il est difficile de conseiller sans connaître les contraintes que vous avez définies dans la vue d'en-tête.
jrturton
Une manière simple pour vous d'accomplir ceci est d'ajouter cette vue dans IB à la tableView .. créez simplement la vue dans la même scène contenant la tableview et faites -la glisser vers la table.
Mariam K.
J'essaie d'éviter IB le plus possible, jusqu'à présent je n'ai pas eu à l'utiliser, si je ne peux pas le faire fonctionner, j'essaierai avec IB
ItsASecret
1
Apple conseille aux développeurs d'utiliser IB chaque fois que possible en ce qui concerne la mise en page automatique. Cela aide vraiment à éviter de nombreux problèmes d'incohérence.
Mariam K.
La véritable solution de mise en page automatique complète est ici
malex

Réponses:

134

J'ai posé et répondu à une question similaire ici . En résumé, j'ajoute l'en-tête une fois et l'utilise pour trouver la hauteur requise. Cette hauteur peut ensuite être appliquée à l'en-tête, et l'en-tête est défini une deuxième fois pour refléter le changement.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.header = [[SCAMessageView alloc] init];
    self.header.titleLabel.text = @"Warning";
    self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long.";

    //set the tableHeaderView so that the required height can be determined
    self.tableView.tableHeaderView = self.header;
    [self.header setNeedsLayout];
    [self.header layoutIfNeeded];
    CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    //update the header's frame and set it again
    CGRect headerFrame = self.header.frame;
    headerFrame.size.height = height;
    self.header.frame = headerFrame;
    self.tableView.tableHeaderView = self.header;
}

Si vous avez des étiquettes multilignes, cela dépend également de la vue personnalisée définissant la valeur favoriteMaxLayoutWidth de chaque étiquette:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
    self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}

ou peut-être plus généralement:

override func layoutSubviews() {
    super.layoutSubviews()  
    for view in subviews {
        guard let label = view as? UILabel where label.numberOfLines == 0 else { continue }
        label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame)
    }
}

Mise à jour janvier 2015

Malheureusement, cela semble encore nécessaire. Voici une version rapide du processus de mise en page:

tableView.tableHeaderView = header
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
tableView.tableHeaderView = header

J'ai trouvé utile de déplacer ceci dans une extension sur UITableView:

extension UITableView {
    //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
    func setAndLayoutTableHeaderView(header: UIView) {
        self.tableHeaderView = header
        header.setNeedsLayout()
        header.layoutIfNeeded()
        header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
        self.tableHeaderView = header
    }
}

Usage:

let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)
Ben Packard
la source
8
Une alternative à l'utilisation preferredMaxLayoutWidthconsiste à ajouter une contrainte de largeur (égale à la largeur de la vue du tableau) sur la vue d'en-tête avant l'utilisation systemLayoutSizeFittingSize:.
Benjohn
2
REMARQUE: si vous constatez que l'en-tête est au-dessus des premières cellules, vous avez oublié de réinitialiser la propriété d'en-tête àself.tableView.tableHeaderView
Laszlo
7
Souvent, je suis étonné de voir à quel point il peut être difficile de faire quelque chose de complètement trivial comme celui-ci.
TylerJames
5
REMARQUE: Si vous avez besoin d'obtenir la largeur exacte en tant que tableView, vous devriez obtenir la hauteur avec la priorité horizontale requiselet height = header.systemLayoutSizeFittingSize(CGSizeMake(CGRectGetWidth(self.bounds), 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
JakubKnejzlik
3
Just Use header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize) self.tableHeaderView = headerfonctionnerait à iOS 10.2
Kesong Xie
24

J'ai été incapable d'ajouter une vue d'en-tête en utilisant des contraintes (dans le code). Si je donne à ma vue une contrainte de largeur et / ou de hauteur, j'obtiens un plantage avec le message disant:

 "terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."

Lorsque j'ajoute une vue dans le storyboard à ma vue de table, cela ne montre aucune contrainte, et cela fonctionne bien comme vue d'en-tête, donc je pense que le placement de la vue d'en-tête n'est pas effectué à l'aide de contraintes. Cela ne semble pas se comporter comme une vue normale à cet égard.

La largeur correspond automatiquement à la largeur de la vue du tableau, la seule chose que vous devez définir est la hauteur - les valeurs d'origine sont ignorées, donc peu importe ce que vous y mettez. Par exemple, cela a bien fonctionné (comme le fait 0,0,0,80 pour le rect):

UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];
headerview.backgroundColor = [UIColor yellowColor];
self.tableView.tableHeaderView = headerview;
rdelmar
la source
J'ai eu cette exception aussi mais l'ajout d'une catégorie à UITableView l'a corrigée Je l'ai trouvée dans cette réponse: stackoverflow.com/questions/12610783
...
Je vais encore essayer ce que vous proposez, mais demain matin, il est 1h34 du matin je vais me coucher, merci beaucoup d'avoir pris le temps de répondre! (Mais je veux vraiment ne pas spécifier de hauteur, je voudrais qu'elle soit calculée par les contraintes que j'ai mis en place sur l'étiquette dans le CustomView)
ItsASecret
Je l'ai essayé et oui, le réglage du cadre fonctionne, mais je cherchais un moyen d'éviter de définir le cadre, je continuerai à chercher et si je ne trouve rien d'autre, j'accepterai votre réponse
ItsASecret
1
J'obtiens cette exception (testant actuellement 7.1) si la vue d'en-tête ajoutée a translatesAutoresizingMaskIntoConstraints = NO. L'activation de la traduction empêche l'erreur - je suppose UITableViewqu'à partir de la version 7.1, il ne tente pas de mettre en page automatiquement sa vue d'en-tête et veut quelque chose avec le cadre prédéfini.
Benjohn
16

J'ai vu beaucoup de méthodes ici faire tellement de choses inutiles, mais vous n'avez pas besoin de beaucoup pour utiliser la mise en page automatique dans la vue d'en-tête. Il vous suffit de créer votre fichier xib, de mettre vos contraintes et de l'instancier comme ceci:

func loadHeaderView () {
        guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else {
            return
        }
        headerView.autoresizingMask = .flexibleWidth
        headerView.translatesAutoresizingMaskIntoConstraints = true
        tableView.tableHeaderView = headerView
    }
Ramon Vasconcelos
la source
Cela a également fonctionné pour nous sur iOS 11 avec un en-tête de hauteur dynamique avec des étiquettes multi-lignes.
Ben Scheirman
1
Vous pouvez également supprimer l'option flexibleHeight-Autoresizing-Option dans IB bien sûr.
d4Rk
J'ai essayé de régler la hauteur de mon tableFooterView (via un xib / nib) et je n'ai pas réussi à définir le cadre, la hauteur, layoutIfNeeded (), etc. Mais cette solution m'a finalement permis de le définir.
vikzilla
N'oubliez pas de définir la contrainte de hauteur sur la vue entière dans un fichier xib.
Denis Kutlubaev le
6

Une autre solution consiste à distribuer la création de la vue d'en-tête au prochain appel de thread principal:

- (void)viewDidLoad {
    [super viewDidLoad];

    // ....

    dispatch_async(dispatch_get_main_queue(), ^{
        _profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
        self.tableView.tableHeaderView = self.profileView;
    });
}

Remarque: il corrige le bogue lorsque la vue chargée a une hauteur fixe. Je n'ai pas essayé lorsque la hauteur de l'en-tête ne dépend que de son contenu.

ÉDITER :

Vous pouvez trouver une solution plus propre à ce problème en implémentant cette fonction et en l'appelant dansviewDidLayoutSubviews

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    [self sizeHeaderToFit];
}
Martin
la source
1
@TussLaszlo tableHeaderViewest une sorte de buggy avec mise en page automatique. Il existe des solutions de contournement, comme celle-ci. Mais puisque je l' ai écrit, je l' ai trouvé une solution meilleure et plus propre ici stackoverflow.com/a/21099430/127493 en appelant son - (void)sizeHeaderToFitenviewDidLayoutSubviews
Martin
4

Code:

  extension UITableView {

          func sizeHeaderToFit(preferredWidth: CGFloat) {
            guard let headerView = self.tableHeaderView else {
              return
            }

            headerView.translatesAutoresizingMaskIntoConstraints = false
            let layout = NSLayoutConstraint(
              item: headerView,
              attribute: .Width,
              relatedBy: .Equal,
              toItem: nil,
              attribute:
              .NotAnAttribute,
              multiplier: 1,
              constant: preferredWidth)

            headerView.addConstraint(layout)

            let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
            headerView.frame = CGRectMake(0, 0, preferredWidth, height)

            headerView.removeConstraint(layout)
            headerView.translatesAutoresizingMaskIntoConstraints = true

            self.tableHeaderView = headerView
          }
  }
Phil
la source
Cela fonctionne.Donnez des contraintes de mise en page automatique appropriées à toutes les sous-vues de tableheaderview. Si vous manquez une seule contrainte, cela ne fonctionnera pas.
abhimuralidharan
4

Extension de cette solution http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ pour l'affichage du pied de page:

@interface AutolayoutTableView : UITableView

@end

@implementation AutolayoutTableView

- (void)layoutSubviews {
    [super layoutSubviews];

    // Dynamic sizing for the header view
    if (self.tableHeaderView) {
        CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.tableHeaderView.frame;

        // If we don't have this check, viewDidLayoutSubviews() will get
        // repeatedly, causing the app to hang.
        if (height != headerFrame.size.height) {
            headerFrame.size.height = height;
            self.tableHeaderView.frame = headerFrame;
            self.tableHeaderView = self.tableHeaderView;
        }

        [self.tableHeaderView layoutIfNeeded];
    }

    // Dynamic sizing for the footer view
    if (self.tableFooterView) {
        CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect footerFrame = self.tableFooterView.frame;

        // If we don't have this check, viewDidLayoutSubviews() will get
        // repeatedly, causing the app to hang.
        if (height != footerFrame.size.height) {
            footerFrame.size.height = height;
            self.tableFooterView.frame = footerFrame;
            self.tableFooterView = self.tableFooterView;
        }

        self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height);
        [self.tableFooterView layoutIfNeeded];
    }
}

@end
k06a
la source
Hier, j'ai passé une journée à essayer d'obtenir le redimensionnement / mise en page automatique du tableHeader correctement. Cette solution fonctionne pour moi. Merci beaucoup.
docchang
Salut! Pourriez-vous s'il vous plaît expliquer une self.tableFooterView.transformpartie? Pourquoi est-ce nécessaire?
mrvn
La transformation @mrvn est utilisée pour déplacer le pied de page vers le bas de la tableView.
k06a
3

Vous pouvez obtenir la mise en page automatique pour vous fournir une taille à l'aide de la méthode systemLayoutSizeFittingSize .

Vous pouvez ensuite l'utiliser pour créer le cadre de votre application. Cette technique fonctionne chaque fois que vous avez besoin de connaître la taille d'une vue qui utilise la disposition automatique en interne.

Le code dans Swift ressemble à

//Create the view
let tableHeaderView = CustomTableHeaderView()

//Set the content
tableHeaderView.textLabel.text = @"Hello world"

//Ask auto layout for the smallest size that fits my constraints    
let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

//Create a frame    
tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size)

//Set the view as the header    
self.tableView.tableHeaderView = self.tableHeaderView

Ou en Objective-C

//Create the view
CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero];

//Set the content
header.textLabel.text = @"Hello world";

//Ask auto layout for the smallest size that fits my constraints
CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

//Create a frame
header.frame = CGRectMake(0,0,size.width,size.height);

//Set the view as the header  
self.tableView.tableHeaderView = header

Il convient également de noter que dans ce cas particulier, le remplacement de requireConstraintBasedLayout dans votre sous-classe entraîne une passe de mise en page, cependant les résultats de cette passe de mise en page sont ignorés et le cadre système défini sur la largeur de tableView et 0 hauteur.

Jonathan
la source
3

Ce qui suit a fonctionné pour moi.

  1. Utilisez un ancien simple UIViewcomme vue d'en-tête.
  2. Ajoutez des sous-vues à cela UIView
  3. Utiliser la mise en page automatique sur les sous-vues

Le principal avantage que je vois est de limiter les calculs de cadres. Apple devrait vraiment mettre à jour UITableViewl'API de pour rendre cela plus facile.

Exemple utilisant SnapKit:

let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60))
layoutView.backgroundColor = tableView.backgroundColor
tableView.tableHeaderView = layoutView

let label = UILabel()
layoutView.addSubview(label)
label.text = "I'm the view you really care about"
label.snp_makeConstraints { make in
    make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15))
}
David Nix
la source
3

Des choses étranges se produisent. systemLayoutSizeFittingSize fonctionne très bien pour iOS9, mais pas pour iOS 8 dans mon cas. Donc, ce problème se résout assez facilement. Obtenez simplement le lien vers la vue de dessous dans l'en-tête et dans viewDidLayoutSubviews après les limites de la vue d'en-tête de mise à jour super appel en insérant la hauteur comme CGRectGetMaxY (yourview.frame) + padding

UPD: La solution la plus simple qui soit : Ainsi, dans la vue d'en-tête, placez la sous-vue et épinglez-la à gauche , à droite , en haut . Dans cette sous-vue, placez vos sous-vues avec des contraintes de hauteur automatique. Après cela, donnez tout le travail à la mise en page automatique (aucun calcul requis)

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame);
    self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
    self.tableView.tableHeaderView = self.tableView.tableHeaderView;
}

En conséquence, la sous-vue s'agrandit / rétrécit comme elle le devrait, à la fin, elle appelle viewDidLayoutSubviews. Au moment où nous connaissons la taille réelle de la vue, définissez la hauteur de headerView et mettez-la à jour en la réaffectant. Fonctionne comme un charme!

Fonctionne également pour la vue pied de page.

HotJard
la source
1
Cela boucle pour moi dans iOS 10.
Simon
3

Mis à jour pour Swift 4.2

extension UITableView {

    var autolayoutTableViewHeader: UIView? {
        set {
            self.tableHeaderView = newValue
            guard let header = newValue else { return }
            header.setNeedsLayout()
            header.layoutIfNeeded()
            header.frame.size = 
            header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            self.tableHeaderView = header
        }
        get {
            return self.tableHeaderView
        }
    }
}
Caleb Friden
la source
2

vous pouvez ajouter une contrainte d'emplacement haut + horizontal entre l'en-tête et la vue du tableau, pour le placer correctement (si l'en-tête lui-même contient toutes les contraintes de mise en page internes nécessaires pour avoir un cadre correct)

dans la méthode tableViewController viewDidLoad

    headerView.translatesAutoresizingMaskIntoConstraints = false

    tableView.tableHeaderView = headerView

    headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true
    headerView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
    headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
GreatWiz
la source
1

Ma vue d'en-tête de table est une sous-classe UIView - j'ai créé une seule UIView contentView dans l'initialiseur, avec ses limites identiques à celles du cadre de la vue d'en-tête de table et j'ai ajouté tous mes objets en tant que sous-vue.

Ajoutez ensuite les contraintes pour vos objets dans la layoutSubviewsméthode de la vue d'en-tête de table plutôt que dans l'initialiseur. Cela a résolu le crash.

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)];
    if (self) {
        UIView *contentView = [[UIView alloc] initWithFrame:self.bounds];
        contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;

        // add other objects as subviews of content view

    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // remake constraints here
}
Ryan
la source
1

Mon AutoLayout fonctionne très bien:

CGSize headerSize = [headerView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth([UIScreen mainScreen].bounds), 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
headerView.frame = CGRectMake(0, 0, headerSize.width, headerSize.height);
self.tableView.tableHeaderView = headerView;
RomanV
la source
Je ne l'ai pas fait exactement mais vous m'avez donné une bonne idée: supprimer l'en-tête, redéfinir son cadre et le rajouter.
dinesharjani
1

Dans la plupart des cas, la meilleure solution est simplement de ne pas lutter contre le framework et d'adopter les masques autoresizing:

// embrace autoresizing masks and let the framework add the constraints for you
headerView.translatesAutoresizingMaskIntoConstraints = true
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

// figure out what's the best size based on the table view width
let width = self.tableView.frame.width
let targetSize = headerView.systemLayoutSizeFitting(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
headerView.frame.size = targetSize
self.tableView.tableHeaderView = headerView

En utilisant des masques de redimensionnement automatique, vous indiquez au cadre comment votre vue doit changer sa taille lorsque la vue supervisée change de taille. Mais ce changement est basé sur le cadre initial que vous avez défini.

pfandrade
la source
0

Je sais que c'est un ancien article, mais après avoir parcouru tous les messages SO à ce sujet et passé un après-midi entier à jouer avec cela, j'ai finalement trouvé une solution propre et pourtant très simple.

Tout d'abord, ma hiérarchie de vues ressemble à ceci:

  1. Vue de tableau
    1. Vue tableHeaderView
      1. Vue avec une prise appelée headerView

Maintenant, à l'intérieur de la vue (n ° 3), j'ai mis en place toutes les contraintes comme je le ferais normalement en incluant l'espace inférieur au conteneur. Cela permettra au conteneur (c'est-à-dire 3.View, c'est-à-dire headerView) de se dimensionner en fonction de ses sous-vues et de leurs contraintes.

Après cela, j'ai défini les contraintes entre 3. Viewet 2. Viewpour ceux-ci:

  1. Espace supérieur au conteneur: 0
  2. Espace menant au conteneur: 0
  3. Espace de fin au conteneur: 0

Remarquez que j'omets intentionnellement l'espace du bas intentionnellement.

Une fois que tout cela est fait dans le storyboard, il ne reste plus qu'à coller ces trois lignes de codes:

if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) {
    UIView *header = self.tableView.tableHeaderView;
    CGRect frame = self.tableView.tableHeaderView.frame;
    frame.size.height = self.headerView.frame.size.height + frame.origin.y;
    header.frame = frame;
    self.tableView.tableHeaderView = header;
}
Marc-Alexandre Bérubé
la source
0

Conseils: Si vous utilisez la méthode setAndLayoutTableHeaderView, vous devez mettre à jour le cadre des sous-vues, donc dans cette situation, le favoriteMaxLayoutWidth d'UILabel doit appeler avant que systemLayoutSizeFittingSize soit appelé, ne pas appeler dans layoutSubview.

code show

user1511613
la source
0

Partagez mon approche.

UITableView+XXXAdditions.m

- (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock {
      CGFloat containerViewHeight = 0;
      UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
      [backgroundView addSubview:tableHeaderView];
      layoutBlock(tableHeaderView, &containerViewHeight);

      backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight);

      self.tableHeaderView = backgroundView;
}

Usage.

[self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) {
    *containerViewHeight = 170;

    [tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
      make.top.equalTo(@20);
      make.centerX.equalTo(@0);
      make.size.mas_equalTo(CGSizeMake(130, 130));
    }];
  }];
Vincent Sit
la source
0

Dans mon cas, la méthode avec systemLayoutSizeFittingSize pour une raison quelconque n'a pas fonctionné. Ce qui a fonctionné pour moi, c'est une modification de la solution publiée par HotJard (sa solution originale ne fonctionnait pas non plus dans mon cas sur iOS 8). Ce que je devais faire, c'est dans la vue d'en-tête, placer une sous-vue et l'épingler à gauche, à droite, en haut (ne pas épingler en bas). Mettez tout en utilisant la mise en page automatique dans cette sous-vue et dans le code, procédez comme suit:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [self resizeHeaderToFitSubview];
}

- (void)resizeHeaderToFitSubview
{
    UIView *header = self.tableView.tableHeaderView;
    [header setNeedsLayout];
    [header layoutIfNeeded];
    CGFloat height = CGRectGetHeight(header.subviews.firstObject.bounds);
    header.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
    self.tableView.tableHeaderView = nil;
    self.tableView.tableHeaderView = header;
}
Leszek Szary
la source
0

Un vieux poste. Mais un bon post. Voici mes 2 cents.

Tout d'abord, assurez-vous que votre vue d'en-tête a ses contraintes arrangées de manière à pouvoir prendre en charge sa propre taille de contenu intrinsèque. Ensuite, procédez comme suit.

//ViewDidLoad
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.configure(title: "Some Text A")

//Somewhere else
headerView.update(title: "Some Text B)

private var widthConstrained = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if widthConstrained == false {
        widthConstrained = true
        tableView.addConstraint(NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: tableView, attribute: .width, multiplier: 1, constant: 0))
        headerView.layoutIfNeeded()
        tableView.layoutIfNeeded()
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { (context) in
        self.headerView.layoutIfNeeded()
        self.tableView.layoutIfNeeded()
    }, completion: nil)
}

la source
0

J'ai pu y parvenir par l'approche suivante (cela fonctionne pour le pied de page de la même manière).

Tout d'abord, vous aurez besoin d'une petite UITableViewextension:

Swift 3

extension UITableView {
    fileprivate func adjustHeaderHeight() {
        if let header = self.tableHeaderView {
            adjustFrame(header)
        }
    }

    private func adjustFrame(_ view: UIView) {
        view.frame.size.height = calculatedViewHeight(view)
    }

    fileprivate func calculatedHeightForHeader() -> CGFloat {
        if let header = self.tableHeaderView {
            return calculatedViewHeight(header)
        }
        return 0.0
    }

    private func calculatedViewHeight(_ view: UIView) -> CGFloat {
        view.setNeedsLayout()
        let height = view.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        return height
    }
}

Dans votre implémentation de classe de contrôleur de vue:

// this is a UIView subclass with autolayout
private var headerView = MyHeaderView()

override func loadView() {
    super.loadView()
    // ...
    self.tableView.tableHeaderView = headerView
    self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension
    // ...
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    // this is to prevent recursive layout calls
    let requiredHeaderHeight = self.tableView.calculatedHeightForHeader()
    if self.headerView.frame.height != requiredHeaderHeight {
        self.tableView.adjustHeaderHeight()
    }
}

Remarques sur l' UIViewimplémentation de sous -vue d'un en-tête :

  1. Vous devez être sûr à 100% que votre vue d'en-tête a la configuration de mise en page automatique correcte. Je recommanderais de commencer avec une vue d'en-tête simple avec une seule contrainte de hauteur et d'essayer la configuration ci-dessus.

  2. Remplacer requiresConstraintBasedLayoutet retourner true:

.

class MyHeaderView: UIView {
   // ...
   override static var requiresConstraintBasedLayout : Bool {
       return true
   }
   // ...
}
Yevhen Dubinin
la source
0

Pour les utilisateurs Xamarin:

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableviewHeader.SetNeedsLayout();
    TableviewHeader.LayoutIfNeeded();

    var height = TableviewHeader.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize).Height;
    var frame = TableviewHeader.Frame;
    frame.Height = height;
    TableviewHeader.Frame = frame;
}

En supposant que vous ayez nommé la vue d'en-tête de votre tableview comme TableviewHeader

Gustavo Baiocchi Costa
la source
0

Voici comment faire dans votre UIViewController

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if headerView.frame.size.height == 0 {
      headerView.label.preferredMaxLayoutWidth = view.bounds.size.width - 20
      let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height

      headerView.frame.size = CGSize(width: tableView.bounds.size.width, height: height)
    }
  }
onmyway133
la source
0

Tout basé sur des contraintes UIViewpeut être un bien tableHeaderView.

Il faut définir un tableFooterViewavant, puis imposer une contrainte de fin supplémentaire sur tableFooterViewet tableHeaderView.

- (void)viewDidLoad {

    ........................
    // let self.headerView is some constraint-based UIView
    self.tableView.tableFooterView = [UIView new];
    [self.headerView layoutIfNeeded];
    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;
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;

}

On peut trouver tous les détails et extraits de code ici

malex
la source
0

J'ai trouvé une solution de contournement. encapsulez votre vue d'en-tête xib écrite de mise en page automatique dans un wrapper uiview vide et affectez la vue d'en-tête à la propriété tableViewHeader de tableView.

    UIView *headerWrapper = [[UIView alloc] init];
    AXLHomeDriverHeaderView *headerView = [AXLHomeDriverHeaderView loadViewFromNib];
    [headerWrapper addSubview:headerView];
    [headerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(headerWrapper);
    }];
    self.tableView.tableHeaderView = headerView;
tounaobun
la source
0

Voici ce qui fonctionne pour UITableViewController dans iOS 12,

Drap un UIView dans le TableView au-dessus de toutes les cellules prototypes pour l'en-tête et au-dessous de toutes les cellules prototypes pour le pied de page. Configurez votre en-tête et votre pied de page selon vos besoins. Définissez toutes les contraintes requises.

Maintenant, utilisez les méthodes d'extension suivantes

public static class UITableVIewExtensions
{

    public static void MakeHeaderAutoDimension(this UITableView tableView)
    {
        if (tableView.TableHeaderView is UIView headerView) {
            var size = headerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
            if (headerView.Frame.Size.Height != size.Height) {
                var frame = headerView.Frame;
                frame.Height = size.Height;
                headerView.Frame = frame;
                tableView.TableHeaderView = headerView;
                tableView.LayoutIfNeeded();
            }
        }
    }

    public static void MakeFooterAutoDimension(this UITableView tableView)
    {
        if (tableView.TableFooterView is UIView footerView) {
            var size = footerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
            if (footerView.Frame.Size.Height != size.Height) {
                var frame = footerView.Frame;
                frame.Height = size.Height;
                footerView.Frame = frame;
                tableView.TableFooterView = footerView;
                tableView.LayoutIfNeeded();
            }
        }
    }
}

et appelez-le dans ViewDidLayoutSubviews de la sous-classe de UITableViewController

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableView.MakeHeaderAutoDimension();
    TableView.MakeFooterAutoDimension();
}
Papa ivre
la source
0

J'ai rencontré le problème d'obtenir une largeur de 375pt, le seul moyen qui a fonctionné pour moi est de relayer la tableView pour obtenir la largeur correcte. J'ai également préféré la mise en page automatique à la définition de la taille du cadre.

Voici la version qui fonctionne pour moi:

Xamarin.iOS

public static void AutoLayoutTableHeaderView(this UITableView tableView, UIView header)
{
    tableView.TableHeaderView = header;
    tableView.SetNeedsLayout();
    tableView.LayoutIfNeeded();
    header.WidthAnchor.ConstraintEqualTo(tableView.Bounds.Width).Active = true;       
    tableView.TableHeaderView = header;
}

Version Swift (modifiée à partir de la réponse @Ben Packard)

extension UITableView {
    //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
    func setAndLayoutTableHeaderView(header: UIView) {
        self.tableHeaderView = header
        self.setNeedsLayout()
        self.layoutIfNeeded()
        header.widthAnchor.widthAnchor.constraint(equalTo: self.bounds.width).isActive = true
        self.tableHeaderView = header
    }
}
Baron Ch'ng
la source
0

Ma solution est de créer une nouvelle classe comme celle-ci.

class BaseTableHeaderView: UIView {

    func sizeToFitBasedOnConstraints(width: CGFloat = Screen.width) {
        let size = systemLayoutSizeFitting(CGSize(width: width, height: 10000),
                                              withHorizontalFittingPriority: .required,
                                              verticalFittingPriority: .fittingSizeLevel)
        frame = CGRect(origin: .zero, size: size)
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        sizeToFitBasedOnConstraints()
        super.willMove(toSuperview: newSuperview)
    }

}

Pour l'utiliser, ajoutez simplement toutes vos sous-vues sur une instance de BaseTableHeaderViewet attachez-la à votre vue de tableau.

let tableHeaderView = BaseTableHeaderView()
tableHeaderView.addSubview(...)
tableView.tableHeaderView = tableHeaderView

Il se redimensionnera automatiquement en fonction de ses contraintes.

Hesse Huang
la source
-1

La réponse acceptée n'est utile que pour les tableaux avec une seule section. Pour les sections multiples, UITableViewassurez-vous simplement que votre en-tête hérite deUITableViewHeaderFooterView et tout ira bien.

Au lieu de cela, intégrez simplement votre en-tête actuel dans le contentViewfichier UITableViewHeaderFooterView. Exactement comme des UITableViewCellœuvres.

redent84
la source
9
question tableHeaderViewne concerne pas l'en-tête de la section.
Rpranata