web-dev-qa-db-ja.com

UICollectionViewでセルを左揃え

プロジェクトでUICollectionViewを使用していますが、1行に幅の異なる複数のセルがあります。以下によると https://developer.Apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

均等なパディングでセルを行全体に広げます。私はそれらを左揃えし、パディング幅をハードコーディングしたいことを除いて、これは予想どおりに起こります。

私はUICollectionViewFlowLayoutをサブクラス化する必要があると考えていますが、オンラインでチュートリアルなどを読んだ後、これがどのように機能するのか分からないようです。

74
Damien

ここにある他の解決策は、ラインが1つのアイテムのみで構成されている場合、または複雑すぎる場合に適切に機能しません。

Ryanの例に基づいて、新しい要素のY位置を調べて新しい行を検出するようにコードを変更しました。非常にシンプルで迅速なパフォーマンス。

迅速:

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

この質問への回答には、多くの素晴らしいアイデアが含まれています。ただし、それらのほとんどにはいくつかの欠点があります。

  • セルのyをチェックしないソリューションは、単一行のレイアウトでのみ動作します。複数行のコレクションビューレイアウトでは失敗します。
  • doを確認するソリューションyAngelGarcíaOlloqui's answerのような値すべてのセルの高さが同じ場合のみ機能します。高さが可変のセルでは失敗します。
  • ほとんどのソリューションは、layoutAttributesForElements(in rect: CGRect)関数のみをオーバーライドします。これらはlayoutAttributesForItem(at indexPath: IndexPath).をオーバーライドしません。これは、コレクションビューが特定のインデックスパスのレイアウト属性を取得するために定期的に後者の関数を呼び出すため、問題です。その関数から適切な属性を返さない場合、あらゆる種類の視覚的なバグに遭遇する可能性があります。セルの挿入および削除アニメーション中、またはコレクションビューレイアウトのestimatedItemSizeを設定してセルのサイズを変更する場合。 Apple docs 状態:

    すべてのカスタムレイアウトオブジェクトは_layoutAttributesForItemAtIndexPath:_メソッドを実装する必要があります。

  • 多くのソリューションは、layoutAttributesForElements(in rect: CGRect)関数に渡されるrectパラメーターについても想定しています。たとえば、多くの場合、rectは常に新しい行の先頭から始まるという前提に基づいていますが、必ずしもそうではありません。

言い換えれば:

このページで提案されているソリューションのほとんどは、特定のアプリケーションで機能しますが、すべての状況で期待どおりに機能するわけではありません。


AlignedCollectionViewFlowLayout

これらの問題に対処するために、 matt および Chris Wagnerによって提案されたのと同様の考え方に従うUICollectionViewFlowLayoutサブクラスを作成しました 同様の質問に対する回答。セルを整列させることができます

⬅︎ left

 Left-aligned layout 

または ➡︎ right

 Right-aligned layout 

さらに、verticallyを使用して、それぞれの行のセルを整列するオプションを提供します(高さが異なる場合)。

ここから簡単にダウンロードできます。

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

使用方法は簡単で、READMEファイルで説明されています。基本的にAlignedCollectionViewFlowLayoutのインスタンスを作成し、目的の配置を指定して、それをコレクションビューのcollectionViewLayoutプロパティ:

_ let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, 
                                                         verticalAlignment: .top)

 yourCollectionView.collectionViewLayout = alignedFlowLayout
_

Cocoapods でも利用可能です。)


仕組み(左揃えのセルの場合):

ここでの概念は、solelyをlayoutAttributesForItem(at indexPath: IndexPath) functionに依存することです。 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で書いた最も長い答えだと思います。これがお役に立てば幸いです。 ????

39
Mischa

Swift 4.1およびiOS 11では、ニーズに応じて、問題を修正するために2以下の完全な実装のいずれかを選択できます。


#1。左揃え自動サイズ変更UICollectionViewCells

以下の実装は、UICollectionViewLayoutの-​​ layoutAttributesForElements(in:)UICollectionViewFlowLayoutの-​​ estimatedItemSize およびUILabelの-​​ 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の-​​ layoutAttributesForElements(in:) およびUICollectionViewFlowLayoutの-​​ 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

21
Imanou Petit

質問はしばらく上がっていますが、答えはありません。良い質問です。答えは、UICollectionViewFlowLayoutサブクラスの1つのメソッドをオーバーライドすることです。

@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が推奨するように、スーパーからレイアウト属性を取得し、それらを繰り返し処理します。行の最初の場合(Origin.xが左マージンにあることで定義されている場合)、そのままにしてxをゼロにリセットします。次に、最初のセルとすべてのセルに対して、そのセルの幅とマージンを追加します。これは、ループ内の次のアイテムに渡されます。最初のアイテムでない場合は、Origin.xに実行中の計算されたマージンを設定し、新しい要素を配列に追加します。

20
Mike Sand

同じ問題がありました。Cocoapod ICollectionViewLeftAlignedLayout を試してください。それをプロジェクトに含めて、次のように初期化します。

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

Michael Sand's answer に基づいて、サブクラス化されたUICollectionViewFlowLayoutライブラリを作成して、左、右、または完全(基本的にはデフォルト)の水平方向の位置合わせを行います。細胞。水平方向の中央揃えと垂直方向の揃えも追加する予定です。

https://github.com/eroth/ERJustifiedFlowLayout

5
Evan R

Swiftで。マイケルズの回答によると

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

例外:セルの自動サイズ設定

悲しいことに1つの大きな例外があります。 UICollectionViewFlowLayoutestimatedItemSizeを使用している場合。内部的に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
    }
}
5
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

Michael Sand's answer に感謝します。複数の行(各行の同じ配置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

ここでの回答に基づきますが、コレクションビューにヘッダーまたはフッターも含まれている場合のクラッシュと位置合わせの問題を修正しました。左のみのセルの整列:

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

AngelGarcíaOlloquiの回答を編集し、デリゲートのcollectionView(_:layout:minimumInteritemSpacingForSectionAt:)を実装する場合、minimumInteritemSpacingを尊重します。

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)セクション内のアイテムの数を定義する。セクションごとに異なる数のアイテムを定義できます。 「セクション」パラメーターを使用してセクション番号を取得できます。

(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