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

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

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

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

25 ответов

Решение

На самом деле я нашел ответ сам.

Я просто создаю новый 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;
    });

Решение Swift 4 без KVO, DispatchQueue, настройка ограничений самостоятельно. Это решение основано на ответе Гульца.

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) Добавить UITableView к вашему макету и установить ограничения со всех сторон. Установите класс этого в IntrinsicTableView,

3) Вы должны увидеть некоторые ошибки, потому что раскадровка не принимает наш подкласс intrinsicContentSize в учетную запись. Исправьте это, открыв инспектор размера и переопределив intrinsicContentSize для значения заполнителя. Это переопределение для времени разработки. Во время выполнения он будет использовать переопределение в нашем IntrinsicTableView учебный класс

Быстрое Решение

Следуй этим шагам:

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

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

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

Добавьте наблюдателя для свойства 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;
    }

Надеюсь, что это поможет вам.

У меня было табличное представление в представлении прокрутки и я должен был вычислить высоту 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
    }

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

Решение Swift 5

Выполните следующие четыре шага:

  1. Установите ограничение высоты для представления таблицы из раскадровки.

  2. Перетащите ограничение высоты из раскадровки и создайте @IBOutlet для него в файле контроллера представления.

    @IBOutlet var tableViewHeightConstraint: NSLayoutConstraint!
    
  3. Добавьте наблюдателя для свойства contentSize в override func viewDidLoad()

override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
 
    }

  1. Затем вы можете динамически изменять высоту таблицы, используя этот код:

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
         if(keyPath == "contentSize"){
             if let newvalue = change?[.newKey]
             {
                 DispatchQueue.main.async {
                 let newsize  = newvalue as! CGSize
                 self.tableViewHeightConstraint.constant = newsize.height
                 }
    
             }
         }
     }
    

Если вы не хотите самостоятельно отслеживать изменения размера содержимого табличного представления, этот подкласс может оказаться полезным.

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

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

tableView.estimatedRowHeight = 0;

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

Я сделал немного по-другому, на самом деле мой 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()
          }
       }

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

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

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

У меня это работает с использованием 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.

на основе ответа fl034

SWift 5

      var tableViewHeight: NSLayoutConstraint?

    tableViewHeight = NSLayoutConstraint(item: servicesTableView, 
    attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,
    multiplier: 0.0, constant: 10)
    tableViewHeight?.isActive = true


  func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    tableViewHeight?.constant = tableView.contentSize.height
    tableView.layoutIfNeeded()
}

Существует гораздо лучший способ сделать это, если вы используете 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
            }
        }
    }
}

Ответ 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;
}

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

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

Для моего случая, как я справляюсь. дать любую постоянную высоту представления таблицы. создайте выход высоты таблицы, а затем вызовите следующую функцию, где бы вы ни перезагружали tableView.

      private func manageHeight(){
        tableViewHeight.constant=CGFloat.greatestFiniteMagnitude
        tableView.reloadData()
        tableView.layoutIfNeeded()
        tableViewHeight.constant=tableView.contentSize.height
    }

примечание: tableView — это выход для вашего табличного представления, а tableViewHeight — это выход для высоты tableView.

В качестве дополнения к ответу 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"];
}

Вы можете попробовать этот Custom 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(),

Я использую расширение UIView, подход близок к подходу @ChrisB выше

 extension UIView {
func updateHeight(_ height:NSLayoutConstraint)
{
    
    let newSize = CGSize(width: self.frame.size.width, height: CGFloat(MAXFLOAT))
    let fitSize : CGSize = self.sizeThatFits(newSize)
    
    height.constant = fitSize.height
    
   
}
}

реализация::

@IBOutlet weak var myTableView: UITableView!
@IBOutlet weak var myTableVieweHeight: NSLayoutConstraint!
//(call it whenever tableView is updated inside/outside delegate methods)
myTableView.updateHeight(myTableVieweHeigh)

Бонус: может использоваться на любых других UIView, например: ваша собственная динамическая метка

На основании ответа 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);
            }
        }
    }

Объектная версия 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;
}

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

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

Мое решение для этого в быстрой 3: вызвать этот метод в viewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}

Установите свойства табличного представления в

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100

И в ячейке табличного представления установите свойство вашего ярлыка numberOfLines = 0

установите верхнюю, нижнюю, левую, правую ограничения вашей метки на верхнюю, нижнюю, левую, правую ограничения ячейки ** Он автоматически вычислит высоту

Другие вопросы по тегам