Предотвращение "обтекания" элементов в 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)
}
}
разделы строки, элементы столбцы