プロジェクトでUICollectionViewを使用していますが、1行に幅の異なる複数のセルがあります。以下によると https://developer.Apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
均等なパディングでセルを行全体に広げます。私はそれらを左揃えし、パディング幅をハードコーディングしたいことを除いて、これは予想どおりに起こります。
私はUICollectionViewFlowLayoutをサブクラス化する必要があると考えていますが、オンラインでチュートリアルなどを読んだ後、これがどのように機能するのか分からないようです。
ここにある他の解決策は、ラインが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;
}
この質問への回答には、多くの素晴らしいアイデアが含まれています。ただし、それらのほとんどにはいくつかの欠点があります。
ほとんどのソリューションは、layoutAttributesForElements(in rect: CGRect)
関数のみをオーバーライドします。これらはlayoutAttributesForItem(at indexPath: IndexPath)
.をオーバーライドしません。これは、コレクションビューが特定のインデックスパスのレイアウト属性を取得するために定期的に後者の関数を呼び出すため、問題です。その関数から適切な属性を返さない場合、あらゆる種類の視覚的なバグに遭遇する可能性があります。セルの挿入および削除アニメーション中、またはコレクションビューレイアウトのestimatedItemSize
を設定してセルのサイズを変更する場合。 Apple docs 状態:
すべてのカスタムレイアウトオブジェクトは_
layoutAttributesForItemAtIndexPath:
_メソッドを実装する必要があります。
多くのソリューションは、layoutAttributesForElements(in rect: CGRect)
関数に渡されるrect
パラメーターについても想定しています。たとえば、多くの場合、rect
は常に新しい行の先頭から始まるという前提に基づいていますが、必ずしもそうではありません。
言い換えれば:
これらの問題に対処するために、 matt および Chris Wagnerによって提案されたのと同様の考え方に従うUICollectionViewFlowLayout
サブクラスを作成しました 同様の質問に対する回答。セルを整列させることができます
⬅︎ left:
または ➡︎ right:
さらに、verticallyを使用して、それぞれの行のセルを整列するオプションを提供します(高さが異なる場合)。
ここから簡単にダウンロードできます。
使用方法は簡単で、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
_値になります。
これで、行の先頭に到達したことを知るだけで済み、前の行のセルの隣にセルを配置しません。 (これは、間違ったレイアウトになるだけでなく、非常に遅れます。)したがって、再帰アンカーが必要です。再帰アンカーを見つけるために使用するアプローチは次のとおりです。
_ +---------+----------------------------------------------------------------+---------+
| | | |
| | +------------+ | |
| | | | | |
| 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で書いた最も長い答えだと思います。これがお役に立てば幸いです。 ????
Swift 4.1およびiOS 11では、ニーズに応じて、問題を修正するために2以下の完全な実装のいずれかを選択できます。
UICollectionViewCell
s以下の実装は、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")
}
}
_
期待される結果:
UICollectionViewCell
sを固定サイズで左揃え以下の実装は、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")
}
}
_
期待される結果:
質問はしばらく上がっていますが、答えはありません。良い質問です。答えは、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に実行中の計算されたマージンを設定し、新しい要素を配列に追加します。
同じ問題がありました。Cocoapod ICollectionViewLeftAlignedLayout を試してください。それをプロジェクトに含めて、次のように初期化します。
UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
Michael Sand's answer に基づいて、サブクラス化されたUICollectionViewFlowLayout
ライブラリを作成して、左、右、または完全(基本的にはデフォルト)の水平方向の位置合わせを行います。細胞。水平方向の中央揃えと垂直方向の揃えも追加する予定です。
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
}
これが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つの大きな例外があります。 UICollectionViewFlowLayout
の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
}
}
すべての答えに基づいて、私は少し変更し、それは私のためにうまく機能します
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
}
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;
}
ここでの回答に基づきますが、コレクションビューにヘッダーまたはフッターも含まれている場合のクラッシュと位置合わせの問題を修正しました。左のみのセルの整列:
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
}
}
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
}
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
マイクサンドの答えは良いのですが、このコードでいくつかの問題が発生しました(長いセルが切り取られているように)。そして新しいコード:
#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;
}
上記のコードは私のために機能します。それぞれの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
}
}