Ridimensionamento di UITableView per adattarlo al contenuto


170

Sto creando un'app che avrà una domanda in una UILabele una risposta a risposta multipla visualizzata in UITableView, ogni riga che mostra una scelta multipla. Le domande e le risposte possono variare, quindi ho bisogno che questo UITableViewsia dinamico in altezza.

Vorrei trovare un sizeToFitlavoro per il tavolo. Dove la cornice del tavolo è impostata all'altezza di tutto il suo contenuto.

Qualcuno può consigliare su come posso raggiungere questo obiettivo?


Per chiunque cerchi di fare una cosa simile su OSX, vedi questa domanda: stackoverflow.com/questions/11322139/…
Michael Teper,

1
@MiMo ciao, puoi spiegare cosa c'è che non va nell'approccio "sizeToFit" per favore? :)
iamdavidlam,

"Vorrei trovare un" sizeToFit "aggirare" . Quindi hai provato o non hai provato il - sizeToFitmetodo UIView ?
Slipp D. Thompson,

Risposte:


155

In realtà ho trovato la risposta da solo.

Ho appena creato un nuovo CGRectper il tableView.framecon il heightditable.contentSize.height

Ciò imposta l'altezza del UITableViewal heightsuo contenuto. Poiché il codice modifica l'interfaccia utente, non dimenticare di eseguirlo nel thread principale:

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
È possibile riscontrare problemi con questo metodo. Se hai un grande elenco, è possibile che l'altezza della cornice taglierà alcune delle tue celle. Puoi vederlo accadere se hai attivato il rimbalzo e scorri verso il basso per vedere alcune celle rimbalzare fuori dalla vista.
whyoz,

Dove hai specificato esattamente l'altezza? Hai effettuato una chiamata per ricaricare i dati e ridimensionarli dopo le parole?
James,

27
Dov'è il posto migliore per inserire questo codice? L'ho provato in viewDidLoad e non ha funzionato presumibilmente perché i metodi delegati di tableview non erano ancora stati attivati. Quale metodo delegato è il migliore?
Kyle Clegg,

4
Ho fatto questo è viewDidAppear, e sembra aver funzionato. Nota che la vista da tavolo potrebbe essere un po 'più alta del suo contenuto perché lascia un po' di spazio per un'intestazione e un piè di pagina
Jameo,

2
Ho scoperto che viewDidAppear è un ottimo posto per mettere cose che vuoi che accadano dopo che tutte le chiamate iniziali tableView cellForRowAtIndexPath sono terminate
rigdonmr

120

Soluzione Swift 5 e 4.2 senza KVO, DispatchQueue o impostazione dei vincoli.

Questa soluzione si basa sulla risposta di Gulz .

1) Creare una sottoclasse di 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) Aggiungi a UITableViewal layout e imposta i vincoli su tutti i lati. Impostare la classe su ContentSizedTableView.

3) Dovresti vedere degli errori, perché Storyboard non tiene conto della nostra sottoclasse ' intrinsicContentSize. Risolvilo aprendo la finestra di ispezione delle dimensioni e sovrascrivendo intrinsicContentSize su un valore segnaposto. Questa è una sostituzione per i tempi di progettazione. In fase di esecuzione utilizzerà l'override nella nostra ContentSizedTableViewclasse


Aggiornamento: codice modificato per Swift 4.2. Se stai usando una versione precedente, usa UIViewNoIntrinsicMetricinvece diUIView.noIntrinsicMetric


1
Ottima risposta, ha funzionato per me, grazie. Una cosa però - nel mio caso avevo bisogno di cambiare la classe di tableViewessere IntrinsicTableViewnello storyboard. Non avevo bisogno di incorporare la vista della mia tabella in un'altra UIView.
dvp.petrov,

Si hai ragione. Ho aggiornato la mia risposta. Il passaggio 2 può essere letto come hai detto. Naturalmente ho intenzione di impostare l' tableViewa IntrinsicTableView. Inoltre UIViewnon è necessario un involucro aggiuntivo . Ovviamente puoi usare qualsiasi vista genitore esistente :)
fl034,

1
Swift 4 Questo ha funzionato bene per metableView.sizeToFit()
Souf ROCHDI,

Sì, potrebbe essere una buona soluzione, se i tuoi contenuti sono statici. Con il mio approccio è possibile utilizzare Auto-Layout per incorporare un tableView con contenuto dinamico in altre viste e mantenerlo sempre a tutta altezza, senza pensare a quali luoghi sizeToFit()devono essere chiamati
fl034,

2
Meraviglioso grazie!. Infine abbina il contenuto principale e avvolgi il contenuto per la visualizzazione tabella ...
Amber K

79

Soluzione rapida

Segui questi passi:

1- Imposta il vincolo di altezza per la tabella dallo storyboard.

2- Trascinare il vincolo di altezza dallo storyboard e creare @IBOutlet per esso nel file del controller di visualizzazione.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Quindi puoi modificare l'altezza per la tabella in modo dinamico usando questo codice:

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

Aggiornare

Se l'ultima riga viene tagliata per te, prova a chiamare viewWillLayoutSubviews () nella funzione della cella willDisplay:

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

1
Ha funzionato per me !! thnx
Pushpa Y

5
Questa soluzione ha sempre interrotto l'ultima riga su Xcode 8.3 per me.
Jen C,

@Jec C: Neanche per me
KMC

Ha funzionato anche per me !!
Antony Raphel,

1
Ho dovuto inserire questo nel metodo cellForRowAtIndexPath. Se lo inserisco nel metodo viewWillLayoutSubviews, l'altezza della dimensione del contenuto calcolata si basa sull'altezza stimata, non sull'altezza effettiva del contenuto.
Manu Mateos,

21

L'ho provato su iOS 7 e ha funzionato per me

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

3
Se UITableView è dinamico (nel senso che le informazioni vengono caricate da e verso di esso al volo), dovrai metterlo altrove, non in viewDidLoad. Per me, l'ho messo in cellForRowAtIndexPath. Inoltre ho dovuto cambiare "tableView" con il nome della mia proprietà IBOutlet per evitare il vecchio errore "proprietà tableView non trovata".
jeremytripp,

grazie, ha avuto problemi con il ridimensionamento della vista tabella all'interno di popover, ha funzionato
Zaporozhchenko Oleksandr

19

Aggiungi un osservatore per la proprietà contentSize nella vista tabella e regola le dimensioni della cornice di conseguenza

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

quindi nel callback:

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

Spero che questo ti possa aiutare.


2
Dovresti aggiungere un'altra cosa. È necessario rimuovere l'osservatore per la vista tabella. Perché si arresta in modo anomalo se la cella viene allocata.
Mahesh Agrawal,

12

Avevo una vista tabella all'interno della vista scorrimento e dovevo calcolare l'altezza di tableView e ridimensionarla di conseguenza. Questi sono i passi che ho preso:

0) aggiungi una UIView a scrollView (probabilmente funzionerà senza questo passaggio ma l'ho fatto per evitare possibili conflitti) - questa sarà una vista contenitiva per la vista della tabella. Se fai questo passaggio, imposta i bordi delle viste su quelli della vista tabella.

1) creare una sottoclasse di 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) imposta la classe di una vista tabella nello Storyboard su IntrinsicTableView: screenshot: http://joxi.ru/a2XEENpsyBWq0A

3) Imposta l'altezzaConstraint sulla vista della tabella

4) trascina l'IBoutlet della tabella sul ViewController

5) trascina l'IBoutlet del vincolo di altezza del tuo tavolo sul ViewController

6) aggiungi questo metodo nel tuo ViewController:

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

Spero che questo ti aiuti


Vedi la mia risposta che si basa sulla tua sottoclasse ma che non richiede alcuna impostazione di vincoli di altezza.
fl034,

@ fl034 fornire link?
Gulz,

@ fl034 Non credo che si può evitare di utilizzare i vincoli quando gli elementi sono incorporati nella Scroll View) Il mio caso è con lo ScrollView
Gulz

Ho anche la mia vista tabella all'interno di una vista di scorrimento e funziona perfettamente :)
fl034

funziona per me su iOS 13 / Xcode 11 Hai messo fine a 3 giorni di sofferenza gentile signore, grazie
Mehdi S.

8

Nel caso in cui non si desideri tenere traccia delle modifiche alle dimensioni del contenuto della vista tabella, è possibile che questa sottoclasse sia 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
    }
}

1
Per swift 3, intrinsicContentSize è diventato un var, comeoverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod

non funziona per me. ancora nascosto in fondo al tavolo
Gulz,


4

Swift 3, iOS 10.3

Soluzione 1: Basta mettereself.tableview.sizeToFit()incellForRowAt indexPath funzione. Assicurati di impostare l'altezza della vista del tavolo più alta del necessario. Questa è una buona soluzione se non hai viste sotto tableview. Tuttavia, se lo hai, il vincolo di tableview inferiore non verrà aggiornato (non ho provato a risolverlo perché ho trovato la soluzione 2)

Esempio:

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

Soluzione 2: imposta il vincolo dell'altezza della vista tabella nello storyboard e trascinalo sul ViewController. Se conosci l'altezza media della tua cella e sai quanti elementi contiene il tuo array, puoi fare qualcosa del genere:

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

*MODIFICARE:

Dopo tutte le soluzioni che ho provato e ognuna di esse stava risolvendo qualcosa, ma non completamente, questa è la risposta che spiega e risolve completamente questo problema.


Questa è una soluzione molto semplice, ha funzionato per me, grazie @Dorde Nilovic
R. Mohan,

1

La risposta di mimo e risposta Anooj VM s' entrambi sono impressionanti, ma c'è un piccolo problema se si dispone di un elenco di grandi dimensioni, è possibile che l'altezza del telaio sarà cutoff alcune delle vostre cellule.

Così. Ho modificato un po 'la risposta:

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

Esiste un modo molto migliore per farlo se si utilizza AutoLayout: modificare il vincolo che determina l'altezza. Basta calcolare l'altezza del contenuto della tabella, quindi trovare il vincolo e modificarlo. Ecco un esempio (supponendo che il vincolo che determina l'altezza della tabella sia in realtà un vincolo di altezza con relazione "Uguale"):

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

Puoi migliorare su questo. Aggiungi un ulteriore vincolo alla vista tabella, ad esempio, altezza <= 10000. Usa il controllo-trascinamento da Interface Builder nel tuo file .h per rendere questo vincolo una proprietà del tuo controller di visualizzazione. Quindi è possibile evitare l'iterazione attraverso vincoli e ricerche. Appena impostato direttamentemyTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

Questo funziona per me usando il layout automatico, con una vista tabella con una sola sezione.

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
    }

Questa funzione viene chiamata durante l'impostazione del layout automatico (l'esempio qui utilizza SnapKit, ma si ottiene l'idea):

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

Voglio che UITableView sia alto solo quanto l'altezza combinata delle celle; Ciclo attraverso le celle e accumulo l'altezza totale delle celle. Poiché la dimensione della vista tabella è CGRect.zero a questo punto, devo impostare i limiti per poter rispettare le regole di layout automatico definite dalla cella. Ho impostato la dimensione su un valore arbitrario che dovrebbe essere abbastanza grande. Le dimensioni effettive verranno calcolate in seguito dal sistema di layout automatico.


1

L'ho fatto in un modo un po 'diverso, in realtà il mio TableView era all'interno di scrollview quindi ho dovuto dare un vincolo di altezza come 0.

Quindi in fase di esecuzione ho apportato le seguenti modifiche,

       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

Come estensione della risposta di Anooj VM, suggerisco quanto segue per aggiornare le dimensioni del contenuto solo quando cambia.

Questo approccio disabilita anche lo scorrimento corretto e supporta elenchi e rotazioni più grandi . Non è necessario dispatch_async perché le modifiche di contentSize vengono inviate sul thread principale.

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

versione objc di 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

Puoi provare questa abitudine AGTableView

Per impostare un vincolo di altezza TableView utilizzando lo storyboard o a livello di codice. (Questa classe recupera automaticamente un vincolo di altezza e imposta l'altezza della vista del contenuto sull'altezza della vista della piattaforma).

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

Nota In caso di problemi per impostare un'altezza, allora yourTableView.layoutSubviews().


0

Basato sulla risposta di fl034 . Ma per gli utenti 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

La mia implementazione di Swift 5 consiste nell'impostare il vincolo alto di tableView sulla dimensione del suo contenuto ( contentSize.height). Questo metodo presuppone che tu stia utilizzando il layout automatico. Questo codice deve essere inserito nel cellForRowAtmetodo tableView.

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

-1

Se vuoi che la tua tabella sia dinamica, dovrai usare una soluzione basata sul contenuto della tabella come descritto sopra. Se si desidera semplicemente visualizzare una tabella più piccola, è possibile utilizzare una vista contenitore e incorporare un UITableViewController in esso: UITableView verrà ridimensionato in base alle dimensioni del contenitore.

Questo evita molti calcoli e chiamate al layout.


1
Non usare la parola aboveperché le risposte potrebbero essere mescolate.
Nik Kov,

-3

Soluzione Mu per questo in rapido 3 : chiama questo metodo inviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.