Redimensionner UITableView pour l'adapter au contenu

170

Je crée une application qui aura une question dans un UILabelet une réponse à choix multiple affichée dans UITableView, chaque ligne montrant un choix multiple. Les questions et les réponses varieront, donc j'ai besoin que cela UITableViewsoit dynamique en hauteur.

Je voudrais trouver un sizeToFittravail autour de la table. Où le cadre de la table est défini à la hauteur de tout son contenu.

Quelqu'un peut-il me conseiller sur la façon dont je peux y parvenir?

MiMo
la source
Pour tous ceux qui cherchent à faire une chose similaire sur OSX, voir cette question: stackoverflow.com/questions/11322139/…
Michael Teper
1
@MiMo salut, pouvez-vous expliquer ce qui ne va pas avec l'approche "sizeToFit" s'il vous plaît? :)
iamdavidlam
"Je voudrais trouver une solution" sizeToFit " . Alors, avez-vous ou n'avez-vous pas essayé la - sizeToFitméthode d'UIView ?
Slipp D.Thompson

Réponses:

155

En fait, j'ai trouvé la réponse moi-même.

Je viens de créer un nouveau CGRectpour le tableView.frameavec le heightdetable.contentSize.height

Cela définit la hauteur de la UITableViewà la heightde son contenu. Puisque le code modifie l'interface utilisateur, n'oubliez pas de l'exécuter dans le thread principal:

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });
MiMo
la source
3
Il est possible de rencontrer des problèmes avec cette méthode. Si vous avez une grande liste, il est possible que la hauteur du cadre coupe certaines de vos cellules. Vous pouvez voir que cela se produit si vous avez activé le rebond et faites défiler vers le bas pour voir certaines cellules rebondir hors de vue.
whyoz
Où exactement avez-vous spécifié la hauteur? Avez-vous passé un appel pour reloadData et le redimensionner après?
James
27
Où est le meilleur endroit pour mettre ce code? Je l'ai essayé dans viewDidLoad et cela n'a probablement pas fonctionné parce que les méthodes de délégué tableview ne s'étaient pas encore déclenchées. Quelle méthode déléguée est la meilleure?
Kyle Clegg
4
Je l'ai fait est viewDidAppear, et cela semble avoir fonctionné. Notez que la tableview peut être un peu plus grande que son contenu car elle laisse un peu d'espace pour un en-tête et un pied de page de section
Jameo
2
J'ai trouvé que viewDidAppear est un endroit idéal pour mettre les choses que vous voulez faire après que tous les appels initiaux de tableView cellForRowAtIndexPath sont terminés
rigdonmr
120

Solution Swift 5 et 4.2 sans KVO, DispatchQueue ou définition de contraintes vous-même.

Cette solution est basée sur la réponse de Gulz .

1) Créez une sous-classe de UITableView:

import UIKit

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

2) Ajoutez un UITableViewà votre mise en page et définissez des contraintes sur tous les côtés. Définissez sa classe sur ContentSizedTableView.

3) Vous devriez voir des erreurs, car Storyboard ne prend pas en compte notre sous-classe intrinsicContentSize. Résolvez ce problème en ouvrant l'inspecteur de taille et en remplaçant intrinsicContentSize par une valeur d'espace réservé. Il s'agit d'un remplacement pour la conception. Au moment de l'exécution, il utilisera le remplacement dans notre ContentSizedTableViewclasse


Mise à jour: code modifié pour Swift 4.2. Si vous utilisez une version antérieure, utilisez à la UIViewNoIntrinsicMetricplace deUIView.noIntrinsicMetric

fl034
la source
1
Très bonne réponse, cela a fonctionné pour moi, merci. Une chose cependant - dans mon cas, j'avais besoin de changer la classe du tableViewpour être IntrinsicTableViewdans le storyboard. Je n'avais pas besoin d'intégrer ma vue de table dans une autre UIView.
dvp.petrov
Oui vous avez raison. J'ai mis à jour ma réponse. L'étape 2 pourrait être lue comme vous l'avez dit. Bien sûr, je voulais définir le tableViewà IntrinsicTableView. De plus, un emballage supplémentaire UIViewn'est pas nécessaire. Vous pouvez utiliser n'importe quelle vue parent existante bien sûr :)
fl034
1
Swift 4 Cela a fonctionné pour moi très bientableView.sizeToFit()
Souf ROCHDI
Ouais, cela peut être une bonne solution, si votre contenu est statique. Avec mon approche, vous pouvez utiliser la mise en page automatique pour intégrer une tableView avec un contenu dynamique dans d'autres vues et la maintenir en pleine hauteur tout le temps, sans penser à quels endroits sizeToFit()doivent être appelés
fl034
2
Merveilleux, merci!. Enfin, faites correspondre le contenu du parent et du wrapper pour tableview ...
Amber K
79

Solution rapide

Suivez ces étapes:

1- Définissez la contrainte de hauteur pour la table à partir du storyboard.

2- Faites glisser la contrainte de hauteur depuis le storyboard et créez-y @IBOutlet dans le fichier du contrôleur de vue.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Ensuite, vous pouvez modifier dynamiquement la hauteur de la table en utilisant ce code:

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
}

Mettre à jour

Si la dernière ligne est coupée pour vous, essayez d'appeler viewWillLayoutSubviews () dans la fonction de cellule willDisplay:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}
Musa almatri
la source
1
A travaillé pour moi !! thnx
Pushpa Y
5
Cette solution coupe toujours la dernière ligne sur Xcode 8.3 pour moi.
Jen C
@Jec C: Pas pour moi non plus
KMC
A travaillé pour moi aussi !!
Antony Raphel
1
J'ai dû mettre cela dans la méthode cellForRowAtIndexPath:. Si je le mets dans la méthode viewWillLayoutSubviews, la hauteur de contentSize calculée est basée sur la hauteur estimée, et non sur la hauteur réelle du contenu.
Manu Mateos
21

J'ai essayé cela dans iOS 7 et cela a fonctionné pour moi

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
Alexey
la source
3
Si votre UITableView est dynamique (ce qui signifie que les informations se chargent vers et à partir de lui à la volée), vous devrez le mettre ailleurs, pas dans viewDidLoad. Pour moi, je l'ai mis dans cellForRowAtIndexPath. J'ai également dû remplacer "tableView" par le nom de ma propriété IBOutlet pour éviter l'ancienne erreur "propriété tableView introuvable".
jeremytripp
thx, j'ai eu un problème avec le redimensionnement de la tableview à l'intérieur de la popover, cela a fonctionné
Zaporozhchenko Oleksandr
19

Ajoutez un observateur pour la propriété contentSize sur la vue de table et ajustez la taille du cadre en conséquence

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

puis dans le rappel:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

J'espère que ceci vous aidera.

VM Anooj
la source
2
Vous devriez ajouter une dernière chose. Vous devez supprimer l'observateur de la vue de table. Parce qu'il plantera si la cellule est désallouée.
Mahesh Agrawal
12

J'avais une vue de table à l'intérieur de la vue de défilement et j'ai dû calculer la hauteur de tableView et la redimensionner en conséquence. Voici les mesures que j'ai prises:

0) ajoutez un UIView à votre scrollView (fonctionnera probablement sans cette étape mais je l'ai fait pour éviter tout conflit éventuel) - ce sera une vue conteneur pour votre vue table. Si vous suivez cette étape, définissez les bordures de la vue directement sur celles de la vue table.

1) Créez une sous-classe de UITableView:

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
    }

}

2) définir la classe d'une vue de table dans Storyboard sur IntrinsicTableView: capture d'écran: http://joxi.ru/a2XEENpsyBWq0A

3) Définissez heightConstraint sur votre vue de table

4) faites glisser l'IBoutlet de votre table vers votre ViewController

5) faites glisser l'IBoutlet de la contrainte de hauteur de votre table vers votre ViewController

6) ajoutez cette méthode dans votre ViewController:

override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
    }

J'espère que cela t'aides

Gulz
la source
Voir ma réponse qui est basée sur votre sous-classe mais qui ne nécessite aucun réglage de contraintes de hauteur.
fl034
@ fl034 fournir un lien?
Gulz
@ fl034 Je ne pense pas que vous puissiez éviter d'utiliser des contraintes lorsque vos éléments sont incorporés dans la vue de défilement) Mon cas est avec la vue de défilement
Gulz
J'ai aussi ma vue de table dans une vue de défilement et cela fonctionne parfaitement :)
fl034
fonctionne pour moi sur iOS 13 / Xcode 11 Vous avez mis fin à 3 jours de souffrance gentil monsieur, merci
Mehdi S.
8

Si vous ne souhaitez pas suivre vous-même les changements de taille de contenu de la vue tableau, vous pouvez trouver cette sous-classe utile.

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {

    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }

            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}
xinatanil
la source
1
Pour swift 3, intrinsicContentSize est devenu une var, commeoverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod
ne fonctionne pas pour moi. se cachant toujours en bas de la table
Gulz
5

Si votre contentSize n'est pas correct, c'est parce qu'il est basé sur la valeur EstiméeRowHeight (automatique), utilisez ceci avant

tableView.estimatedRowHeight = 0;

source: https://forums.developer.apple.com/thread/81895

Yohan Dahmani
la source
4

Swift 3, iOS 10.3

Solution 1: Mettez simplementself.tableview.sizeToFit()encellForRowAt indexPathfonction. Assurez-vous de régler la hauteur de la vue de la table plus élevée que nécessaire. C'est une bonne solution si vous n'avez pas de vues sous tableview. Cependant, si vous avez, la contrainte de vue du bas de la table ne sera pas mise à jour (je n'ai pas essayé de la corriger car j'ai trouvé la solution 2)

Exemple:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }

    return UITableViewCell()
}

Solution 2: définissez la contrainte de hauteur de tableview dans le storyboard et faites-la glisser vers le ViewController. Si vous connaissez la hauteur moyenne de votre cellule et que vous savez combien d'éléments votre tableau contient, vous pouvez faire quelque chose comme ceci:

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*ÉDITER:

Une fois que toutes les solutions que j'ai essayé et chacun d'eux réparait quelque chose, mais pas complètement, c'est la réponse qui explique et corrige complètement ce problème.

Đorđe Nilović
la source
C'est une solution très simple, cela a fonctionné pour moi, Merci @Dorde Nilovic
R. Mohan
1

La réponse de Mimo et la réponse de Anooj VM à la fois sont impressionnantes , mais il y a un petit problème si vous avez une grande liste, il est possible que la hauteur du cadre sera coupure de vos cellules.

Alors. J'ai un peu modifié la réponse:

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}
SandeepAggarwal
la source
1

Il existe une bien meilleure façon de le faire si vous utilisez la mise en page automatique: modifiez la contrainte qui détermine la hauteur. Calculez simplement la hauteur du contenu de votre table, puis trouvez la contrainte et modifiez-la. Voici un exemple (en supposant que la contrainte qui détermine la hauteur de votre table est en fait une contrainte de hauteur avec la relation «Égal»):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}
ChrisB
la source
Vous pouvez améliorer cela. Ajoutez une contrainte supplémentaire à votre vue de table, par exemple, hauteur <= 10000. Utilisez le contrôle-glisser depuis Interface Builder vers votre fichier .h pour faire de cette contrainte une propriété de votre contrôleur de vue. Vous pouvez alors éviter d'itérer à travers les contraintes et la recherche. Juste directement réglémyTableHeightConstraint.constant = tableView.contentSize.height
RobP
1

Cela fonctionne pour moi en utilisant la disposition automatique, avec une vue de tableau avec une seule section.

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

J'appelle cette fonction lors de la configuration de la disposition automatique (l'exemple ici utilise SnapKit, mais vous voyez l'idée):

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

Je veux que l'UITableView soit aussi haut que la hauteur combinée des cellules; Je boucle à travers les cellules et accumule la hauteur totale des cellules. Étant donné que la taille de la vue de tableau est CGRect.zero à ce stade, je dois définir les limites pour pouvoir respecter les règles de disposition automatique définies par la cellule. J'ai défini la taille sur une valeur arbitraire qui devrait être suffisamment grande. La taille réelle sera calculée ultérieurement par le système de mise en page automatique.

André
la source
1

Je l'ai fait d'une manière un peu différente, en fait, mon TableView était à l'intérieur de scrollview, j'ai donc dû donner une contrainte de hauteur à 0.

Ensuite, au moment de l'exécution, j'ai apporté les modifications suivantes,

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }
Niki
la source
0

En tant qu'extension de la réponse d'Anooj VM, je suggère ce qui suit pour actualiser la taille du contenu uniquement lorsqu'elle change.

Cette approche permet également de désactiver correctement le défilement et de prendre en charge des listes et une rotation plus grandes . Il n'est pas nécessaire de dispatch_async car les modifications contentSize sont réparties sur le thread principal.

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}


- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }

- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}
Ramiro
la source
0

version objc de Musa almatri

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}
Anton Tropashko
la source
0

Vous pouvez essayer cette coutume AGTableView

Pour définir une contrainte de hauteur TableView à l'aide du storyboard ou par programme. (Cette classe récupère automatiquement une contrainte de hauteur et définit la hauteur de la vue du contenu sur la hauteur de votre table).

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!

    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }

    override open func layoutSubviews() {
        super.layoutSubviews()

        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }

    func associateConstraints() {
        // iterate through height constraints and identify

        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

Remarque En cas de problème pour définir une hauteur, alors yourTableView.layoutSubviews().

AshvinGudaliya
la source
0

Basé sur la réponse de fl034 . Mais pour les utilisateurs de Xamarin.iOS :

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }
Jelle
la source
0

Ma mise en œuvre Swift 5 consiste à définir la contrainte de hauteur de la tableView à la taille de son contenu ( contentSize.height). Cette méthode suppose que vous utilisez la disposition automatique. Ce code doit être placé à l'intérieur de la cellForRowAtméthode tableView.

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
Dominic Egginton
la source
-1

Si vous souhaitez que votre tableau soit dynamique, vous devrez utiliser une solution basée sur le contenu de la table comme détaillé ci-dessus. Si vous souhaitez simplement afficher une table plus petite, vous pouvez utiliser une vue de conteneur et y incorporer un UITableViewController - l'UITableView sera redimensionné en fonction de la taille du conteneur.

Cela évite beaucoup de calculs et d'appels à la mise en page.

chevalier_ vert
la source
1
N'utilisez pas de mots abovecar les réponses pourraient être mélangées.
Nik Kov le
-3

Solution Mu pour cela dans swift 3 : appelez cette méthode dansviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
Brahimm
la source