Я использую UICollectionView в своем проекте, где в строке есть несколько ячеек разной ширины. Согласно: https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
он распределяет ячейки по строке с равным отступом. Это происходит, как и ожидалось, за исключением того, что я хочу выровнять их по левому краю и жестко закодировать ширину заполнения.
Я полагаю, мне нужно создать подкласс UICollectionViewFlowLayout, однако после прочтения некоторых руководств и т. Д. В Интернете я просто не понимаю, как это работает.
Ответы:
Другие решения в этом потоке не работают должным образом, если строка состоит только из 1 элемента или слишком сложна.
Основываясь на примере, приведенном Райаном, я изменил код, чтобы обнаружить новую строку, проверив позицию Y нового элемента. Очень просто и быстро в исполнении.
Swift:
class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in if layoutAttribute.frame.origin.y >= maxY { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes } }
Если вы хотите, чтобы дополнительные представления сохраняли свой размер, добавьте следующее в верхней части закрытия
forEach
вызова:guard layoutAttribute.representedElementCategory == .cell else { return }
Цель-C:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attributes = [super layoutAttributesForElementsInRect:rect]; CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use. CGFloat maxY = -1.0f; //this loop assumes attributes are in IndexPath order for (UICollectionViewLayoutAttributes *attribute in attributes) { if (attribute.frame.origin.y >= maxY) { leftMargin = self.sectionInset.left; } attribute.frame = CGRectMake(leftMargin, attribute.frame.origin.y, attribute.frame.size.width, attribute.frame.size.height); leftMargin += attribute.frame.size.width + self.minimumInteritemSpacing; maxY = MAX(CGRectGetMaxY(attribute.frame), maxY); } return attributes; }
источник
let attributes = super.layoutAttributesForElementsInRect(rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
В ответах на этот вопрос есть много замечательных идей. Однако у большинства из них есть недостатки:
Большинство решений только переопределяют
layoutAttributesForElements(in rect: CGRect)
функцию. Они не отменяютlayoutAttributesForItem(at indexPath: IndexPath)
. Это проблема, потому что представление коллекции периодически вызывает последнюю функцию для получения атрибутов макета для определенного пути индекса. Если вы не вернете правильные атрибуты из этой функции, вы, вероятно, столкнетесь со всевозможными визуальными ошибками, например, во время вставки и удаления анимаций ячеек или при использовании саморазмеров ячеек путем установки макета представления коллекцииestimatedItemSize
. В документации Apple говорится :Многие решения также делают предположения о
rect
параметре, который передается вlayoutAttributesForElements(in rect: CGRect)
функцию. Например, многие из них основаны на предположении, чтоrect
всегда начинается с начала новой строки, что не обязательно так.Другими словами:
Большинство решений, предлагаемых на этой странице, работают для некоторых конкретных приложений, но не во всех ситуациях работают должным образом.
AlignedCollectionViewFlowLayout
Чтобы решить эти проблемы, я создал
UICollectionViewFlowLayout
подкласс, который следует аналогичной идее, предложенной Мэттом и Крисом Вагнером в их ответах на аналогичный вопрос. Он может выровнять ячейки⬅︎ слева :
или ➡︎ правильно :
и дополнительно предлагает варианты вертикального выравнивания ячеек в соответствующих строках (если они различаются по высоте).
Вы можете просто скачать его здесь:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
Использование просто и объясняется в файле README. По сути, вы создаете экземпляр
AlignedCollectionViewFlowLayout
, указываете желаемое выравнивание и назначаете егоcollectionViewLayout
свойству представления коллекции :(Это также доступно на 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 ...
... Я «рисую» прямоугольник вокруг текущей ячейки и растягиваю его по ширине всей коллекции. Поскольку
UICollectionViewFlowLayout
центры всех ячеек по вертикали, каждая ячейка в одной строке должна пересекаться с этим прямоугольником.Таким образом, я просто проверяю, пересекается ли ячейка с индексом i-1 с этим прямоугольником линии, созданным из ячейки с индексом i .
Если он пересекается, ячейка с индексом i не является самой левой ячейкой в строке.
→ Получить кадр предыдущей ячейки (с индексом i − 1 ) и переместить текущую ячейку рядом с ним.
Если он не пересекается, ячейка с индексом i является самой левой ячейкой в строке.
→ Переместите ячейку к левому краю представления коллекции (не меняя ее вертикального положения).
Я не буду публиковать здесь реальную реализацию
layoutAttributesForItem(at indexPath: IndexPath)
функции, потому что считаю, что наиболее важной частью является понимание идеи, и вы всегда можете проверить мою реализацию в исходном коде . (Это немного сложнее, чем объясняется здесь, потому что я также разрешаю.right
выравнивание и различные варианты вертикального выравнивания. Однако он следует той же идее.)Вау, я думаю, это самый длинный ответ, который я когда-либо писал на Stackoverflow. Надеюсь, это поможет. 😉
источник
В Swift 4.1 и iOS 11, в соответствии с вашими потребностями, вы можете выбрать одну из 2 следующих полных реализаций , чтобы решить вашу проблему.
№1. Выровнять по левому краю автоизменение размера
UICollectionViewCell
sВ приведенной ниже реализации показано, как использовать
UICollectionViewLayout
'slayoutAttributesForElements(in:)
,UICollectionViewFlowLayout
' sestimatedItemSize
иUILabel
'spreferredMaxLayoutWidth
для выравнивания по левому краю ячеек с автоматическим изменением размера в aUICollectionView
:CollectionViewController.swift
import UIKit class CollectionViewController: UICollectionViewController { let array = ["1", "1 2", "1 2 3 4 5 6 7 8", "1 2 3 4 5 6 7 8 9 10 11", "1 2 3", "1 2 3 4", "1 2 3 4 5 6", "1 2 3 4 5 6 7 8 9 10", "1 2 3 4", "1 2 3 4 5 6 7", "1 2 3 4 5 6 7 8 9", "1", "1 2 3 4 5", "1", "1 2 3 4 5 6"] let columnLayout = FlowLayout( minimumInteritemSpacing: 10, minimumLineSpacing: 10, sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = columnLayout collectionView?.contentInsetAdjustmentBehavior = .always collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell") } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return array.count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell cell.label.text = array[indexPath.row] return cell } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout() super.viewWillTransition(to: size, with: coordinator) } }
FlowLayout.swift
import UIKit class FlowLayout: UICollectionViewFlowLayout { required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) { super.init() estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize self.minimumInteritemSpacing = minimumInteritemSpacing self.minimumLineSpacing = minimumLineSpacing self.sectionInset = sectionInset sectionInsetReference = .fromSafeArea } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes } guard scrollDirection == .vertical else { return layoutAttributes } // Filter attributes to compute only cell attributes let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell }) // Group cell attributes by row (cells with same vertical center) and loop on those groups for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) { // Set the initial left inset var leftInset = sectionInset.left // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell for attribute in attributes { attribute.frame.origin.x = leftInset leftInset = attribute.frame.maxX + minimumInteritemSpacing } } return layoutAttributes } }
CollectionViewCell.swift
import UIKit class CollectionViewCell: UICollectionViewCell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .orange label.preferredMaxLayoutWidth = 120 label.numberOfLines = 0 contentView.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false contentView.layoutMarginsGuide.topAnchor.constraint(equalTo: label.topAnchor).isActive = true contentView.layoutMarginsGuide.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
Ожидаемый результат:
№2. Выровнять по левому краю
UICollectionViewCell
с фиксированным размеромРеализация ниже показывает, как использовать
UICollectionViewLayout
'slayoutAttributesForElements(in:)
иUICollectionViewFlowLayout
' sitemSize
для выравнивания ячеек по левому краю с предопределенным размером вUICollectionView
:CollectionViewController.swift
import UIKit class CollectionViewController: UICollectionViewController { let columnLayout = FlowLayout( itemSize: CGSize(width: 140, height: 140), minimumInteritemSpacing: 10, minimumLineSpacing: 10, sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = columnLayout collectionView?.contentInsetAdjustmentBehavior = .always collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell") } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 7 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell return cell } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout() super.viewWillTransition(to: size, with: coordinator) } }
FlowLayout.swift
import UIKit class FlowLayout: UICollectionViewFlowLayout { required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) { super.init() self.itemSize = itemSize self.minimumInteritemSpacing = minimumInteritemSpacing self.minimumLineSpacing = minimumLineSpacing self.sectionInset = sectionInset sectionInsetReference = .fromSafeArea } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes } guard scrollDirection == .vertical else { return layoutAttributes } // Filter attributes to compute only cell attributes let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell }) // Group cell attributes by row (cells with same vertical center) and loop on those groups for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) { // Set the initial left inset var leftInset = sectionInset.left // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell for attribute in attributes { attribute.frame.origin.x = leftInset leftInset = attribute.frame.maxX + minimumInteritemSpacing } } return layoutAttributes } }
CollectionViewCell.swift
import UIKit class CollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .cyan } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
Ожидаемый результат:
источник
10
ли произвольное число?Простое решение в 2019 году
Это один из тех удручающих вопросов, где за эти годы многое изменилось. Теперь это просто.
В основном вы просто делаете это:
// as you move across one row ... a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing // and, obviously start fresh again each row
Все, что вам сейчас нужно, это шаблонный код:
override func layoutAttributesForElements( in rect: CGRect)->[UICollectionViewLayoutAttributes]? { guard let att = super.layoutAttributesForElements(in: rect) else { return [] } var x: CGFloat = sectionInset.left var y: CGFloat = -1.0 for a in att { if a.representedElementCategory != .cell { continue } if a.frame.origin.y >= y { x = sectionInset.left } a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing y = a.frame.maxY } return att }
Просто скопируйте и вставьте это в
UICollectionViewFlowLayout
- готово.Полное рабочее решение для копирования и вставки:
Вот и все:
class TagsLayout: UICollectionViewFlowLayout { required override init() {super.init(); common()} required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder); common()} private func common() { estimatedItemSize = UICollectionViewFlowLayout.automaticSize minimumLineSpacing = 10 minimumInteritemSpacing = 10 } override func layoutAttributesForElements( in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let att = super.layoutAttributesForElements(in:rect) else {return []} var x: CGFloat = sectionInset.left var y: CGFloat = -1.0 for a in att { if a.representedElementCategory != .cell { continue } if a.frame.origin.y >= y { x = sectionInset.left } a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing y = a.frame.maxY } return att } }
И наконец...
Поблагодарите @AlexShubin выше, который первым разъяснил это!
источник
Вопрос был задан некоторое время, но ответа нет, и это хороший вопрос. Ответ - переопределить один метод в подклассе UICollectionViewFlowLayout:
@implementation MYFlowLayoutSubclass //Note, the layout's minimumInteritemSpacing (default 10.0) should not be less than this. #define ITEM_SPACING 10.0f - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect]; NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count]; CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use. //this loop assumes attributes are in IndexPath order for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) { if (attributes.frame.origin.x == self.sectionInset.left) { leftMargin = self.sectionInset.left; //will add outside loop } else { CGRect newLeftAlignedFrame = attributes.frame; newLeftAlignedFrame.origin.x = leftMargin; attributes.frame = newLeftAlignedFrame; } leftMargin += attributes.frame.size.width + ITEM_SPACING; [newAttributesForElementsInRect addObject:attributes]; } return newAttributesForElementsInRect; } @end
В соответствии с рекомендациями Apple вы получаете атрибуты макета из super и перебираете их. Если он первый в строке (определяется тем, что его origin.x находится на левом поле), вы оставляете его в покое и сбрасываете x на ноль. Затем для первой ячейки и каждой ячейки вы добавляете ширину этой ячейки плюс некоторый запас. Это передается следующему элементу цикла. Если это не первый элемент, вы устанавливаете его origin.x на текущую вычисляемую маржу и добавляете новые элементы в массив.
источник
attribute.center.x == self.collectionView?.bounds.midX
leftMargin
сif (attributes.frame.origin.x == self.sectionInset.left) {
проверкой. Это предполагает, что макет по умолчанию выровнял ячейку новой строки так, чтобы она была на одном уровне сsectionInset.left
. Если это не так, результатом будет описанное вами поведение. Я бы вставил точку останова внутри цикла и сразу перед сравнением проверил бы ячейку второй строки с обеих сторон. Удачи.У меня была такая же проблема, попробуйте Cocoapod UICollectionViewLeftAlignedLayout . Просто включите его в свой проект и инициализируйте следующим образом:
UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init]; UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
источник
Вот оригинальный ответ на Swift. В основном он по-прежнему отлично работает.
class LeftAlignedFlowLayout: UICollectionViewFlowLayout { private override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElementsInRect(rect) var leftMargin = sectionInset.left attributes?.forEach { layoutAttribute in if layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } else { layoutAttribute.frame.origin.x = leftMargin } leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing } return attributes } }
Исключение: автоматическое изменение размеров ячеек.
К сожалению, есть одно большое исключение. Если вы используете
UICollectionViewFlowLayout
«SestimatedItemSize
. ВнутриUICollectionViewFlowLayout
все немного меняется. Я не отследил его полностью, но ясно, что он вызывает другие методы после того,layoutAttributesForElementsInRect
как ячейки самостоятельно определяют размер.layoutAttributesForItemAtIndexPath
Методом проб и ошибок я обнаружил, что при автоматическом изменении размера каждая ячейка вызывается индивидуально. Это обновлениеLeftAlignedFlowLayout
отлично работает сestimatedItemSize
. Он также работает с ячейками статического размера, однако дополнительные вызовы макета заставляют меня использовать исходный ответ в любое время, когда мне не нужно автоматическое изменение размера ячеек.class LeftAlignedFlowLayout: UICollectionViewFlowLayout { private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { let layoutAttribute = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes // First in a row. if layoutAttribute?.frame.origin.x == sectionInset.left { return layoutAttribute } // We need to align it to the previous item. let previousIndexPath = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section) guard let previousLayoutAttribute = self.layoutAttributesForItemAtIndexPath(previousIndexPath) else { return layoutAttribute } layoutAttribute?.frame.origin.x = previousLayoutAttribute.frame.maxX + self.minimumInteritemSpacing return layoutAttribute } }
источник
Основываясь на ответе Майкла Сэнда , я создал подклассовую
UICollectionViewFlowLayout
библиотеку для выполнения горизонтального выравнивания влево, вправо или полного (в основном по умолчанию) горизонтального выравнивания - он также позволяет вам установить абсолютное расстояние между каждой ячейкой. Я планирую добавить к нему горизонтальное выравнивание по центру и вертикальное выравнивание.https://github.com/eroth/ERJustifiedFlowLayout
источник
Быстро. Согласно ответу Майклса
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let oldAttributes = super.layoutAttributesForElementsInRect(rect) else { return super.layoutAttributesForElementsInRect(rect) } let spacing = CGFloat(50) // REPLACE WITH WHAT SPACING YOU NEED var newAttributes = [UICollectionViewLayoutAttributes]() var leftMargin = self.sectionInset.left for attributes in oldAttributes { if (attributes.frame.origin.x == self.sectionInset.left) { leftMargin = self.sectionInset.left } else { var newLeftAlignedFrame = attributes.frame newLeftAlignedFrame.origin.x = leftMargin attributes.frame = newLeftAlignedFrame } leftMargin += attributes.frame.width + spacing newAttributes.append(attributes) } return newAttributes }
источник
Основываясь на всех ответах, я немного меняю, и мне это нравится
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in if layoutAttribute.frame.origin.y >= maxY || layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } if layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } else { layoutAttribute.frame.origin.x = leftMargin } leftMargin += layoutAttribute.frame.width maxY = max(layoutAttribute.frame.maxY, maxY) } return attributes }
источник
Если кто-то из вас столкнулся с проблемой - некоторые из ячеек справа от представления коллекции выходят за границы представления коллекции . Тогда используйте это -
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin : CGFloat = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in if Int(layoutAttribute.frame.origin.y) >= Int(maxY) { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes } }
Используйте INT вместо сравнения значений CGFloat .
источник
На основе ответов здесь, но исправлены сбои и проблемы с выравниванием, когда ваше представление коллекции также имеет верхние или нижние колонтитулы. Выравнивание только левых ячеек:
class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin = sectionInset.left var prevMaxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in guard layoutAttribute.representedElementCategory == .cell else { return } if layoutAttribute.frame.origin.y >= prevMaxY { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing prevMaxY = layoutAttribute.frame.maxY } return attributes } }
источник
Спасибо за ответ Майкла Сэнда . Я изменил его на решение из нескольких строк (одинаковое выравнивание по верху y каждой строки), которое является выравниванием по левому краю, даже с интервалом для каждого элемента.
static CGFloat const ITEM_SPACING = 10.0f; - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { CGRect contentRect = {CGPointZero, self.collectionViewContentSize}; NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:contentRect]; NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count]; CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use. NSMutableDictionary *leftMarginDictionary = [[NSMutableDictionary alloc] init]; for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) { UICollectionViewLayoutAttributes *attr = attributes.copy; CGFloat lastLeftMargin = [[leftMarginDictionary valueForKey:[[NSNumber numberWithFloat:attributes.frame.origin.y] stringValue]] floatValue]; if (lastLeftMargin == 0) lastLeftMargin = leftMargin; CGRect newLeftAlignedFrame = attr.frame; newLeftAlignedFrame.origin.x = lastLeftMargin; attr.frame = newLeftAlignedFrame; lastLeftMargin += attr.frame.size.width + ITEM_SPACING; [leftMarginDictionary setObject:@(lastLeftMargin) forKey:[[NSNumber numberWithFloat:attributes.frame.origin.y] stringValue]]; [newAttributesForElementsInRect addObject:attr]; } return newAttributesForElementsInRect; }
источник
Вот мой путь к поиску лучшего кода, который работает со Swift 5. Я присоединился к нескольким ответам из этого и некоторых других потоков, чтобы устранить предупреждения и проблемы, с которыми я столкнулся. У меня было предупреждение и некорректное поведение при прокрутке моего представления коллекции. Консоль напечатает следующее:
Еще одна проблема, с которой я столкнулся, заключается в том, что некоторые длинные ячейки обрезаются в правой части экрана. Кроме того, я устанавливал вставки раздела и minimumInteritemSpacing в функциях делегата, в результате чего значения не отражались в настраиваемом классе. Исправление заключалось в установке этих атрибутов для экземпляра макета перед его применением к моему представлению коллекции.
Вот как я использовал макет для представления моей коллекции:
let layout = LeftAlignedCollectionViewFlowLayout() layout.minimumInteritemSpacing = 5 layout.minimumLineSpacing = 7.5 layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) super.init(frame: frame, collectionViewLayout: layout)
Вот класс макета потока
class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes } var leftMargin = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in guard layoutAttribute.representedElementCategory == .cell else { return } if Int(layoutAttribute.frame.origin.y) >= Int(maxY) || layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } if layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } else { layoutAttribute.frame.origin.x = leftMargin } leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes } }
источник
Проблема с UICollectionView заключается в том, что он пытается автоматически уместить ячейки в доступной области. Я сделал это, сначала определив количество строк и столбцов, а затем определив размер ячейки для этой строки и столбца.
1) Чтобы определить разделы (строки) моего UICollectionView:
(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
2) Определить количество элементов в разделе. Вы можете определить разное количество элементов для каждого раздела. номер раздела можно узнать с помощью параметра section.
(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
3) Определить размер ячейки для каждого раздела и строки отдельно. Вы можете получить номер раздела и номер строки с помощью параметра indexPath, т.е.
[indexPath section]
для номера раздела и номера[indexPath row]
строки.(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
4) Затем вы можете отображать свои ячейки в строках и разделах, используя:
(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
ПРИМЕЧАНИЕ. В UICollectionView
источник
Ответ Майка Сэнда хорош, но у меня возникли некоторые проблемы с этим кодом (например, длинная ячейка вырезана). И новый код:
#define ITEM_SPACE 7.0f @implementation LeftAlignedCollectionViewFlowLayout - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect]; for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) { if (nil == attributes.representedElementKind) { NSIndexPath* indexPath = attributes.indexPath; attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame; } } return attributesToReturn; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath]; UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset]; if (indexPath.item == 0) { // first item of section CGRect frame = currentItemAttributes.frame; frame.origin.x = sectionInset.left; // first item of the section should always be left aligned currentItemAttributes.frame = frame; return currentItemAttributes; } NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section]; CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame; CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width + ITEM_SPACE; CGRect currentFrame = currentItemAttributes.frame; CGRect strecthedCurrentFrame = CGRectMake(0, currentFrame.origin.y, self.collectionView.frame.size.width, currentFrame.size.height); if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line // the approach here is to take the current frame, left align it to the edge of the view // then stretch it the width of the collection view, if it intersects with the previous frame then that means it // is on the same line, otherwise it is on it's own new line CGRect frame = currentItemAttributes.frame; frame.origin.x = sectionInset.left; // first item on the line should always be left aligned currentItemAttributes.frame = frame; return currentItemAttributes; } CGRect frame = currentItemAttributes.frame; frame.origin.x = previousFrameRightPoint; currentItemAttributes.frame = frame; return currentItemAttributes; }
источник
Отредактированный ответ Ангела Гарсиа Оллоки вызывает уважение
minimumInteritemSpacing
делегатаcollectionView(_:layout:minimumInteritemSpacingForSectionAt:)
, если он его реализует.override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in if layoutAttribute.frame.origin.y >= maxY { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout let spacing = delegate?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: 0) ?? minimumInteritemSpacing leftMargin += layoutAttribute.frame.width + spacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes }
источник
У меня работает приведенный выше код. Я хотел бы поделиться соответствующим кодом Swift 3.0.
class SFFlowLayout: UICollectionViewFlowLayout { let itemSpacing: CGFloat = 3.0 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attriuteElementsInRect = super.layoutAttributesForElements(in: rect) var newAttributeForElement: Array<UICollectionViewLayoutAttributes> = [] var leftMargin = self.sectionInset.left for tempAttribute in attriuteElementsInRect! { let attribute = tempAttribute if attribute.frame.origin.x == self.sectionInset.left { leftMargin = self.sectionInset.left } else { var newLeftAlignedFrame = attribute.frame newLeftAlignedFrame.origin.x = leftMargin attribute.frame = newLeftAlignedFrame } leftMargin += attribute.frame.size.width + itemSpacing newAttributeForElement.append(attribute) } return newAttributeForElement } }
источник