В ответах на этот вопрос есть много замечательных идей. Однако у большинства из них есть недостатки:
- Решения, которые не проверяют значение y для ячейки, работают только для однострочных макетов. . Они не подходят для макетов представления коллекции с несколькими линиями.
- Решения , которые делают проверки у значения как ответ Angel García Olloqui в единственной работе , если все клетки имеют одинаковую высоту . Они не подходят для ячеек с переменной высотой.
Большинство решений только переопределяют layoutAttributesForElements(in rect: CGRect)
функцию. Они не отменяют layoutAttributesForItem(at indexPath: IndexPath)
. Это проблема, потому что представление коллекции периодически вызывает последнюю функцию для получения атрибутов макета для определенного пути индекса. Если вы не вернете правильные атрибуты из этой функции, вы, вероятно, столкнетесь со всевозможными визуальными ошибками, например, во время вставки и удаления анимаций ячеек или при использовании саморазмеров ячеек путем установки макета представления коллекции estimatedItemSize
. В документации Apple говорится :
Ожидается, что каждый объект настраиваемого макета будет реализовывать этот layoutAttributesForItemAtIndexPath:
метод.
Многие решения также делают предположения о rect
параметре, который передается в layoutAttributesForElements(in rect: CGRect)
функцию. Например, многие из них основаны на предположении, что rect
всегда начинается с начала новой строки, что не обязательно так.
Другими словами:
Большинство решений, предлагаемых на этой странице, работают для некоторых конкретных приложений, но не во всех ситуациях работают должным образом.
AlignedCollectionViewFlowLayout
Чтобы решить эти проблемы, я создал UICollectionViewFlowLayout
подкласс, который следует аналогичной идее, предложенной Мэттом и Крисом Вагнером в их ответах на аналогичный вопрос. Он может выровнять ячейки
⬅︎ слева :
или ➡︎ правильно :
и дополнительно предлагает варианты вертикального выравнивания ячеек в соответствующих строках (если они различаются по высоте).
Вы можете просто скачать его здесь:
Использование просто и объясняется в файле README. По сути, вы создаете экземпляр AlignedCollectionViewFlowLayout
, указываете желаемое выравнивание и назначаете его collectionViewLayout
свойству представления коллекции :
let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left,
verticalAlignment: .top)
yourCollectionView.collectionViewLayout = alignedFlowLayout
(Это также доступно на Cocoapods .)
Как это работает (для ячеек с выравниванием по левому краю):
Идея здесь заключается в том, чтобы полагаться исключительно на layoutAttributesForItem(at indexPath: IndexPath)
функцию . В этом layoutAttributesForElements(in rect: CGRect)
мы просто получаем пути индекса всех ячеек внутри, rect
а затем вызываем первую функцию для каждого пути индекса, чтобы получить правильные кадры:
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))
layoutAttributesObjects?.forEach({ (layoutAttributes) in
if layoutAttributes.representedElementCategory == .cell {
let indexPath = layoutAttributes.indexPath
if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
( copy()
Функция просто создает глубокую копию всех атрибутов макета в массиве. Вы можете изучить исходный код для ее реализации.)
Итак, теперь единственное, что нам нужно сделать, это правильно реализовать layoutAttributesForItem(at indexPath: IndexPath)
функцию. Суперкласс UICollectionViewFlowLayout
уже помещает правильное количество ячеек в каждую строку, поэтому нам нужно только сдвинуть их влево в соответствующей строке. Сложность заключается в вычислении количества пространства, необходимого для смещения каждой ячейки влево.
Поскольку мы хотим иметь фиксированный интервал между ячейками, основная идея состоит в том, чтобы просто предположить, что предыдущая ячейка (ячейка слева от ячейки, которая находится в данный момент) уже расположена должным образом. Затем нам нужно только добавить интервал ячейки к maxX
значению кадра предыдущей ячейки, и этоorigin.x
значение для кадра текущей ячейки.
Теперь нам нужно только знать, когда мы достигли начала строки, чтобы мы не выравнивали ячейку рядом с ячейкой в предыдущей строке. (Это не только приведет к неправильному макету, но и приведет к чрезмерной задержке.) Итак, нам нужен рекурсивный якорь. Подход, который я использую для поиска этого рекурсивного якоря, следующий:
Чтобы узнать, находится ли ячейка с индексом i в той же строке, что и ячейка с индексом i-1 ...
+---------+----------------------------------------------------------------+---------+
| | | |
| | +------------+ | |
| | | | | |
| section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
| inset | |intersection| | | line rect | inset |
| |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| |
| (left) | | | current item | (right) |
| | +------------+ | |
| | previous item | |
+---------+----------------------------------------------------------------+---------+
... Я «рисую» прямоугольник вокруг текущей ячейки и растягиваю его по ширине всей коллекции. Поскольку UICollectionViewFlowLayout
центры всех ячеек по вертикали, каждая ячейка в одной строке должна пересекаться с этим прямоугольником.
Таким образом, я просто проверяю, пересекается ли ячейка с индексом i-1 с этим прямоугольником линии, созданным из ячейки с индексом i .
Если он пересекается, ячейка с индексом i не является самой левой ячейкой в строке.
→ Получить кадр предыдущей ячейки (с индексом i − 1 ) и переместить текущую ячейку рядом с ним.
Если он не пересекается, ячейка с индексом i является самой левой ячейкой в строке.
→ Переместите ячейку к левому краю представления коллекции (не меняя ее вертикального положения).
Я не буду публиковать здесь реальную реализацию layoutAttributesForItem(at indexPath: IndexPath)
функции, потому что считаю, что наиболее важной частью является понимание идеи, и вы всегда можете проверить мою реализацию в исходном коде . (Это немного сложнее, чем объясняется здесь, потому что я также разрешаю .right
выравнивание и различные варианты вертикального выравнивания. Однако он следует той же идее.)
Вау, я думаю, это самый длинный ответ, который я когда-либо писал на Stackoverflow. Надеюсь, это поможет. 😉