Изменение размера UITableView для соответствия содержанию

170

Я создаю приложение, в котором будет UILabelотображаться вопрос в виде ответов с несколькими вариантами ответов UITableView, каждая строка отображает вариант с несколькими вариантами ответов . Вопросы и ответы будут разными, поэтому мне нужно, UITableViewчтобы они были динамичными по высоте.

Я хотел бы найти sizeToFitобходной стол. Где рамка таблицы установлена ​​на высоту всего ее содержимого.

Кто-нибудь может посоветовать, как мне этого добиться?

MiMo
источник
Для тех, кто хочет сделать подобное на OSX, посмотрите этот вопрос: stackoverflow.com/questions/11322139/…
Майкл Тепер
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;
    });
MiMo
источник
3
С этим методом можно столкнуться с проблемами. Если у вас большой список, возможно, высота рамки обрежет некоторые ваши ячейки. Это может произойти, если вы включили подпрыгивание и прокрутите вниз, чтобы увидеть, как некоторые клетки отскакивают от поля зрения.
Почему
Где именно вы указали высоту? Вы сделали вызов reloadData и изменили его размер после слов?
Джеймс
27
Где лучшее место для размещения этого кода? Я попробовал это в viewDidLoad, и он предположительно не работал, потому что методы делегата tableview еще не сработали. Какой метод делегата лучше?
Кайл Клегг
4
Я сделал это viewDidAppear, и, похоже, сработало. Обратите внимание, что табличное представление может быть немного выше, чем его содержимое, потому что оно оставляет немного места для заголовка и
нижнего
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) Вы должны увидеть некоторые ошибки, потому что раскадровка не учитывает наш подкласс intrinsicContentSize. Исправьте это, открыв инспектор размера и переопределив intrinsicContentSize для значения заполнителя. Это переопределение для времени разработки. Во время выполнения он будет использовать переопределение в нашем ContentSizedTableViewклассе


Обновление: изменен код для Swift 4.2. Если вы используете предыдущую версию, используйте UIViewNoIntrinsicMetricвместоUIView.noIntrinsicMetric

fl034
источник
1
Очень хороший ответ, у меня все получилось, спасибо. Одна вещь, хотя - в моем случае мне нужно было изменить класс, tableViewчтобы быть IntrinsicTableViewв раскадровке. Мне не нужно было вставлять мой вид таблицы в другой UIView.
dvp.petrov
Да, ты прав. Я обновил свой ответ. Шаг 2 можно прочитать, как вы сказали. Конечно , я имел в виду , чтобы установить tableViewв IntrinsicTableView. Также дополнительная упаковка UIViewне требуется. Вы можете использовать любое существующее родительское представление, конечно :)
fl034
1
Swift 4 Это сработало для меня очень хорошоtableView.sizeToFit()
Souf ROCHDI
Да, это может быть хорошим решением, если ваш контент статичен. При моем подходе вы можете использовать Auto-Layout для встраивания табличного представления с динамическим содержимым в другие представления и постоянно поддерживать его в полную высоту, не задумываясь о том, какие места sizeToFit()нужно вызывать
fl034
2
Чудесно, спасибо!. Наконец, сопоставьте родительский и оберните содержимое для tableview ...
Янтарь 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
Pushpa Y
5
Это решение всегда обрезало последнюю строку в Xcode 8.3 для меня.
Джен С
@Jec C: Не для меня тоже
KMC
У меня тоже сработало !!
Энтони Рафель
1
Я должен был поместить это в cellForRowAtIndexPath: метод. Если я добавлю его в метод viewWillLayoutSubviews, вычисленная высота contentSize будет основана на предполагаемой высоте, а не на фактической высоте содержимого.
Ману Матеос
21

Я попробовал это в iOS 7, и это сработало для меня

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
Алексей
источник
3
Если ваш UITableView является динамическим (что означает, что информация загружается туда и обратно на лету), вам нужно поместить это в другое место, а не в viewDidLoad. Для меня я положил его в cellForRowAtIndexPath. Также пришлось изменить «tableView» на имя моего свойства IBOutlet, чтобы избежать старой ошибки «property tableView not found».
jeremytripp
спасибо, у меня была проблема с изменением размера таблицы внутри 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
Вы должны добавить еще одну вещь. Вы должны удалить наблюдателя для представления таблицы. Потому что он потерпит крах, если освободить ячейку.
Махеш Агравал
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) установить класс табличного представления в раскадровке на 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
    }

Надеюсь это поможет

Gulz
источник
Посмотрите мой ответ, который основан на вашем подклассе, но не требует установки ограничений по высоте.
fl034
@ fl034 предоставить ссылку?
Гульц
@ fl034 Я не думаю , что вы можете избежать использования ограничений , когда ваши элементы встроены в Scroll View) Мой случай с ScrollView
Gulz
У меня также есть мое табличное представление в представлении прокрутки, и оно отлично работает :)
fl034
работает для меня на iOS 13 / Xcode 11 Вы положили конец 3 дням страдания доброго сэра, спасибо
Мехди С.
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
    }
}
xinatanil
источник
1
Для swift 3 intrinsicContentSize стал override public var intrinsicContentSize: CGSize { //... return someCGSize }
переменным
не работает для меня. все еще прячется за столом
Gulz
5

В случае, если ваш contentSize не является правильным, это потому, что он основан на оценочном RowHeight (автоматически), используйте это до

tableView.estimatedRowHeight = 0;

источник: https://forums.developer.apple.com/thread/81895

Йохан Дахмани
источник
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

*РЕДАКТИРОВАТЬ:

После всех решений, которые я попробовал, и каждое из них что-то исправляло, но не полностью, этот ответ полностью объясняет и устраняет эту проблему.

Đorđe Nilović
источник
Это очень простое решение, оно сработало для меня, Спасибо @Dorde Nilovic
Р. Мохан
1

Ответ Mimo и ответ 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;
}
SandeepAggarwal
источник
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
            }
        }
    }
}
ChrisB
источник
Вы можете улучшить это. Добавьте дополнительное ограничение для вашего табличного представления, скажем, height <= 10000. Используйте перетаскивание элемента управления из Interface Builder в ваш файл .h, чтобы сделать это ограничение свойством вашего контроллера представления. Тогда вы можете избежать перебора ограничений и поиска. Просто установленоmyTableHeightConstraint.constant = tableView.contentSize.height
RobP
1

Это работает для меня, используя Auto Layout, с табличным представлением только с одним разделом.

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
    }

Я вызываю эту функцию при настройке Auto Layout (в данном примере используется SnapKit, но вы поняли):

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

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

Андре
источник
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()
          }
       }
Niki
источник
0

В качестве дополнения к ответу Anooj VM, я предлагаю следующее, чтобы обновить размер контента только при его изменении.

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

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

объективная версия 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().

AshvinGudaliya
источник
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 будет изменен в соответствии с размером контейнера.

Это позволяет избежать множества вычислений и призывов к макету.

green_knight
источник
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;        
}
brahimm
источник