Zmiana rozmiaru UITableView w celu dopasowania do zawartości

170

Tworzę aplikację, w której będzie wyświetlane pytanie UILabeli odpowiedzi wielokrotnego wyboru, w UITableViewkażdym wierszu będzie wyświetlana opcja wielokrotnego wyboru. Pytania i odpowiedzi będą się różnić, więc potrzebuję UITableViewdynamicznej wysokości.

Chciałbym znaleźć rozwiązanie sizeToFitdla stołu. Gdzie rama tabeli jest ustawiona na wysokość całej jej zawartości.

Czy ktoś może doradzić, jak mogę to osiągnąć?

MiMo
źródło
Dla każdego, kto chce zrobić coś podobnego w OSX, zobacz to pytanie: stackoverflow.com/questions/11322139/ ...
Michael Teper
1
@MiMo cześć, czy możesz wyjaśnić, co jest nie tak z podejściem „sizeToFit”? :)
iamdavidlam
„Chciałbym znaleźć obejście„ sizeToFit ” . Więc wypróbowałeś - sizeToFitmetodę UIView ?
Slipp D. Thompson

Odpowiedzi:

155

Właściwie sam znalazłem odpowiedź.

Ja po prostu stworzyć nową CGRectdla tableView.framez heightztable.contentSize.height

To ustawia wysokość UITableViewdo heightjego zawartości. Ponieważ kod modyfikuje interfejs użytkownika, nie zapomnij uruchomić go w głównym wątku:

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
źródło
3
Przy tej metodzie można napotkać problemy. Jeśli masz dużą listę, możliwe, że wysokość ramki spowoduje odcięcie niektórych komórek. Możesz to zobaczyć, jeśli masz włączone odbijanie i przewiń w dół, aby zobaczyć, jak niektóre komórki znikają z widoku.
whyoz
Gdzie dokładnie określiłeś wysokość? Czy wykonałeś wywołanie, aby ponownie załadować dane i zmienić ich rozmiar po słowie?
James
27
Gdzie najlepiej umieścić ten kod? Wypróbowałem to w viewDidLoad i prawdopodobnie nie zadziałało, ponieważ metody delegata widoku tabeli jeszcze się nie uruchomiły. Która metoda delegata jest najlepsza?
Kyle Clegg
4
Zrobiłem to jako viewDidAppear i wydaje się, że zadziałało. Zwróć uwagę, że widok tabeli może być nieco wyższy niż jego zawartość, ponieważ pozostawia trochę miejsca na nagłówek i stopkę sekcji
Jameo,
2
Odkryłem, że viewDidAppear to świetne miejsce do umieszczania rzeczy, które chcesz wydarzyć po zakończeniu wszystkich początkowych wywołań tableView cellForRowAtIndexPath
rigdonmr
120

Rozwiązanie Swift 5 i 4.2 bez KVO, DispatchQueue lub samodzielnego ustawiania ograniczeń.

To rozwiązanie jest oparte na odpowiedzi Gulza .

1) Utwórz podklasę 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) Dodaj a UITableViewdo swojego układu i ustaw ograniczenia ze wszystkich stron. Ustaw jego klasę naContentSizedTableView .

3) Powinieneś zobaczyć błędy, ponieważ Storyboard nie bierze intrinsicContentSizepod uwagę naszej podklasy . Napraw ten problem, otwierając inspektora rozmiaru i zastępując intrinsicContentSize wartością zastępczą. To jest nadpisanie czasu projektowania. W czasie wykonywania użyje on override w naszej ContentSizedTableViewklasie


Aktualizacja: zmieniony kod dla Swift 4.2. Jeśli używasz wcześniejszej wersji, użyj UIViewNoIntrinsicMetriczamiastUIView.noIntrinsicMetric

fl034
źródło
1
Bardzo dobra odpowiedź, mi się udało, dzięki. Jednak jedno - w moim przypadku musiałem zmienić klasę, tableViewaby znaleźć się IntrinsicTableVieww storyboardzie. Nie musiałem osadzać widoku tabeli w innym UIView.
dvp.petrov
Tak, masz rację. Zaktualizowałem odpowiedź. Krok 2 można przeczytać tak, jak powiedziałeś. Oczywiście mam na myśli, aby ustawić tableViewsię IntrinsicTableView. Nie jest też konieczne dodatkowe opakowanie UIView. Możesz oczywiście użyć dowolnego istniejącego widoku rodzica :)
fl034
1
Swift 4 To zadziałało dla mnie dobrzetableView.sizeToFit()
Souf ROCHDI
Tak, to może być dobre rozwiązanie, jeśli treść jest statyczna. Z moim podejściem możesz użyć Auto-Layout, aby osadzić tableView z dynamiczną zawartością w innych widokach i utrzymywać go w pełnej wysokości przez cały czas, bez zastanawiania się, w którym miejscu sizeToFit()należy wywołać
fl034
2
Wspaniale, dziękuję!. Na koniec dopasuj rodzica i zawiń zawartość do widoku tabeli ...
Amber K
79

Szybkie rozwiązanie

Wykonaj następujące kroki:

1- Ustaw ograniczenie wysokości stołu z serii ujęć.

2- Przeciągnij ograniczenie wysokości z serii ujęć i utwórz dla niego @IBOutlet w pliku kontrolera widoku.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Następnie możesz dynamicznie zmienić wysokość tabeli za pomocą tego kodu:

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

Aktualizacja

Jeśli ostatni wiersz jest dla Ciebie wycięty, spróbuj wywołać metodę viewWillLayoutSubviews () w funkcji komórki willDisplay:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}
Musa almatri
źródło
1
Pracował dla mnie !! thnx
Pushpa Y
5
To rozwiązanie zawsze odcina ostatni wiersz w Xcode 8.3.
Jen C
@Jec C: Dla mnie też nie
KMC
U mnie też zadziałało !!
Antony Raphel
1
Musiałem to umieścić w metodzie cellForRowAtIndexPath:. Jeśli wstawię to do metody viewWillLayoutSubviews, obliczona wysokość contentSize jest oparta na szacowanej wysokości, a nie na rzeczywistej wysokości treści.
Manu Mateos
21

Wypróbowałem to w iOS 7 i zadziałało

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
Alexey
źródło
3
Jeśli Twój UITableView jest dynamiczny (co oznacza, że ​​informacje są ładowane do iz niego w locie), musisz umieścić to w innym miejscu, a nie w viewDidLoad. Dla mnie umieściłem to w cellForRowAtIndexPath. Musiałem także zmienić „tableView” na nazwę mojej właściwości IBOutlet, aby uniknąć starego błędu „property tableView not found”.
jeremytripp
thx, miał problem ze zmianą rozmiaru tabeli w popover, zadziałało
Zaporożczenko Oleksandr
19

Dodaj obserwatora dla właściwości contentSize w widoku tabeli i odpowiednio dostosuj rozmiar ramki

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

następnie w oddzwonieniu:

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

Mam nadzieję, że to ci pomoże.

Anooj VM
źródło
2
Powinieneś dodać jeszcze jedną rzecz. Powinieneś usunąć obserwatora dla widoku tabeli. Ponieważ ulegnie awarii, jeśli komórka zostanie zwolniona.
Mahesh Agrawal
12

Miałem widok tabeli w widoku przewijania i musiałem obliczyć wysokość tableView i odpowiednio zmienić jego rozmiar. Oto kroki, które podjąłem:

0) dodaj UIView do swojego scrollView (prawdopodobnie będzie działać bez tego kroku, ale zrobiłem to, aby uniknąć ewentualnych konfliktów) - będzie to widok kontenera dla twojego widoku tabeli. Jeśli zrobisz ten krok, ustaw granice widoków bezpośrednio na widoki tabeli.

1) utwórz podklasę 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) ustaw klasę widoku tabeli w Storyboard na IntrinsicTableView: zrzut ekranu: http://joxi.ru/a2XEENpsyBWq0A

3) Ustaw heightConstraint na swój widok tabeli

4) przeciągnij IBoutlet swojej tabeli do kontrolera ViewController

5) przeciągnij IBoutlet ograniczenia wysokości tabeli do swojego ViewController

6) Dodaj tę metodę do swojego ViewController:

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

Mam nadzieję że to pomoże

Gulz
źródło
Zobacz moją odpowiedź, która jest oparta na twojej podklasie, ale nie wymaga ustawienia ograniczeń wysokości.
fl034,
@ fl034 podać link?
Gulz,
@ fl034 Nie sądzę, aby można było uniknąć stosowania ograniczeń, gdy elementy są osadzone w widoku przewijania). Mój przypadek dotyczy
ScrollView
Mam też widok tabeli w widoku przewijania i działa idealnie :)
fl034,
działa dla mnie na iOS 13 / Xcode 11 Położyłeś kres 3 dniom cierpienia miłego pana, dziękuję
Mehdi S.
8

Jeśli nie chcesz samodzielnie śledzić zmian rozmiaru zawartości widoku tabeli, ta podklasa może być przydatna.

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
źródło
1
Dla Swift 3, intrinsicContentSize stał się override public var intrinsicContentSize: CGSize { //... return someCGSize }
zmienną
nie działa dla mnie. wciąż chowający dół tabeli
Gulz
5

Jeśli twój contentSize jest nieprawidłowy, dzieje się tak, ponieważ jest oparty na szacowanej wysokościRowHeight (automatycznie), użyj tego przed

tableView.estimatedRowHeight = 0;

źródło: https://forums.developer.apple.com/thread/81895

Yohan Dahmani
źródło
4

Swift 3, iOS 10.3

Rozwiązanie 1: Wystarczy umieścićself.tableview.sizeToFit()wcellForRowAt indexPathfunkcji. Upewnij się, że wysokość widoku stołu jest wyższa niż potrzebujesz. To dobre rozwiązanie, jeśli nie masz widoków poniżej widoku tabeli. Jeśli jednak masz, ograniczenie dolnej tabeli widoku nie zostanie zaktualizowane (nie próbowałem tego naprawić, ponieważ wymyśliłem rozwiązanie 2)

Przykład:

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

Rozwiązanie 2: Ustaw ograniczenie wysokości widoku tabeli w scenorysie i przeciągnij go do ViewController. Jeśli znasz średnią wysokość komórki i wiesz, ile elementów zawiera twoja tablica, możesz zrobić coś takiego:

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

*EDYTOWAĆ:

Po wszystkich rozwiązaniach, które wypróbowałem i każdy z nich coś naprawiał, ale nie do końca, to jest odpowiedź, która całkowicie wyjaśnia i rozwiązuje ten problem.

Đorđe Nilović
źródło
To bardzo proste rozwiązanie, zadziałało dla mnie, dziękuję @Dorde Nilovic
R. Mohan
1

Odpowiedź przejazd Mimo użytkownika i odpowiedź Anooj VM „s oba są niesamowite, ale jest mały problem, jeśli masz dużą listę, to możliwe, że wysokość ramy będzie odcięcia niektórych swoich komórek.

Więc. Trochę zmodyfikowałem odpowiedź:

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
źródło
1

Jest o wiele lepszy sposób, aby to zrobić, jeśli używasz AutoLayout: zmień ograniczenie określające wysokość. Po prostu oblicz wysokość zawartości tabeli, a następnie znajdź ograniczenie i zmień je. Oto przykład (zakładając, że ograniczenie określające wysokość stołu jest w rzeczywistości ograniczeniem wysokości z relacją „Równe”):

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
źródło
Możesz to poprawić. Dodaj dodatkowe ograniczenie do widoku tabeli, powiedzmy, wysokość <= 10000. Użyj przeciągania z klawiszem Control z programu Interface Builder do pliku .h, aby uczynić to ograniczenie właściwością kontrolera widoku. Wtedy możesz uniknąć iteracji przez ograniczenia i wyszukiwania. Po prostu ustaw bezpośredniomyTableHeightConstraint.constant = tableView.contentSize.height
RobP
1

Działa to dla mnie przy użyciu automatycznego układu, z widokiem tabeli z tylko jedną sekcją.

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
    }

Nazywam tę funkcję podczas konfigurowania automatycznego układu (przykład tutaj używa SnapKit, ale masz pomysł):

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

Chcę, aby UITableView był tak wysoki, jak łączna wysokość komórek; Przechodzę przez komórki w pętli i zliczam całkowitą wysokość komórek. Ponieważ rozmiar widoku tabeli to CGRect.zero w tym momencie, muszę ustawić granice, aby móc przestrzegać reguł automatycznego układu zdefiniowanych przez komórkę. Ustawiłem rozmiar na dowolną wartość, która powinna być wystarczająco duża. Rzeczywisty rozmiar zostanie obliczony później przez system Auto Layout.

André
źródło
1

Zrobiłem w nieco inny sposób, Właściwie mój TableView był w widoku przewijania, więc musiałem podać ograniczenie wysokości jako 0.

Następnie w czasie wykonywania wprowadziłem następujące zmiany,

       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
źródło
0

Jako rozszerzenie odpowiedzi Anooj VM sugeruję, aby odświeżyć rozmiar zawartości tylko wtedy, gdy się zmieni.

Takie podejście również wyłącza prawidłowe przewijanie i obsługuje większe listy i rotację . Nie ma potrzeby dispatch_async, ponieważ zmiany contentSize są wysyłane w głównym wątku.

- (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
źródło
0

objc wersja 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
źródło
0

Możesz wypróbować ten niestandardowy AGTableView

Aby ustawić ograniczenie wysokości TableView przy użyciu scenorysu lub programowo. (Ta klasa automatycznie pobiera ograniczenie wysokości i ustawia wysokość widoku zawartości na wysokość widoku tabeli).

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

Uwaga Jeśli masz problem z ustawieniem wysokości, to yourTableView.layoutSubviews().

AshvinGudaliya
źródło
0

Na podstawie odpowiedzi fl034 . Ale dla użytkowników 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
źródło
0

Moja implementacja Swift 5 polega na ustawieniu ograniczenia wysokości elementu tableView na rozmiar jego content ( contentSize.height). Ta metoda zakłada, że ​​używasz układu automatycznego. Ten kod należy umieścić wewnątrz cellForRowAtmetody tableView.

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
Dominic Egginton
źródło
-1

Jeśli chcesz, aby tabela była dynamiczna, musisz użyć rozwiązania opartego na zawartości tabeli, jak opisano powyżej. Jeśli chcesz po prostu wyświetlić mniejszą tabelę, możesz użyć widoku kontenera i osadzić w nim UITableViewController - rozmiar UITableView zostanie zmieniony zgodnie z rozmiarem kontenera.

Pozwala to uniknąć wielu obliczeń i wywołań układu.

green_knight
źródło
1
Nie używaj słów, aboveponieważ odpowiedzi można wymieszać.
Nik Kov
-3

Rozwiązanie mu w szybkim 3 : Wywołaj tę metodęviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
brahimm
źródło