Вопрос по uicollectionview, uicollectionviewlayout, objective-c, ios – Ячейки выравнивания по левому краю в UICollectionView

63

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

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

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

Ваш Ответ

15   ответов
26

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

Решения, которые не проверяют клеткиy значениеработать только для однострочных макетов, Они терпят неудачу для макетов представления коллекции с несколькими строками.Решения, которыеделать проверитьy значение какОтвет Анхели Гарсии Оллоки работает только если все клетки имеют одинаковую высоту, Они не подходят для клеток с переменной высотой.

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

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

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

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

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

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

⬅︎ оставил:

или же➡︎ право:

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

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

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
}

(Thecopy() Функция просто создает полную копию всех атрибутов макета в массиве. Вы можете посмотреть висходный код для его реализации.)

Так что теперь единственное, что нам нужно сделать, это реализоватьlayoutAttributesForItem(at indexPath: IndexPath) работает правильно. Супер классUICollectionViewFlowLayout уже помещает правильное количество ячеек в каждой строке, поэтому нам нужно только сместить их влево в пределах их соответствующего ряда. Трудность заключается в том, чтобы вычислить количество места, которое нам нужно, чтобы сместить каждую ячейку влево.

Поскольку мы хотим иметь фиксированный интервал между ячейками, основная идея состоит в том, чтобы просто предположить, что предыдущая ячейка (ячейка слева от ячейки, которая в данный момент расположена) уже правильно размещена. Тогда нам нужно только добавить расстояние между ячейками кmaxX значение кадра предыдущей ячейки, и этоorigin.x значение для кадра текущей ячейки.

Теперь нам нужно знать только, когда мы достигли начала строки, чтобы мы не выравнивали ячейку рядом с ячейкой в ​​предыдущей строке. (Это приведет не только к неправильной компоновке, но и к запаздыванию.) Поэтому нам нужно иметь привязку рекурсии. Подход, который я использую для нахождения этой рекурсивной привязки, заключается в следующем:

Чтобы узнать, если ячейка по индексуi находится в той же строке, что и ячейка с индексомя-1 ...
 +---------+----------------------------------------------------------------+---------+
 |         |                                                                |         |
 |         |     +------------+                                             |         |
 |         |     |            |                                             |         |
 | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
 |  inset  |     |intersection|        |                     |   line rect  |  inset  |
 |         |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -|         |
 | (left)  |     |            |             current item                    | (right) |
 |         |     +------------+                                             |         |
 |         |     previous item                                              |         |
 +---------+----------------------------------------------------------------+---------+

... Я "рисую" прямоугольник вокруг текущей ячейки и растягиваю его по ширине всего представления коллекции. КакUICollectionViewFlowLayout центрирует все ячейки вертикально, каждую ячейку в одной линиидолжен пересекаются с этим прямоугольником.

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

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

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

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

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

ваша библиотека отлично работает в моем приложении. Спасибо! Tonny Xu
Уронил и сразу работает! Спасибо! Chris Prince
Работает отлично! Спасибо Rotem
Я применяю с этим сценариемstackoverflow.com/questions/56349052/...  но это не работает не могли бы вы сказать мне, как я могу достичь этого Nazmul Hasan
13

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

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

Реализация ниже показывает, как использоватьUICollectionViewLayout«slayoutAttributesForElements(in:), UICollectionViewFlowLayout«sestimatedItemSize а такжеUILabel«spreferredMaxLayoutWidth чтобы выровнять по левому краю ячейки авторазмера в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")
    }

}

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

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

}

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

Просто, понятно и работает довольно круто! Спасибо gpichler
Работает как шарм. Matias Jurfest
10

дай кокаподUICollectionViewLeftAlignedLayout попытка Просто включите его в свой проект и инициализируйте так:

UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
спасибо - исправил mklb
Отсутствует открывающая квадратная скобка в вашей инициализации макета там RyanOfCourse
Я проверил два ответаgithub.com/eroth/ERJustifiedFlowLayout и UICollectionViewLeftAlignedLayout. Только второй из них дал правильный результат при использованиивычисленный размер (self-size). EckhardN
5

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

https://github.com/eroth/ERJustifiedFlowLayout

1

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

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

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

что он пытается автоматически подогнать ячейки в доступной области. Я сделал это, сначала определив количество строк и столбцов, а затем определив размер ячейки для этой строки и столбца.

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
105

когда линия состоит только из 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;
}
Если бы я мог, я бы отметил это как ответ, по крайней мере, для Свифта. Он делает то, что говорит вопрос, не доставляя слишком много хлопот. Fernando Mata
Для тех, кто, возможно, наткнулся на это, ища выровненную по центру версию, как я, я разместил здесь измененную версию ответа Ангела:stackoverflow.com/a/38254368/2845237 Alex Koshy
Спасибо! Свифт хорошо работает для меня. meow2x
@ Angle, мы сталкиваемся с проблемой в дополнительном представлении в устройстве iphone X. дополнительные не отображаются, если мы используем вышеуказанное решение. Пожалуйста помоги chirag shah
5

Быстро. Согласно Майклу ответ

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
}
Работает как волшебство! Marin
Работает как шарм! Sebastian Krogull
0

твующим кодом 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
    }
}
20

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

Не похоже, что это работает, если в строке есть один элемент, потому что UICollectionViewFlowLayout центрирует его. Вы можете проверить центр, чтобы решить проблему. Что-то вродеattribute.center.x == self.collectionView?.bounds.midX Ryan Poolos
Похоже, это порвать сestimatedItemSize установить на макете. Смотрите ответ ниже, если вам нужно автоматически изменять размер ячеек. Ryan Poolos
@NicolasMiari Цикл решает, что это новая строка и сбрасываетleftMargin сif (attributes.frame.origin.x == self.sectionInset.left) { проверять. Это предполагает, что компоновка по умолчанию выровняла ячейку новой строки, чтобы быть вровень сsectionInset.left, Если это не так, то поведение, которое вы описываете, будет результатом. Я бы вставил точку останова внутри цикла и проверил обе стороны на наличие ячейки второго ряда непосредственно перед сравнением. Удачи. Mike Sand
Я думаю, что это намного лучше, чем решение Криса Вагнера (stackoverflow.com/a/15554667/482557) по связанному вопросу - ваша реализация имеет ноль повторяющихся вызовов UICollectionViewLayout. Evan R
3

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

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

Отредактированный ответ 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
}
4

ном.

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
    }
}
Это работает для первого раздела, однако размер метки не изменяется во втором разделе, пока таблица не прокручивается. Мое представление коллекции находится в ячейке таблицы. EI Captain v2.0
0

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

#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;
}

Похожие вопросы