Предотвращение "обтекания" элементов в UICollectionView

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

Признавая, что UICollectionView это подкласс UIScrollViewЯ попытался просто вручную установить свойство размера содержимого представления коллекции в viewDidLoad:

self.collectionView.contentSize = CGSizeMake((columns * (cellWidth + itemSpacingX), (rows * (cellHeight + itemSpacingY));

Но это не имело никакого эффекта. Вопросы:

  • Есть ли простой способ сделать это без создания собственного макета?
  • Будет использовать пользовательский макет и переопределить collectionViewContentSize метод преуспеть в том, чтобы заставить представление коллекции остановить обертывание элементов и прокрутить в обоих направлениях?
  • Если мне нужно создать собственный макет - который я должен потратить некоторое время, чтобы изучить - я подкласс UICollectionViewLayoutили подклассы UICollectionViewFlowLayout сэкономить время?

ОБНОВЛЕНИЕ: я попытался встроить UICollectionView как подпредставление UIScrollView. Само представление коллекции ведет себя правильно - строки не переносятся по краю представления прокрутки, сообщая мне, что оно заполняет установленный мною размер контента UIScrollView. Но представление прокрутки не панорамируется в горизонтальном направлении, то есть, оно прокручивается только вертикально, что в любом случае делает представление коллекции. Так застрял снова. Может ли быть проблема с цепочкой респондента?

3 ответа

Решение

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

Простой способ: использовать UIScrollView и подкласс UICollectionViewFlowLayout. Вставить UICollectionView в UIScrollView, Установите свойство contentSize представления прокрутки в viewDiDLoad чтобы соответствовать полному размеру, который будет занимать представление вашей коллекции (это позволит макету потока по умолчанию размещать элементы в одной строке внутри раздела без переноса). Подкласс UICollectionViewFlowLayoutи установите этот объект в качестве пользовательского макета для представления коллекции. В пользовательском макете потока переопределите collectionViewContentSize вернуть полный размер матрицы представления коллекции. При таком подходе вы будете использовать макет потока, но сможете прокручивать в обоих направлениях, чтобы просматривать неупакованные разделы. Недостатком является то, что у вас все еще есть довольно ограниченная схема потока. Плюс, кажется, неуклюже, чтобы положить UICollectionView внутри экземпляра своего собственного суперкласса только для того, чтобы получить функциональность, которую должно иметь само представление коллекции.

Более сложный путь, но более универсальный и элегантный: подкласс UICollectionViewLayout. Я использовал этот учебник, чтобы узнать, как реализовать полный пользовательский макет. Вам не нужен UIScrollView Вот. Если вы отказываетесь от макета потока, подкласс UICollectionViewLayoutи установите его в качестве пользовательского макета, вы можете построить матрицу и получить правильное поведение из самого представления коллекции. Это больше работы, потому что вам нужно сгенерировать все атрибуты макета, но вы сможете настроить представление коллекции так, как вам нужно.

По моему мнению, Apple должна добавить свойство к макету потока по умолчанию, которое подавляет перенос. Получение устройства для отображения 2D-матрицы с неповрежденными строками и столбцами не является экзотической функциональностью, и кажется, что это должно быть легче сделать.

Вот версия кода AndrewK, обновленная до Swift 4:

import UIKit

class CollectionViewMatrixLayout: UICollectionViewLayout {

  var itemSize: CGSize
  var interItemSpacingY: CGFloat
  var interItemSpacingX: CGFloat
  var layoutInfo: [IndexPath: UICollectionViewLayoutAttributes]

  override init() {
    itemSize = CGSize(width: 50, height: 50)
    interItemSpacingY = 1
    interItemSpacingX = 1
    layoutInfo = [IndexPath: UICollectionViewLayoutAttributes]()

    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func prepare() {
    guard let collectionView = self.collectionView else {
      return
    }

    var cellLayoutInfo = [IndexPath: UICollectionViewLayoutAttributes]()
    var indexPath = IndexPath(item: 0, section: 0)

    let sectionCount = collectionView.numberOfSections
    for section in 0..<sectionCount {

      let itemCount = collectionView.numberOfItems(inSection: section)
      for item in 0..<itemCount {
        indexPath = IndexPath(item: item, section: section)
        let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        itemAttributes.frame = frameForCell(at: indexPath)
        cellLayoutInfo[indexPath] = itemAttributes
      }

      self.layoutInfo = cellLayoutInfo
    }
  }

  func frameForCell(at indexPath: IndexPath) -> CGRect {
    let row = indexPath.section
    let column = indexPath.item

    let originX = (itemSize.width + interItemSpacingX) * CGFloat(column)
    let originY = (itemSize.height + interItemSpacingY) * CGFloat(row)

    return CGRect(x: originX, y: originY, width: itemSize.width, height: itemSize.height)
  }

  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
  {
    var allAttributes = Array<UICollectionViewLayoutAttributes>()

    for (_, attributes) in self.layoutInfo {
      if (rect.intersects(attributes.frame)) {
        allAttributes.append(attributes)
      }
    }
    return allAttributes
  }

  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return self.layoutInfo[indexPath]
  }

  override var collectionViewContentSize: CGSize {
    guard let collectionView = self.collectionView else {
      return .zero
    }

    let sectionCount = collectionView.numberOfSections
    let height = (itemSize.height + interItemSpacingY) * CGFloat(sectionCount)

    let itemCount = Array(0..<sectionCount)
      .map { collectionView.numberOfItems(inSection: $0) }
      .max() ?? 0

    let width  = (itemSize.width + interItemSpacingX) * CGFloat(itemCount)

    return CGSize(width: width, height: height)

  }
}

Вот полная матрица customLayout:

import UIKit

class MatrixLayout: UICollectionViewLayout {

    var itemSize: CGSize!
    var interItemSpacingY: CGFloat!
    var interItemSpacingX: CGFloat!
    var layoutInfo: Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>!

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)

        itemSize = CGSizeMake(50.0, 50.0)
        interItemSpacingY = 1.0
        interItemSpacingX = 1.0
    }

    override func prepareLayout() {

        var cellLayoutInfo = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()

        let sectionCount = self.collectionView?.numberOfSections()
        var indexPath = NSIndexPath(forItem: 0, inSection: 0)

        for (var section = 0; section < sectionCount; section += 1)
        {
            let itemCount = self.collectionView?.numberOfItemsInSection(section)

            for (var item = 0; item < itemCount; item += 1)
            {
                indexPath = NSIndexPath(forItem:item, inSection: section)
                let itemAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
                itemAttributes.frame = frameForCellAtIndexPath(indexPath)
                cellLayoutInfo[indexPath] = itemAttributes
            }

            self.layoutInfo = cellLayoutInfo
        }
    }

    func frameForCellAtIndexPath(indexPath: NSIndexPath) -> CGRect
    {
        let row = indexPath.section
        let column = indexPath.item

        let originX = (self.itemSize.width + self.interItemSpacingX) * CGFloat(column)
        let originY = (self.itemSize.height + self.interItemSpacingY) * CGFloat(row)

        return CGRectMake(originX, originY, self.itemSize.width, self.itemSize.height)
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
    {
        var allAttributes = Array<UICollectionViewLayoutAttributes>()

        for (index, attributes) in self.layoutInfo
        {
            if (CGRectIntersectsRect(rect, attributes.frame))
            {
                allAttributes.append(attributes)
            }
        }
        return allAttributes
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        return self.layoutInfo[indexPath]
    }


    override func collectionViewContentSize() -> CGSize {

        let width:CGFloat = (self.itemSize.width + self.interItemSpacingX) * CGFloat((self.collectionView?.numberOfItemsInSection(0))!)
        let height:CGFloat = (self.itemSize.height + self.interItemSpacingY) * CGFloat((self.collectionView?.numberOfSections())!)

        return CGSizeMake(width, height)
    }
}

разделы строки, элементы столбцы

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