web-dev-qa-db-fra.com

Redimensionner UITableView pour l'adapter au contenu

Je crée une application qui aura une question dans un UILabel et des réponses à choix multiples affichées dans UITableView, chaque ligne indiquant un choix multiple. Les questions et les réponses varient, j'ai donc besoin que UITableView soit dynamique en hauteur.

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

Quelqu'un peut-il me dire comment je peux y arriver?

145
MiMo

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

Je viens de créer un nouveau CGRect pour le tableView.frame avec le height de table.contentSize.height

Cela définit la hauteur de UITableView sur height de 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;
    });
148
MiMo

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

Cette solution est basée sur 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 quelques erreurs, car Storyboard ne prend pas en compte notre intrinsicContentSize de notre sous-classe. Résolvez ce problème en ouvrant l'inspecteur de taille et en remplaçant le paramètre intrinsicContentSize par une valeur d'espace réservé. Ceci est un remplacement pour le temps de conception. Au moment de l'exécution, il utilisera le remplacement dans notre classe ContentSizedTableView


Mise à jour: Code modifié pour Swift 4.2. Si vous utilisez une version antérieure, utilisez UIViewNoIntrinsicMetric au lieu de UIView.noIntrinsicMetric

67
fl034

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 du storyboard et créez-la @IBOutlet dans le fichier du contrôleur de vue.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

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

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

Mise à 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()
}
55
Musa almatri

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

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
20
Alexey

Ajoutez un observateur pour la propriété contentSize dans la vue tableau 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 cela vous aidera.

18
Anooj VM

Si vous ne souhaitez pas suivre vous-même les modifications de la taille du contenu de la vue Table, 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
    }
}
8
xinatanil

J'avais une vue de table à l'intérieur de la vue de défilement et devais calculer la hauteur de tableView et la redimensionner en conséquence. Ce sont des 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 possible) - ce sera une vue conteneur pour votre vue tableau. Si vous suivez cette étape, définissez ensuite les bordures des vues sur celles de tableview.

1) créer 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 la heightConstraint à votre vue de table

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

5) faites glisser la contrainte de hauteur IBoutlet de votre table sur votre ViewController

6) ajoutez cette méthode à votre ViewController:

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

J'espère que cela t'aides

6
Gulz

Swift 3, iOS 10.3

Solution 1: Il suffit de mettre self.tableview.sizeToFit() dans cellForRowAt indexPath une fonction. Veillez à définir une hauteur de table supérieure à celle dont vous avez besoin. C'est une bonne solution si vous n'avez pas de vues sous tableview. Cependant, si vous l’avez, la contrainte de la base de la table ne sera pas mise à jour (je n’ai pas essayé de la réparer car j’ai eu 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 la vue table dans le storyboard et faites-la glisser vers ViewController. Si vous connaissez la hauteur moyenne de votre cellule et le nombre d'éléments contenus dans votre tableau, vous pouvez procéder de la manière suivante:

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

* EDIT:

Après toutes les solutions que j’ai essayées et que chacune d’elles corrigeait quelque chose, mais pas complètement, this est la réponse qui explique et corrige complètement ce problème.

3
Đorđe Nilović

Si votre contentSize n’est pas correct, c’est parce qu’il est basé sur estimationRowHeight (automatique), utilisez-le avant.

tableView.estimatedRowHeight = 0;

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

3
Yohan Dahmani

Il existe un moyen bien plus simple de le faire si vous utilisez AutoLayout: modifiez la contrainte qui détermine la hauteur. Calculez simplement la hauteur du contenu de votre table, puis recherchez la contrainte et modifiez-la. Voici un exemple (en supposant que la contrainte qui détermine la hauteur de votre table est en réalité une contrainte de hauteur avec la relation "Egal"):

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
            }
        }
    }
}
1
ChrisB

réponse de Mimo et Anooj VM les deux sont géniaux, mais il y a un petit problème si vous avez une longue liste, il est possible que la hauteur du cadre coupera certaines de vos cellules.

Alors. J'ai modifié un peu 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;
}
1
SandeepAggarwal

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

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

0
green_knight

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);
            }
        }
    }
0
Jelle

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

Cette approche aussi désactiver le défilement correctement et prendre en charge des listes plus volumineuses et rotation. Dispatch_async n'est pas nécessaire car les modifications de contentSize sont distribuées 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"];
}
0
Ramiro

Vous pouvez essayer cette coutume AGTableView

Pour définir une contrainte de hauteur TableView Utilisation 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 votre hauteur de vue de 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
                }
            }
        }
    }
}

Note S'il y a un problème pour définir une hauteur, alors yourTableView.layoutSubviews().

0
AshvinGudaliya

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;
}
0
Anton Tropashko