Указание одного измерения ячеек в UICollectionView с помощью автоматического макета


в iOS 8 the UICollectionViewFlowLayout поддерживает автоматическое изменение размеров ячеек на основе их собственного размера содержимого. Это изменяет размер ячеек как по ширине, так и по высоте в соответствии с их содержимым.

можно ли указать фиксированное значение для ширины (или высоты) всех ячеек и разрешить другим измерениям изменять размер?

для простого примера рассмотрим многострочную метку в ячейке с ограничениями, расположенными по сторонам ячейки. Мульти-линии метки может быть изменен различными способами для размещения текста. Ячейка должна заполнить ширину представления коллекции и соответствующим образом настроить его высоту. Вместо этого ячейки имеют случайный размер, и это даже вызывает сбой, когда размер ячейки больше, чем не прокручиваемый размер представления коллекции.


iOS 8 представляет метод systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: для каждой ячейки в представлении коллекции макет вызывает этот метод в ячейке, передавая предполагаемый размер. Что будет иметь смысл для меня было бы переопределить этот метод в ячейке, передать размер, который задан, и установить горизонтальное ограничение на required и низкий приоритет вертикального ограничения. Таким образом, горизонтальный размер фиксируется к значению, заданному в макете, а вертикальный размер может быть гибким.

что-то вроде этого:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
    attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    return attributes;
}

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

- это size параметр этого метода действительно просто способ передать две константы? Нет ли способа достичь поведения, которое я ожидаю получить соответствующую высоту для данного размера?


Альтернативные Решения

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

2) Используйте табличное представление. Табличные представления работают таким образом из коробки, поскольку ячейки имеют фиксированную ширину, но это не будет учитывать другие ситуации как макет iPad с фиксированной шириной ячеек в нескольких столбцах.

5 98

5 ответов:

похоже, что вы просите способ использовать UICollectionView для создания макета, такого как UITableView. Если это действительно то, что вы хотите, правильный способ сделать это с пользовательский подкласс UICollectionViewLayout (что-то вроде SBTableLayout).

С другой стороны, если вы действительно спрашиваете, есть ли чистый способ сделать это с помощью uicollectionviewflowlayout по умолчанию, то я считаю, что нет никакого способа. Даже с клетками собственн-загрунтовкы iOS8, оно не просто. Фундаментальная проблема, как вы говорите, заключается в том, что механизм компоновки потока не дает возможности исправить одно измерение и позволить другому реагировать. (Кроме того, даже если бы вы могли, возникла бы дополнительная сложность вокруг необходимости двух проходов макета для определения размера многострочных меток. Это может не соответствовать как самостоятельной калибровки ячеек для вычисления всех размеров через один звонок в systemLayoutSizeFittingSize.)

однако, если вы все еще хотите создать макет типа tableview с помощью макет потока, с ячейками, которые определяют свой собственный размер и естественно реагируют на ширину представления коллекции, конечно, это возможно. Есть еще грязный путь. Я сделал это с помощью "ячейки размера", т. е. не отображаемого UICollectionViewCell, который контроллер сохраняет только для вычисления размеров ячеек.

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

в вашем UICollectionViewDelegateFlowLayout вы реализуете такой метод:

func collectionView(collectionView: UICollectionView,
  layout collectionViewLayout: UICollectionViewLayout,
  sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
  // NOTE: here is where we say we want cells to use the width of the collection view
   let requiredWidth = collectionView.bounds.size.width

   // NOTE: here is where we ask our sizing cell to compute what height it needs
  let targetSize = CGSize(width: requiredWidth, height: 0)
  /// NOTE: populate the sizing cell's contents so it can compute accurately
  self.sizingCell.label.text = items[indexPath.row]
  let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
  return adequateSize
}

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

вторая часть состоит в том, чтобы заставить ячейку размера использовать свои собственные ограничения AL для вычисления высоты. Это может быть сложнее, чем должно быть, из-за того, как многострочный UILabel эффективно требует двухэтапного процесса макета. Работа выполняется в методе preferredLayoutSizeFittingSize, что примерно так:

 /*
 Computes the size the cell will need to be to fit within targetSize.

 targetSize should be used to pass in a width.

 the returned size will have the same width, and the height which is
 calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
 can fit within that width.

 */
 func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {

   // save original frame and preferredMaxLayoutWidth
   let originalFrame = self.frame
   let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth

   // assert: targetSize.width has the required width of the cell

   // step1: set the cell.frame to use that width
   var frame = self.frame
   frame.size = targetSize
   self.frame = frame

   // step2: layout the cell
   self.setNeedsLayout()
   self.layoutIfNeeded()
   self.label.preferredMaxLayoutWidth = self.label.bounds.size.width

   // assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width

   // step3: compute how tall the cell needs to be

   // this causes the cell to compute the height it needs, which it does by asking the 
   // label what height it needs to wrap within its current bounds (which we just set).
   let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

   // assert: computedSize has the needed height for the cell

   // Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
   let newSize = CGSize(width:targetSize.width,height:computedSize.height)

   // restore old frame and preferredMaxLayoutWidth
   self.frame = originalFrame
   self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth

   return newSize
 }

(этот код адаптирован из примера кода Apple из примера кода сессии WWDC2014 на "расширенном представлении коллекции".)

несколько моментов, чтобы заметить. Он использует layoutIfNeeded () для принудительной компоновки всей ячейки, чтобы вычислить и установить ширину метки. Но этого недостаточно. Я считаю вам также нужно установить preferredMaxLayoutWidth Так что метка будет использовать эту ширину с автоматической компоновкой. И только тогда вы можете использовать systemLayoutSizeFittingSize чтобы получить ячейку для вычисления ее высоты с учетом метки.

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

я надеюсь, что со временем самостоятельной калибровки клетки работают по-разному и все это будет сделать намного проще.

пример проекта показывать его на работе.

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

в теории, новые средства iOS8 для" клеток собственн-загрунтовкы " должны сделать это ненужным. Если вы определили ячейку с автоматической компоновкой (AL), то представление коллекции должно быть достаточно умным, чтобы позволить ему размерить себя и выложите себя правильно. На практике я не видел примеров, чтобы это работало с многострочными метками. Я думаю этой отчасти потому, что само-калибровки сотового механизм до сих пор глючит.

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

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

а как насчет preferredLayoutAttributesFittingattributes:?

The preferredLayoutAttributesFittingAttributes: метод-это отвлекающий маневр, я думаю. Это только там, чтобы быть использованы с новой самостоятельной калибровки аппарата клетки. Так что это не ответ, пока этот механизм ненадежен.

а что такое вверх с systemlayoutSizeFittingSize:?

вы правы, документы сбивают с толку.

документы на systemLayoutSizeFittingSize: и systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: оба предполагают, что вы должны передать только UILayoutFittingCompressedSize и UILayoutFittingExpandedSize как targetSize. Однако сама сигнатура метода, комментарии заголовка и поведение функций указывают на то, что они отвечают точному значению

там чище способ сделать это, чем некоторые другие ответы здесь, и он работает хорошо. Он должен быть производительным (просмотры коллекции загружаются быстро, нет ненужных автоматических проходов макета и т. д.) и не имеет никаких "магических чисел", таких как фиксированная ширина представления коллекции. Изменение размера представления коллекции, например, при вращении, а затем аннулирование макета также должно отлично работать.

1. Создайте следующий подкласс компоновки потока

class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // Don't forget to use this class in your storyboard (or code, .xib etc)

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
        guard let collectionView = collectionView else { return attributes }
        attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
        return attributes
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let allAttributes = super.layoutAttributesForElementsInRect(rect)
        return allAttributes?.flatMap { attributes in
            switch attributes.representedElementCategory {
            case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
            default: return attributes
            }
        }
    }
}

2. Регистрация представления коллекции для автоматической калибровки

// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)

flowLayout.estimatedItemSize = flowLayout.itemSize

3. Используйте предопределенную ширину + пользовательскую высоту в подклассе ячейки

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    return layoutAttributes
}

простой способ сделать это в iOS 9 в нескольких строках кодов-горизонтальный способ exemple (фиксация его высоты к высоте представления коллекции):

введите макет потока представления коллекции с помощью estimatedItemSize для того чтобы включить собственн-размер ячейки :

self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);

реализовать делегат макета представления коллекции (в вашем контроллере представления большую часть времени), collectionView:layout:sizeForItemAtIndexPath: . Цель здесь состоит в том, чтобы установить фиксированную высоту (или ширину) в размер вида коллекции. Значение 10 может быть любым, но вы следует установить его в значение, которое не нарушает ограничения :

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}

переопределите свою пользовательскую ячейку preferredLayoutAttributesFittingAttributes: метод, эта часть фактически вычисляет вашу динамическую ширину ячейки на основе ваших ограничений автоматической компоновки и высоты, которую вы только что установили:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
    float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
    CGRect frame = attributes.frame;
    frame.size.width = desiredWidth;
    attributes.frame = frame;
    return attributes;
}

попробуйте исправить свою ширину в предпочтительных атрибутах макета:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
    CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGRect newFrame = attr.frame;
    newFrame.size.height = size.height;
    attr.frame = newFrame;
    return attr;
}

естественно, вы также хотите, чтобы убедиться, что вы правильно настроили свой макет:

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];

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

да это можно сделать с помощью автоматического макета программно и путем установки ограничений в раскадровке или xib. Вам нужно добавить ограничение для размера ширины, чтобы оставаться постоянным и установить высоту больше или равную.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/

http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
надеюсь, это будет полезно и решить вашу проблему.