Ändern der Größe von UITableView an den Inhalt


170

Ich erstelle eine App, in der eine Frage in einer UILabelund eine Multiple-Choice-Antwort angezeigt UITableViewwerden. Jede Zeile zeigt eine Multiple-Choice- Antwort . Fragen und Antworten variieren, daher muss diese UITableViewHöhe dynamisch sein.

Ich würde gerne eine sizeToFitArbeit für den Tisch finden. Wo der Rahmen der Tabelle auf die Höhe des gesamten Inhalts eingestellt ist.

Kann mir jemand raten, wie ich das erreichen kann?


Für alle, die unter OSX etwas Ähnliches tun möchten
Michael Teper

1
@ MiMo Hallo, können Sie bitte erklären, was mit dem "sizeToFit" -Ansatz falsch ist? :)
iamdavidlam

"Ich würde gerne eine" sizeToFit "-Umgehung finden" . Haben Sie die UIView- - sizeToFitMethode ausprobiert oder nicht ?
Slipp D. Thompson

Antworten:


155

Eigentlich habe ich die Antwort selbst gefunden.

Ich erstelle gerade ein neues CGRectfür das tableView.framemit dem heightvontable.contentSize.height

Das setzt die Höhe des UITableViewauf den heightInhalt. Da der Code die Benutzeroberfläche ändert, vergessen Sie nicht, ihn im Hauptthread auszuführen:

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

3
Bei dieser Methode können Probleme auftreten. Wenn Sie eine große Liste haben, ist es möglich, dass die Höhe des Rahmens einige Ihrer Zellen abschneidet. Sie können dies sehen, wenn Sie das Bouncen aktiviert haben und nach unten scrollen, um zu sehen, dass einige Zellen nicht mehr sichtbar sind.
Whyoz

Wo genau haben Sie die Höhe angegeben? Haben Sie reloadData aufgerufen und die Größe der Nachwörter geändert?
James

27
Wo kann man diesen Code am besten ablegen? Ich habe es in viewDidLoad versucht und es hat vermutlich nicht funktioniert, da die Tableview-Delegatenmethoden noch nicht ausgelöst wurden. Welche Delegate-Methode ist die beste?
Kyle Clegg

4
Ich habe dies getan, ist viewDidAppear, und es scheint funktioniert zu haben. Beachten Sie, dass die Tabellenansicht möglicherweise etwas größer als ihr Inhalt ist, da sie etwas Platz für eine Abschnittsüberschrift und
-fußzeile lässt

2
Ich habe festgestellt, dass viewDidAppear ein großartiger Ort ist, um Dinge zu platzieren, die passieren sollen, nachdem alle ersten tableView cellForRowAtIndexPath-Aufrufe beendet wurden
rigdonmr

120

Schnelle 5 und 4.2-Lösung ohne KVO, DispatchQueue oder das Festlegen von Einschränkungen.

Diese Lösung basiert auf Gulz 'Antwort .

1) Erstellen Sie eine Unterklasse von 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) Fügen Sie UITableViewIhrem Layout ein hinzu und legen Sie auf allen Seiten Einschränkungen fest. Setzen Sie die Klasse auf ContentSizedTableView.

3) Sie sollten einige Fehler sehen, da Storyboard unsere Unterklasse nicht intrinsicContentSizeberücksichtigt. Beheben Sie dies, indem Sie den Größeninspektor öffnen und die intrinsicContentSize auf einen Platzhalterwert überschreiben. Dies ist eine Überschreibung für die Entwurfszeit. Zur Laufzeit wird die Überschreibung in unserer ContentSizedTableViewKlasse verwendet


Update: Code für Swift 4.2 geändert. Wenn Sie eine frühere Version verwenden, verwenden Sie UIViewNoIntrinsicMetricanstelle vonUIView.noIntrinsicMetric


1
Sehr gute Antwort, es hat für mich geklappt, danke. Eines jedoch - in meinem Fall musste ich die Klasse der ändern tableView, um IntrinsicTableViewim Storyboard zu sein. Ich musste meine Tabellenansicht nicht in eine andere einbetten UIView.
dvp.petrov

Ja, du hast recht. Ich habe meine Antwort aktualisiert. Schritt 2 könnte wie gesagt gelesen werden. Natürlich bedeuten , dass ich das einstellen tableViewzu IntrinsicTableView. Auch eine zusätzliche Verpackung UIViewist nicht erforderlich. Sie können natürlich jede vorhandene übergeordnete Ansicht verwenden :)
fl034

1
Swift 4 Das hat bei mir ganz gut tableView.sizeToFit()
funktioniert

Ja, das kann eine gute Lösung sein, wenn Ihr Inhalt statisch ist. Mit meinem Ansatz können Sie Auto-Layout verwenden, um eine Tabellenansicht mit dynamischem Inhalt in andere Ansichten einzubetten und sie die ganze Zeit in voller Höhe zu halten, ohne darüber nachzudenken, an welchen Stellen sizeToFit()aufgerufen werden muss
fl034

2
Wunderbar, Danke!. Schließlich passen Eltern und Wrap-Inhalt für Tabellenansicht ...
Amber K

79

Schnelle Lösung

Folge diesen Schritten:

1- Legen Sie die Höhenbeschränkung für die Tabelle im Storyboard fest.

2- Ziehen Sie die Höhenbeschränkung aus dem Storyboard und erstellen Sie ein @IBOutlet dafür in der View Controller-Datei.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Dann können Sie die Höhe für die Tabelle dynamisch mit diesem Code ändern:

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

Aktualisieren

Wenn die letzte Zeile für Sie ausgeschnitten ist, versuchen Sie, viewWillLayoutSubviews () in der willDisplay-Zellenfunktion aufzurufen:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}

1
Hat für mich gearbeitet !! Danke
Pushpa Y

5
Diese Lösung hat für mich immer die letzte Zeile in Xcode 8.3 abgeschnitten.
Jen C

@Jec C: Auch nicht für mich
KMC

Hat auch für mich gearbeitet !!
Antony Raphel

1
Ich musste dies in die cellForRowAtIndexPath: -Methode einfügen. Wenn ich es in die viewWillLayoutSubviews-Methode einfüge, basiert die berechnete contentSize-Höhe auf der geschätzten Höhe und nicht auf der tatsächlichen Inhaltshöhe.
Manu Mateos

21

Ich habe dies in iOS 7 versucht und es hat bei mir funktioniert

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

3
Wenn Ihre UITableView dynamisch ist (dh Informationen werden im laufenden Betrieb von und zu ihr geladen), müssen Sie diese an einer anderen Stelle ablegen, nicht in viewDidLoad. Für mich habe ich es in cellForRowAtIndexPath eingefügt. Außerdem musste "tableView" in den Namen meiner IBOutlet-Eigenschaft geändert werden, um den alten Fehler "property tableView not found" zu vermeiden.
Jeremytripp

thx, hatte Problem mit der Größenänderung der Tabellenansicht in Popover, es funktionierte
Zaporozhchenko Oleksandr

19

Fügen Sie in der Tabellenansicht einen Beobachter für die contentSize-Eigenschaft hinzu und passen Sie die Rahmengröße entsprechend an

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

dann im Rückruf:

- (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;
    }

Hoffe das wird dir helfen.


2
Sie sollten noch eine Sache hinzufügen. Sie sollten den Beobachter für die Tabellenansicht entfernen. Weil es abstürzt, wenn die Zelle freigegeben wird.
Mahesh Agrawal

12

Ich hatte eine Tabellenansicht in der Bildlaufansicht und musste die Höhe von tableView berechnen und die Größe entsprechend ändern. Das sind Schritte, die ich unternommen habe:

0) Fügen Sie Ihrer scrollView eine UIView hinzu (funktioniert wahrscheinlich ohne diesen Schritt, aber ich habe es getan, um mögliche Konflikte zu vermeiden) - dies ist eine Containr-Ansicht für Ihre Tabellenansicht. Wenn Sie diesen Schritt ausführen, setzen Sie die Ansichtsrahmen direkt auf die der Tabellenansicht.

1) Erstellen Sie eine Unterklasse von 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) Setzen Sie die Klasse einer Tabellenansicht im Storyboard auf IntrinsicTableView: Screenshot: http://joxi.ru/a2XEENpsyBWq0A

3) Stellen Sie die heightConstraint auf Ihre Tabellenansicht ein

4) Ziehen Sie das IBoutlet Ihrer Tabelle auf Ihren ViewController

5) Ziehen Sie das IBoutlet der Höhenbeschränkung Ihrer Tabelle auf Ihren ViewController

6) Fügen Sie diese Methode Ihrem ViewController hinzu:

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

Hoffe das hilft


Siehe meine Antwort, die auf Ihrer Unterklasse basiert, aber keine Einstellung der Höhenbeschränkungen erfordert.
fl034

@ fl034 Link bereitstellen?
Gulz

@ fl034 Ich glaube nicht, dass Sie die Verwendung von Einschränkungen vermeiden können, wenn Ihre Elemente in die Bildlaufansicht eingebettet sind.) Mein Fall ist mit der ScrollView
Gulz

Ich habe auch meine Tabellenansicht in einer
Bildlaufansicht

funktioniert für mich unter iOS 13 / Xcode 11 Sie haben 3 Tage des Leidens beendet, mein Herr, danke
Mehdi S.

8

Falls Sie die Änderungen der Inhaltsgröße der Tabellenansicht nicht selbst verfolgen möchten, ist diese Unterklasse möglicherweise hilfreich.

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

1
Für Swift 3 ist intrinsicContentSize zu einem var geworden, wieoverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod

Ich arbeite nicht für mich. versteckt immer noch den unteren Rand des Tisches
Gulz


4

Swift 3, iOS 10.3

Lösung 1: Einfachself.tableview.sizeToFit()incellForRowAt indexPathFunktion setzen. Stellen Sie sicher, dass die Höhe der Tabellenansicht höher als erforderlich ist. Dies ist eine gute Lösung, wenn Sie keine Ansichten unterhalb der Tabellenansicht haben. Wenn dies jedoch der Fall ist, wird die Einschränkung für die untere Tabellenansicht nicht aktualisiert (ich habe nicht versucht, sie zu beheben, da ich Lösung 2 gefunden habe).

Beispiel:

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()
}

Lösung 2: Legen Sie die Höhenbeschränkung für die Tabellenansicht im Storyboard fest und ziehen Sie sie auf den ViewController. Wenn Sie die durchschnittliche Höhe Ihrer Zelle kennen und wissen, wie viele Elemente Ihr Array enthält, können Sie Folgendes tun:

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

*BEARBEITEN:

Nach all den Lösungen, die ich ausprobiert habe und von denen jeder etwas repariert hat, aber nicht vollständig, ist dies die Antwort, die dieses Problem erklärt und vollständig behebt.


Dies ist eine sehr einfache Lösung, es hat bei mir funktioniert. Danke @Dorde Nilovic
R. Mohan

1

Mimo Antwort und Anooj VM ‚s Antwort beide sind genial , aber es ist ein kleines Problem , wenn Sie eine große Liste haben, ist es möglich , dass die Höhe des Rahmens werden einige Ihrer Zellen Cutoff.

So. Ich habe die Antwort ein wenig geändert:

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

Wenn Sie AutoLayout verwenden, gibt es eine viel bessere Möglichkeit: Ändern Sie die Einschränkung, die die Höhe bestimmt. Berechnen Sie einfach die Höhe Ihres Tabelleninhalts, suchen Sie die Einschränkung und ändern Sie sie. Hier ein Beispiel (unter der Annahme, dass die Einschränkung, die die Höhe Ihrer Tabelle bestimmt, tatsächlich eine Höhenbeschränkung mit der Beziehung "Gleich" ist):

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

Sie können dies verbessern. Fügen Sie Ihrer Tabellenansicht eine zusätzliche Einschränkung hinzu, z. B. height <= 10000. Verwenden Sie das Ziehen per Strg aus dem Interface Builder in Ihre .h-Datei, um diese Einschränkung zu einer Eigenschaft Ihres Ansichtscontrollers zu machen. Dann können Sie das Durchlaufen von Einschränkungen und Suchen vermeiden. Einfach direkt einstellenmyTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

Dies funktioniert bei Verwendung des automatischen Layouts mit einer Tabellenansicht mit nur einem Abschnitt.

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
    }

Ich rufe diese Funktion beim Einrichten des automatischen Layouts auf (Das Beispiel hier verwendet SnapKit, aber Sie haben die Idee):

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

Ich möchte, dass UITableView nur so hoch ist wie die kombinierte Höhe der Zellen. Ich durchlaufe die Zellen und sammle die Gesamthöhe der Zellen. Da die Größe der Tabellenansicht zu diesem Zeitpunkt CGRect.zero ist, muss ich die Grenzen festlegen, um die von der Zelle definierten Regeln für das automatische Layout einhalten zu können. Ich habe die Größe auf einen beliebigen Wert eingestellt, der groß genug sein sollte. Die tatsächliche Größe wird später vom Auto-Layout-System berechnet.


1

Ich habe es auf eine etwas andere Art gemacht. Eigentlich war meine TableView in der Bildlaufansicht, also musste ich die Höhenbeschränkung als 0 angeben.

Dann habe ich zur Laufzeit folgende Änderungen vorgenommen:

       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()
          }
       }

0

Als Erweiterung der Antwort von Anooj VM empfehle ich Folgendes, um die Inhaltsgröße nur zu aktualisieren, wenn sie sich ändert.

Dieser Ansatz deaktiviert auch das Scrollen ordnungsgemäß und unterstützt größere Listen und Rotationen . Dispatch_async ist nicht erforderlich, da Änderungen an contentSize im Hauptthread ausgelöst werden.

- (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

objc Version von 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

Sie können diese benutzerdefinierte ausprobieren AGTableView

So legen Sie eine TableView-Höhenbeschränkung mithilfe des Storyboards oder programmgesteuert fest (Diese Klasse ruft automatisch eine Höhenbeschränkung ab und setzt die Höhe der Inhaltsansicht auf die Höhe Ihrer Tabellenansicht.)

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

Hinweis Wenn es Probleme beim Einstellen einer Höhe gibt, dann yourTableView.layoutSubviews().


0

Basierend auf der Antwort von fl034 . Aber für Xamarin.iOS- Benutzer:

    [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

Meine Swift 5-Implementierung besteht darin, die Höhenbeschränkung der tableView auf die Größe ihres Inhalts ( contentSize.height) festzulegen . Diese Methode setzt voraus, dass Sie das automatische Layout verwenden. Dieser Code sollte in die cellForRowAttableView-Methode eingefügt werden.

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true

-1

Wenn Ihre Tabelle dynamisch sein soll, müssen Sie eine Lösung verwenden, die auf dem oben beschriebenen Tabelleninhalt basiert. Wenn Sie einfach eine kleinere Tabelle anzeigen möchten, können Sie eine Containeransicht verwenden und einen UITableViewController darin einbetten. Die Größe der UITableView wird entsprechend der Containergröße geändert.

Dies vermeidet viele Berechnungen und Aufrufe des Layouts.


1
Verwenden Sie kein Wort, aboveda die Antworten gemischt werden könnten.
Nik Kov

-3

Mu Lösung dafür in schnell 3 : Rufen Sie diese Methode inviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.