Змінення розміру UITableView відповідно до вмісту


170

Я створюю додаток, який матиме запитання у відповідях UILabelіз декількома варіантами, у UITableViewкожному рядку із декількома варіантами. Питання та відповіді будуть різними, тому мені потрібно, UITableViewщоб це було динамічним у зрості.

Я хотів би знайти sizeToFitроботу за столом. Там, де рамка таблиці встановлена ​​на висоту всього вмісту.

Хтось може порадити, як я можу цього досягти?


Для всіх, хто хоче зробити подібну справу на OSX, дивіться це запитання: stackoverflow.com/questions/11322139/…
Michael Teper

1
@MiMo привіт, чи можете ви пояснити, що не так з підходом "sizeToFit"? :)
iamdavidlam

"Я хотів би знайти" sizeToFit "навколо" . Так ви чи не спробували - sizeToFitметод UIView ?
Сліпп Д. Томпсон

Відповіді:


155

Насправді я сама знайшла відповідь.

Я просто створити новий CGRectдля tableView.frameз heightзtable.contentSize.height

Це встановлює висоту UITableViewдо heightвмісту. Оскільки код модифікує інтерфейс користувача, не забудьте запустити його в основний потік:

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
З цим методом можна зіткнутися з проблемами. Якщо у вас великий список, можливо, висота кадру обріже частину ваших комірок. Ви можете бачити це, якщо увімкнено підстрибування і прокрутіть донизу, щоб побачити, як деякі клітини відскакують.
whyoz

Де саме ви вказали висоту? Ви закликали перезавантажити дані та змінити розмір після них?
Джеймс

27
Де найкраще розмістити цей код? Я спробував це в viewDidLoad, і він, ймовірно, не спрацював, оскільки методи делегата tableview ще не запустилися. Який делегатний метод найкращий?
Кайл Клегг

4
Я зробив це viewDidAppear, і, схоже, спрацювало. Зауважте, що перегляд таблиці може бути трохи вище, ніж його вміст, оскільки він залишає трохи місця для заголовка та колонтитулу розділу
Jameo

2
Я виявив, що viewDidAppear - це чудове місце для розміщення речей, які ви хочете, після того, як всі початкові виклики tableView cellForRowAtIndexPath завершилися
rigdonmr

120

Рішення Swift 5 і 4.2 без KVO, DispatchQueue або встановлення обмежень самостійно.

Це рішення засноване на відповіді Гульца .

1) Створіть підклас 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) Додайте UITableViewдо свого макета і встановіть обмеження з усіх боків. Встановіть його клас на ContentSizedTableView.

3) Ви повинні побачити деякі помилки, тому що в Storyboard не враховується наш підклас ' intrinsicContentSize. Виправте це, відкривши інспектор розміру і перевівши IntrinsicContentSize на значення заповнення. Це перевизначення часу на розробку. Під час виконання він буде використовувати перевагу в нашому ContentSizedTableViewкласі


Оновлення: Змінений код для Swift 4.2. Якщо ви використовуєте попередню версію, використовуйте UIViewNoIntrinsicMetricзамістьUIView.noIntrinsicMetric


1
Дуже гарна відповідь, це спрацювало для мене, дякую. Однак одне - у моєму випадку мені потрібно було змінити клас того, tableViewщоб бути IntrinsicTableViewу розгорнутій дошці. Мені не потрібно було вбудовувати подання таблиці в інший UIView.
dvp.petrov

Так, ти маєш рацію. Я оновив свою відповідь. Крок 2 можна прочитати так, як ви сказали. Звичайно , я мав в виду , щоб встановити tableViewв IntrinsicTableView. Також додаткове обгортання UIViewне потрібно. Ви, звичайно, можете використовувати будь-який існуючий вигляд батьків :)
fl034,

1
Swift 4 Це спрацювало для мене просто чудовоtableView.sizeToFit()
Souf ROCHDI

Так, це може бути хорошим рішенням, якщо ваш вміст статичний. З моїм підходом ви можете використовувати Auto-Layout, щоб вбудовувати таблицю ViewView з динамічним вмістом в інші види та постійно підтримувати його в повний зріст, не замислюючись про те, які місця sizeToFit()потрібно викликати
fl034

2
Чудовий Дякую! Нарешті збігайте батьківський вміст і обгортайте вміст для перегляду таблиці ...
Amber K

79

Швидке рішення

Виконайте такі дії:

1- Встановіть обмеження по висоті для таблиці з розкадрівки.

2- Перетягніть обмеження по висоті з архівної таблиці та створіть для неї @IBOutlet у файлі контролера перегляду.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Тоді ви можете змінити висоту для таблиці динамічно, використовуючи цей код:

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

Оновлення

Якщо для вас скорочений останній рядок, спробуйте зателефонувати viewWillLayoutSubviews () у функції комірки willDisplay:

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

1
Працювали для мене !! thnx
Пушпа Y

5
Це рішення завжди відсікало для мене останній рядок у Xcode 8.3.
Jen C

@Jec C: Не для мене
KMC

Працювали і для мене !!
Антоній Рафель

1
Мені довелося помістити це у метод cellForRowAtIndexPath:. Якщо я поміщую його у метод viewWillLayoutSubviews, то обчислювана висота contentSize ґрунтується на оціночній висоті, а не на фактичній висоті вмісту.
Ману Матеос

21

Я спробував це в iOS 7, і він працював на мене

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

3
Якщо ваш UITableView динамічний (це означає, що інформаційне завантаження в нього та з нього на ходу), вам потрібно буде розмістити це в іншому місці, а не в viewDidLoad. Для мене я помістив його у cellForRowAtIndexPath. Крім того, довелося змінити "tableView" на ім'я мого властивості IBOutlet, щоб уникнути старої помилки "властивості tableView not found".
jeremytripp

thx, виникли проблеми із зміною розміру столового перегляду всередині popover, він спрацював
Запорожченко Олександр

19

Додайте спостерігача за властивістю contentSize у подання таблиці та відповідно регулюйте розмір кадру

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

потім у зворотному дзвінку:

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

Сподіваюся, що це вам допоможе.


2
Вам слід додати ще одну річ. Вам слід видалити спостерігача за поданням таблиці. Тому що він вийде з ладу, якщо клітинка буде розташована.
Mahesh Agrawal

12

У мене було подання таблиці у вікні прокрутки, і довелося обчислити висоту tableView та змінити її відповідно. Це кроки, які я зробив:

0) додайте UIView до свого scrollView (ймовірно, він буде працювати без цього кроку, але я зробив це, щоб уникнути можливих конфліктів) - це буде перегляд контейнера для подання таблиці. Якщо ви зробите цей крок, то встановіть межі поглядів праворуч на представлення таблиці.

1) створити підклас 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) встановити клас подання таблиці в Storyboard to IntrinsicTableView: скріншот: http://joxi.ru/a2XEENpsyBWq0A

3) Встановіть обмеження висоти для подання таблиці

4) перетягніть IBoutlet зі свого столу на свій ViewController

5) перетягніть IBoutlet обмеження висоти таблиці у ваш ViewController

6) додайте цей метод у свій ViewController:

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

Сподіваюся, це допомагає


Дивіться мою відповідь, яка ґрунтується на вашому підкласі, але не потребує жодних налаштувань обмежень по висоті.
fl034

@ fl034 надати посилання?
Gulz

@ fl034 Я не думаю, що ви не зможете уникнути використання обмежень, коли ваші елементи вбудовані у вигляд прокрутки) Моя справа з ScrollView
Gulz

У мене також є перегляд таблиці у вікні прокрутки, і він прекрасно працює :)
fl034,

працює для мене на iOS 13 / Xcode 11 Ви закінчили три дні страждань доброго сер, дякую
Мехді С.

8

Якщо ви не хочете самостійно відстежувати розмір вмісту подання таблиці, ви можете вважати цей підклас корисним.

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
Для швидкого 3, intrinsicContentSize став вар, якoverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod

не працює для мене. все ще ховається внизу таблиці
Гульц


4

Swift 3, iOS 10.3

Рішення 1: Просто помістітьself.tableview.sizeToFit()вcellForRowAt indexPathфункції. Не забудьте встановити висоту перегляду таблиці вище, ніж вам потрібно. Це хороше рішення, якщо у вас немає переглядів нижче таблиці. Однак, якщо у вас є, обмеження щодо перегляду нижньої таблиці не буде оновлено (я не намагався це виправити, оскільки я придумав рішення 2)

Приклад:

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

Рішення 2: Встановіть обмеження висоти перегляду таблиць у табло та перетягніть його на ViewController. Якщо ви знаєте середню висоту своєї комірки і знаєте, скільки елементів містить ваш масив, ви можете зробити щось подібне:

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

* Редагувати:

Після всіх рішень, які я спробував, і кожне з них щось виправляло, але не повністю, це відповідь, яка пояснює та виправляє цю проблему повністю.


Це дуже просте рішення, воно спрацювало для мене, Дякую @Dorde Nilovic
Р. Мохан

1

Відповідь Мімо та відповідь Anooj VM є приголомшливими, але існує невелика проблема, якщо у вас є великий список, можливо, висота кадру обріже частину ваших комірок.

Так. Відповідь я трохи змінив:

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

Є набагато кращий спосіб зробити це, якщо ви використовуєте AutoLayout: змінити обмеження, яке визначає висоту. Просто обчисліть висоту вмісту вашої таблиці, а потім знайдіть обмеження та змініть його. Ось приклад (якщо припустити, що обмеження, яке визначає висоту вашої таблиці, насправді є обмеженням по висоті у відношенні "Рівне"):

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

Ви можете покращити це. Додайте додаткове обмеження для перегляду таблиці, скажімо, висота <= 10000. Використовуйте перетягування елементів керування з Інтерфейсу Builder до .h-файлу, щоб зробити це обмеження властивістю контролера перегляду. Тоді ви можете уникнути ітерації через обмеження та пошук. Просто встановленоmyTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

Це працює для мене, використовуючи функцію автоматичного макета, з поданням таблиці лише з одним розділом.

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
    }

Я називаю цю функцію при налаштуванні автоматичного макета (у прикладі тут використовується SnapKit, але ви розумієте):

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

Я хочу, щоб UITableView був таким же високим, як і комбінована висота комірок; Я проводжу через клітинки і накопичую загальну висоту комірок. Оскільки розмір подання таблиці є CGRect.zero на даний момент, мені потрібно встановити межі, щоб мати змогу дотримуватись правил автоматичної розмітки, визначених коміркою. Я встановлюю розмір на довільне значення, яке повинно бути досить великим. Фактичний розмір буде обчислений пізніше системою автоматичного макета.


1

Я робив дещо по-іншому, насправді мій TableView був всередині scrollview, тому мені довелося задавати обмеження висоти як 0.

Тоді під час виконання я вніс наступні зміни,

       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

Як розширення відповіді Anooj VM, я пропоную наступне, щоб оновити розмір вмісту лише тоді, коли він змінюється.

Цей підхід також відключає прокрутку належним чином та підтримує більші списки та обертання . Немає необхідності в dispatch_async, оскільки зміни змісту розміру надсилаються в основний потік.

- (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 версія 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

Ви можете спробувати цей звичай AGTableView

Встановлення обмеження висоти TableView, використовуючи розкадровку або програмно. (Цей клас автоматично отримує обмеження по висоті і встановлює висоту перегляду вмісту на висоту перегляду вашого столу).

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

Примітка Якщо будь-яка проблема встановити висоту, тоді yourTableView.layoutSubviews().


0

На основі відповіді fl034 . Але для користувачів 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

Моя реалізація Swift 5 полягає у встановленні обмеження висоти tableView на розмір її вмісту ( contentSize.height). Цей метод передбачає використання автоматичного макета. Цей код слід розмістити всередині cellForRowAtметоду tableView.

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

-1

Якщо ви хочете, щоб ваша таблиця була динамічною, вам потрібно буде використовувати рішення на основі вмісту таблиці, як описано вище. Якщо ви просто хочете відобразити меншу таблицю, ви можете скористатися поданням контейнера та вставити в нього UITableViewController - розмір UITableView буде змінено відповідно до розміру контейнера.

Це дозволяє уникнути безлічі розрахунків і закликів до компонування.


1
Не вживайте слова, aboveоскільки відповіді можна перетасувати.
Нік Ков

-3

Му розв’язання для цього в швидкому 3 : Зателефонуйте в цей методviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.