ios uitableviewcell dynamic - Использование автоматической компоновки в UITableView для динамических раскладок ячеек и переменных высот строк





12 Answers

Для IOS8 это очень просто:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

ИЛИ ЖЕ

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

Но для IOS7 ключ вычисляет высоту после автовыключения,

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

Важный

  • Если несколько ярлыков строк, не забудьте установить для numberOfLines значение 0 .

  • Не забывайте label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

Здесь приведен полный пример кода.

EDIT Swift 4.2 UITableViewAutomaticDimension изменен на UITableView.automaticDimension

height swift 10

Как вы используете автоматическую компоновку в UITableViewCell s в представлении таблицы, чтобы содержимое и подсечки каждой ячейки определяли высоту строки (сама / автоматически) при сохранении плавной прокрутки?




Я упаковал решение iOS7 @ smileyborg в категории

Я решил обернуть это умное решение @smileyborg в категорию UICollectionViewCell+AutoLayoutDynamicHeightCalculation .

Категория также устраняет проблемы, описанные в ответе @ wildmonkey (загрузка ячейки из systemLayoutSizeFittingSize: и systemLayoutSizeFittingSize: возврат CGRectZero )

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

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see .com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Пример использования:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

К счастью, нам не придется делать этот джаз в iOS8, но там он пока!




Решение, предложенное @smileyborg, почти идеально. Если у вас есть пользовательская ячейка, и вы хотите один или несколько UILabelс динамическими высотами, тогда метод systemLayoutSizeFittingSize в сочетании с включенным AutoLayout возвращает a, CGSizeZeroесли вы не переместите все ограничения соты из ячейки в свой контентный вид (как это предложено в @TomSwift здесь. Как изменить размер супервизора на подходит для всех подзапросов с автозапуском? ).

Для этого вам нужно вставить следующий код в свою пользовательскую реализацию UITableViewCell (благодаря @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Смешение ответа @smileyborg с этим должно работать.




В случае, если у людей все еще есть проблемы с этим. Я написал краткое сообщение в блоге об использовании Autolayout с помощью UITableViews « Использование Autolayout for Dynamic Cell Heights», а также компонент с открытым исходным кодом, который поможет сделать его более абстрактным и более простым в реализации. https://github.com/Raizlabs/RZCellSizeManager




Пока ваш макет в вашей камере хорош.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Обновление. Вы должны использовать динамическое изменение размера, введенное в iOS 8.




Чтобы установить автоматическое измерение высоты строки и оцененной высоты строки, выполните следующие шаги, чтобы сделать автоматическое измерение эффективным для макета высоты ячейки / строки.

  • Присвоить и реализовать данные tableviewSource и делегировать
  • Назначить UITableViewAutomaticDimensionrowHeight & оценкамRowHeight
  • Внедрить методы делегирования / dataSource (т.е. heightForRowAtи вернуть ему значение UITableViewAutomaticDimension)

-

Цель C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;
}

Swift:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

Для экземпляра ярлыка в UITableviewCell

  • Установить число строк = 0 (& режим разрыва строки = обрезать хвост)
  • Задайте все ограничения (сверху, снизу, справа налево) относительно контейнера супервизора / ячейки.
  • Необязательно : установите минимальную высоту для метки, если вы хотите, чтобы минимальная вертикальная область была покрыта меткой, даже если данных нет.

Примечание . Если у вас есть несколько ярлыков (UIElements) с динамической длиной, которые должны быть скорректированы в соответствии с его размером содержимого: Настройте «Охват содержимого и приоритет сжатия» для меток, которые вы хотите расширить / сжать с более высоким приоритетом.




Еще одно «решение»: пропустите все это разочарование и вместо этого используйте UIScrollView, чтобы получить результат, который выглядит и чувствует себя идентичным UITableView.

Это было болезненное «решение» для меня, после того, как мы поставили буквально 20+ очень расстраивающих часов, пытаясь построить что-то вроде того, что предлагалось и не хватало smileyborg в течение многих месяцев и трех версий выпусков App Store.

Я считаю, что если вам действительно нужна поддержка iOS 7 (для нас это важно), то технология слишком хрупка, и вы будете пытаться вытянуть свои волосы. И что UITableView полностью переборщил, если вы не используете некоторые из усовершенствованных функций редактирования строк и / или действительно нуждаетесь в поддержке 1000+ «строк» ​​(в нашем приложении это реалистично не более 20 строк).

Дополнительный бонус заключается в том, что код становится безумно простым по сравнению со всем дерьмом делегата и обратно и вперед, который поставляется с UITableView. Это всего лишь один цикл кода в viewOnLoad, который выглядит элегантно и легко управляется.

Вот несколько советов о том, как это сделать:

1) Используя либо Storyboard, либо файл nib, создайте ViewController и связанный с ним корневой вид.

2) Перетащите UIScrollView на корневой режим.

3) Добавьте ограничения верхнего, нижнего, левого и правого к представлению верхнего уровня, чтобы UIScrollView заполнил весь корневой вид.

4) Добавьте UIView внутри UIScrollView и назовите его «контейнер». Добавьте ограничения сверху, снизу, слева и справа в UIScrollView (его родительский элемент). KEY TRICK: Также добавьте ограничения «Равная ширина», чтобы связать UIScrollView и UIView.

Вы получите сообщение об ошибке «просмотр прокрутки имеет неоднозначную высоту прокручиваемого содержимого» и что ваш UIView для контейнера должен иметь высоту 0 пикселей. Ни одна ошибка не имеет значения, когда приложение работает.

5) Создайте nib-файлы и контроллеры для каждой из ваших «ячеек». Используйте UIView, а не UITableViewCell.

5) В корневом ViewController вы по существу добавляете все «строки» в контейнер UIView и программно добавляете ограничения, связывающие их левый и правый края с видом контейнера, их верхние края либо сверху, либо в верхнем ящике контейнера (для первого элемента), либо предыдущей ячейки. Затем свяжите последнюю ячейку с дном контейнера.

Для нас каждая «строка» находится в файле nib. Таким образом, код выглядит примерно так:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

И вот код для UITools.addViewToTop:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)

        //Set left and right constraints so fills full horz width:

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))

        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

Единственный «полученный», который я нашел с этим подходом до сих пор, заключается в том, что UITableView имеет приятную функцию «плавающих» заголовков разделов в верхней части представления при прокрутке. Вышеупомянутое решение не будет делать этого, если вы не добавите больше программ, но для нашего конкретного случая эта функция не была на 100% существенной, и никто не заметил, когда она ушла.

Если вы хотите делителей между вашими ячейками, просто добавьте UIView с высотой 1 пиксель в нижней части пользовательской «ячейки», которая выглядит как разделитель.

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

TableView показывает некоторые пустые строки и разделители под вашим контентом, если он не заполняет весь экран, где это решение отсутствует. Но лично я предпочитаю, чтобы эти пустые строки не были в любом случае - с переменной высотой ячейки он всегда выглядел «багги» для меня, чтобы иметь пустые строки там.

Здесь надеется, что какой-то другой программист прочитает мой пост, прежде чем тратить 20 + часов, пытаясь понять это с помощью Table View в своем собственном приложении. :)




Мне приходилось использовать динамические представления (установки представлений и ограничений по коду), и когда я хотел установить ширину ярлыка preferredMaxLayoutWidth равным 0. Поэтому у меня неправильная высота ячейки.

Затем я добавил

[cell layoutSubviews];

перед выполнением

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

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




Если вы используете программный подход, вот что следует учитывать для iOS 10 с использованием якорей в Swift.

Существует три правила / этапы

NUMBER 1: установите эти два свойства tableview в viewDidLoad, первый из которых сообщает табличному представлению, что должен ожидать динамические размеры в своих ячейках, второй - это просто, чтобы приложение подсчитывало размер индикатора полосы прокрутки, поэтому это помогает спектакль.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMBER 2: важно, чтобы вы добавляли subviews в contentView ячейки не к представлению, а также использовали его layoutsmarginguide для привязки subviews к началу и снизу, это рабочий пример того, как это сделать.

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

Создайте метод, который добавит subviews и выполнит макет, вызовите его в методе init.

НОМЕР 3: НЕ ПРИЗЫВАЙТЕ МЕТОД:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

Если вы это сделаете, вы переопределите свою реализацию.

Следуйте этим 3 правилам для динамических ячеек в табличных представлениях.

вот рабочая реализация https://github.com/jamesrochabrun/MinimalViewController




В моем случае отступы были из-за высот разделаHeader и sectionFooter, где раскадровка позволяла мне изменить его на минимум 1. Таким образом, в методе viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0



Что касается принятого ответа @smileyborg, я нашел

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

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

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

Где w: ширина таблицы




Просто добавьте эти две функции в свой контроллер управления, и это решит вашу проблему. Здесь список - это строковый массив, который содержит строку каждой строки.

 func tableView(_ tableView: UITableView, 
   estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])

    return (tableView.rowHeight) 
}

func calculateHeight(inString:String) -> CGFloat
{
    let messageString = input.text
    let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]

    let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)

    let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)

    let requredSize:CGRect = rect
    return requredSize.height
}



Related