У відповідях на це питання є багато чудових ідей. Однак у більшості з них є деякі недоліки:
- Рішення, які не перевіряють значення y комірки, працюють лише для однорядкових макетів . Вони не вдаються до макетів подання колекції з кількома рядками.
- Рішення, які перевіряють значення y, як відповідь Ангела Гарсії Оллокі, працюють, лише якщо всі клітинки мають однакову висоту . Вони виходять з ладу для комірок зі змінною висотою.
Більшість рішень замінює лише 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]? {
// We may not change the original layout attributes
// or UICollectionViewFlowLayout might complain.
let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))
layoutAttributesObjects?.forEach({ (layoutAttributes) in
if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
let indexPath = layoutAttributes.indexPath
// Retrieve the correct frame from layoutAttributesForItem(at: 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. Сподіваюся, це допоможе. 😉