it-swarm.com.ru

Ячейки выравнивания по левому краю в UICollectionView

Я использую UICollectionView в моем проекте, где есть несколько ячеек разной ширины на линии. Согласно: https://developer.Apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

он распределяет ячейки по линии с одинаковым заполнением. Это происходит, как и ожидалось, за исключением того, что я хочу выровнять их по левому краю и жестко закодировать ширину отступа.

Я полагаю, что мне нужно создать подкласс UICollectionViewFlowLayout, однако, прочитав некоторые учебные пособия и т.д. В Интернете, я просто не понимаю, как это работает.

62
Damien

Другие решения здесь не работают должным образом, когда строка состоит только из 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
}

Objective-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;
}
105
Angel G. Olloqui

Есть много прекрасных идей, включенных в ответы на этот вопрос. Однако большинство из них имеют некоторые недостатки:

  • Решения, которые не проверяют значение ячейки y, работают только для однострочных макетов . Они терпят неудачу для макетов представления коллекции с несколькими строками.
  • Решения, которые do проверяют значение y, например ответ Angel García Olloqui, работают, только если все ячейки имеют одинаковые высота . Они не подходят для клеток с переменной высотой.
  • Большинство решений переопределяют только функцию layoutAttributesForElements(in rect: CGRect). Они не переопределяют layoutAttributesForItem(at indexPath: IndexPath). Это проблема, потому что представление коллекции периодически вызывает последнюю функцию для получения атрибутов макета для определенного пути индекса. Если вы не вернете надлежащие атрибуты из этой функции, вы, скорее всего, столкнетесь со всевозможными визуальными ошибками, например, во время вставки и удаления анимаций ячеек или при использовании ячеек с саморазмером путем установки estimatedItemSize макета представления коллекции. Состояние Apple docs :

    Предполагается, что каждый объект пользовательского макета реализует метод layoutAttributesForItemAtIndexPath:.

  • Многие решения также делают предположения о параметре rect, который передается в функцию layoutAttributesForElements(in rect: CGRect). Например, многие из них основаны на предположении, что rect всегда начинается в начале новой строки, что не всегда так.

Итак, другими словами:

Большинство решений, предлагаемых на этой странице, работают для некоторых конкретных приложений, но они не работают должным образом в каждой ситуации.


AlignedCollectionViewFlowLayout

Для решения этих проблем я создал подкласс UICollectionViewFlowLayout, который следует аналогичной идее, предложенной matt и Chris Wagner в своих ответах на аналогичный вопрос. Это может либо выровнять клетки

⬅︎  левый :

Left-aligned layout

или же ➡︎  правый :

Right-aligned layout

и дополнительно предлагает варианты вертикального выравнивания ячеек в их соответствующих строках (в случае, если они различаются по высоте).

Вы можете просто скачать его здесь:

https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout

Использование простое и объяснено в файле 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 центрирует все ячейки вертикально, каждая ячейка в одной строке must ​​пересекается с этим прямоугольником.

Таким образом, я просто проверяю, пересекается ли ячейка с индексом i-1 с прямоугольником этой линии, созданным из ячейки с индексом i.

  • Если оно пересекается, ячейка с индексом i не является самой левой ячейкой в ​​строке.
    → Получить кадр предыдущей ячейки (с индексом i − 1) и переместить текущую ячейку рядом с ней.

  • Если он не пересекается, ячейка с индексом i является самой левой ячейкой в ​​строке.
    → Переместите ячейку к левому краю вида сбора (не изменяя его вертикальное положение).

Я не буду публиковать здесь фактическую реализацию функции layoutAttributesForItem(at indexPath: IndexPath), потому что я думаю, что наиболее важной частью является понимание idea, и вы всегда можете проверить мою реализацию в исходный код , (Это немного сложнее, чем объяснено здесь, потому что я также разрешаю выравнивание .right и различные варианты вертикального выравнивания. Однако, это следует той же идее.)


Ух ты, я думаю, это самый длинный ответ, который я когда-либо писал на Stackoverflow. Надеюсь, это поможет. ????

36
Mischa

Вопрос был давно, но ответа нет, и это хороший вопрос. Ответ заключается в переопределении одного метода в подклассе 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 на текущее вычисленное поле и добавляете новые элементы в массив. 

20
Mike Sand

В Swift 4.1 и iOS 11, в соответствии с вашими потребностями, вы можете выбрать одну из 2 после полных реализаций, чтобы решить вашу проблему.


# 1. Выравнивание по левому краю авторазмера UICollectionViewCells

Реализация ниже показывает, как использовать UICollectionViewLayout 's layoutAttributesForElements(in:) , UICollectionViewFlowLayout' s estimatedItemSize и UILabel 's preferredMaxLayoutWidth для выравнивания по левому краю ячеек автоматического изменения размера в UICollectionView:

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")
    }

}

Ожидаемый результат:

 enter image description here


# 2. Выровнять по левому краю UICollectionViewCells с фиксированным размером

В приведенной ниже реализации показано, как использовать UICollectionViewLayout 's layoutAttributesForElements(in:) и UICollectionViewFlowLayout' s itemSize для выравнивания ячеек слева с предварительно определенным размером в 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")
    }

}

Ожидаемый результат:

 enter image description here

13
Imanou Petit

У меня была такая же проблема, Дайте Cocoapod UICollectionViewLeftAlignedLayout попробовать. Просто включите его в свой проект и инициализируйте так:

UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
10
mklb

Основываясь на ответе Майкла Сэнда , я создал подклассную библиотеку UICollectionViewFlowLayout для выполнения выравнивания по горизонтали влево, вправо или полностью (в основном по умолчанию) - оно также позволяет вам установить абсолютное расстояние между каждой ячейкой. Я также планирую добавить горизонтальное выравнивание по центру и вертикальное выравнивание.

https://github.com/eroth/ERJustifiedFlowLayout

5
Evan R

В Свифте. Согласно Майклу ответ

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
}
5
GregP

Вот оригинальный ответ в 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's estimatedItemSize. Внутренне 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
    }
}
4
Ryan Poolos

Основываясь на всех ответах, я немного меняюсь, и это хорошо для меня

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
}
3
kotvaska

Спасибо за ответ Майкла Сэнда . Я изменил его для решения нескольких строк (одинаковое выравнивание Top 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;
}
1
Shu Zhang

Отредактировал ответ Angel García Olloqui об уважении 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
}
0
Radek

Проблема с 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

Section == Row
IndexPath.Row == Column
0
Anas iqbal

Ответ Майка Сэнда хорош, но у меня возникли некоторые проблемы с этим кодом (например, отрезана длинная ячейка). И новый код:

#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;
}
0
Lal Krishna

Приведенный выше код работает для меня. Я хотел бы поделиться соответствующим кодом 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
    }
}
0
Naresh

На основе ответов здесь, но исправлены сбои и проблемы с выравниванием, когда представление вашей коллекции также получает верхние или нижние колонтитулы. Выравнивание оставленных только ячеек:

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
    }
}
0
Alex Shubin