Saya menggunakan UICollectionView dalam proyek saya, di mana ada beberapa sel dengan lebar berbeda pada satu baris. Menurut: https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
itu menyebarkan sel ke luar garis dengan padding yang sama. Ini terjadi seperti yang diharapkan, kecuali saya ingin kiri membenarkan mereka, dan kode keras lebar padding.
Saya pikir saya perlu membuat subclass UICollectionViewFlowLayout, namun setelah membaca beberapa tutorial dll secara online, saya sepertinya tidak mengerti cara kerjanya.
Jawaban:
Solusi lain di utas ini tidak berfungsi dengan baik, jika garis hanya terdiri dari 1 item atau terlalu rumit.
Berdasarkan contoh yang diberikan oleh Ryan, saya mengubah kode untuk mendeteksi baris baru dengan memeriksa posisi Y elemen baru. Sangat sederhana dan cepat dalam performa.
Cepat:
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 } }
Jika Anda ingin tampilan tambahan mempertahankan ukurannya, tambahkan berikut ini di bagian atas closure dalam
forEach
panggilan: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; }
sumber
let attributes = super.layoutAttributesForElementsInRect(rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
Ada banyak ide hebat yang disertakan dalam jawaban atas pertanyaan ini. Namun, kebanyakan dari mereka memiliki beberapa kekurangan:
Sebagian besar solusi hanya mengesampingkan
layoutAttributesForElements(in rect: CGRect)
fungsi. Mereka tidak menimpalayoutAttributesForItem(at indexPath: IndexPath)
. Ini menjadi masalah karena tampilan koleksi secara berkala memanggil fungsi terakhir untuk mengambil atribut tata letak untuk jalur indeks tertentu. Jika Anda tidak mengembalikan atribut yang sesuai dari fungsi itu, Anda kemungkinan akan mengalami semua jenis bug visual, misalnya selama penyisipan dan penghapusan animasi sel atau saat menggunakan sel yang mengukur sendiri dengan menyetel tata letak tampilan koleksiestimatedItemSize
. The Apel docs negara:Banyak solusi juga membuat asumsi tentang
rect
parameter yang diteruskan kelayoutAttributesForElements(in rect: CGRect)
fungsi. Misalnya, banyak yang didasarkan pada asumsi bahwarect
selalu dimulai di awal baris baru yang belum tentu demikian.Jadi dengan kata lain:
Sebagian besar solusi yang disarankan di halaman ini berfungsi untuk beberapa aplikasi tertentu, tetapi tidak berfungsi seperti yang diharapkan dalam setiap situasi.
AlignedCollectionViewFlowLayout
Untuk mengatasi masalah ini, saya telah membuat
UICollectionViewFlowLayout
subclass yang mengikuti ide serupa seperti yang disarankan oleh matt dan Chris Wagner dalam jawaban mereka untuk pertanyaan serupa. Itu bisa menyelaraskan sel⬅︎ kiri :
atau ➡︎ kanan :
dan juga menawarkan opsi untuk menyelaraskan secara vertikal sel di barisnya masing-masing (jika tingginya bervariasi).
Anda cukup mendownloadnya di sini:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
Penggunaannya langsung dan dijelaskan di file README. Pada dasarnya Anda membuat sebuah instance
AlignedCollectionViewFlowLayout
, menentukan perataan yang diinginkan dan menetapkannya kecollectionViewLayout
properti tampilan koleksi Anda :(Ini juga tersedia di Cocoapods .)
Cara kerjanya (untuk sel rata kiri):
Konsepnya di sini hanya
layoutAttributesForItem(at indexPath: IndexPath)
mengandalkan fungsi . Di dalamlayoutAttributesForElements(in rect: CGRect)
kita cukup mendapatkan jalur indeks dari semua sel di dalamrect
dan kemudian memanggil fungsi pertama untuk setiap jalur indeks untuk mengambil bingkai yang benar: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()
Fungsi ini hanya membuat salinan dalam dari semua atribut tata letak dalam larik. Anda dapat melihat kode sumber untuk implementasinya.)Jadi sekarang satu-satunya hal yang harus kita lakukan adalah mengimplementasikan
layoutAttributesForItem(at indexPath: IndexPath)
fungsi dengan benar. Kelas superUICollectionViewFlowLayout
sudah menempatkan jumlah sel yang benar di setiap baris jadi kita hanya perlu menggesernya ke kiri dalam baris masing-masing. Kesulitannya terletak pada menghitung jumlah ruang yang kita butuhkan untuk menggeser setiap sel ke kiri.Karena kita ingin memiliki jarak tetap antara sel, ide intinya adalah dengan menganggap bahwa sel sebelumnya (sel kiri sel yang saat ini diletakkan) sudah diposisikan dengan benar. Kemudian kita hanya perlu menambahkan jarak sel ke
maxX
nilai bingkai sel sebelumnya dan itulahorigin.x
nilai untuk bingkai sel saat ini.Sekarang kita hanya perlu mengetahui kapan kita telah mencapai awal baris, sehingga kita tidak menyelaraskan sel di sebelah sel pada baris sebelumnya. (Ini tidak hanya akan menghasilkan tata letak yang salah tetapi juga akan sangat lamban.) Jadi kita perlu memiliki jangkar rekursi. Pendekatan yang saya gunakan untuk menemukan jangkar rekursi adalah sebagai berikut:
Untuk mengetahui apakah sel pada indeks i berada pada baris yang sama dengan sel dengan indeks i-1 ...
... Saya "menggambar" persegi panjang di sekitar sel saat ini dan merentangkannya di atas lebar tampilan keseluruhan koleksi. Sebagai
UICollectionViewFlowLayout
pusat, semua sel secara vertikal, setiap sel dalam garis yang sama harus berpotongan dengan persegi panjang ini.Jadi, saya cukup memeriksa apakah sel dengan indeks i-1 berpotongan dengan persegi panjang garis ini yang dibuat dari sel dengan indeks i .
Jika berpotongan, sel dengan indeks i bukanlah sel paling kiri dalam baris.
→ Dapatkan bingkai sel sebelumnya (dengan indeks i − 1 ) dan pindahkan sel saat ini ke sebelahnya.
Jika tidak berpotongan, sel dengan indeks i adalah sel paling kiri dalam baris.
→ Pindahkan sel ke tepi kiri tampilan koleksi (tanpa mengubah posisi vertikalnya).
Saya tidak akan memposting implementasi sebenarnya dari
layoutAttributesForItem(at indexPath: IndexPath)
fungsi di sini karena menurut saya bagian yang paling penting adalah memahami idenya dan Anda selalu dapat memeriksa penerapan saya di kode sumber . (Ini sedikit lebih rumit daripada yang dijelaskan di sini karena saya juga mengizinkan.right
perataan dan berbagai opsi perataan vertikal. Namun, ini mengikuti ide yang sama.)Wow, saya rasa ini adalah jawaban terpanjang yang pernah saya tulis di Stackoverflow. Saya harap ini membantu. 😉
sumber
Dengan Swift 4.1 dan iOS 11, sesuai kebutuhan Anda, Anda dapat memilih salah satu dari 2 implementasi lengkap berikut untuk memperbaiki masalah Anda.
# 1. Meninggalkan autoresizing menyelaraskan
UICollectionViewCell
sPelaksanaan di bawah ini menunjukkan bagaimana menggunakan
UICollectionViewLayout
'slayoutAttributesForElements(in:)
,UICollectionViewFlowLayout
' sestimatedItemSize
danUILabel
'spreferredMaxLayoutWidth
untuk sel menyelaraskan autoresizing kiri dalamUICollectionView
: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") } }
Hasil yang diharapkan:
# 2. Rata kiri
UICollectionViewCell
dengan ukuran tetapImplementasi di bawah ini menunjukkan bagaimana menggunakan
UICollectionViewLayout
'slayoutAttributesForElements(in:)
danUICollectionViewFlowLayout
'itemSize
untuk menyelaraskan kiri sel dengan ukuran yang telah ditentukan dalamUICollectionView
: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") } }
Hasil yang diharapkan:
sumber
10
nomor arbitrer?Solusi sederhana di tahun 2019
Ini adalah salah satu pertanyaan menyedihkan di mana banyak hal telah berubah selama bertahun-tahun. Sekarang mudah.
Pada dasarnya Anda hanya melakukan ini:
// as you move across one row ... a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing // and, obviously start fresh again each row
Yang Anda butuhkan sekarang adalah kode boilerplate:
override func layoutAttributesForElements( in rect: CGRect)->[UICollectionViewLayoutAttributes]? { guard let att = super.layoutAttributesForElements(in: rect) else { return [] } var x: CGFloat = sectionInset.left var y: CGFloat = -1.0 for a in att { if a.representedElementCategory != .cell { continue } if a.frame.origin.y >= y { x = sectionInset.left } a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing y = a.frame.maxY } return att }
Cukup salin dan tempel ke dalam
UICollectionViewFlowLayout
- Anda sudah selesai.Solusi kerja lengkap untuk menyalin dan menempel:
Ini semuanya:
class TagsLayout: UICollectionViewFlowLayout { required override init() {super.init(); common()} required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder); common()} private func common() { estimatedItemSize = UICollectionViewFlowLayout.automaticSize minimumLineSpacing = 10 minimumInteritemSpacing = 10 } override func layoutAttributesForElements( in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let att = super.layoutAttributesForElements(in:rect) else {return []} var x: CGFloat = sectionInset.left var y: CGFloat = -1.0 for a in att { if a.representedElementCategory != .cell { continue } if a.frame.origin.y >= y { x = sectionInset.left } a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing y = a.frame.maxY } return att } }
Dan akhirnya...
Terima kasih kepada @AlexShubin di atas yang pertama kali mengklarifikasi ini!
sumber
Pertanyaannya sudah lama tapi tidak ada jawaban dan itu pertanyaan yang bagus. Jawabannya adalah dengan mengganti satu metode dalam subkelas 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
Seperti yang direkomendasikan oleh Apple, Anda mendapatkan atribut tata letak dari super dan mengulanginya. Jika itu yang pertama di baris (ditentukan oleh origin.x berada di margin kiri), Anda membiarkannya dan mengatur ulang x ke nol. Kemudian untuk sel pertama dan setiap sel, Anda menambahkan lebar sel tersebut ditambah beberapa margin. Ini diteruskan ke item berikutnya dalam loop. Jika ini bukan item pertama, Anda menyetelnya origin.x ke margin terhitung yang berjalan, dan menambahkan elemen baru ke larik.
sumber
attribute.center.x == self.collectionView?.bounds.midX
leftMargin
denganif (attributes.frame.origin.x == self.sectionInset.left) {
cek. Ini mengasumsikan bahwa tata letak default telah menyelaraskan sel baris baru menjadi rata dengansectionInset.left
. Jika tidak demikian, perilaku yang Anda gambarkan akan terjadi. Saya akan memasukkan breakpoint di dalam loop dan memeriksa kedua sisi sel baris kedua tepat sebelum perbandingan. Semoga berhasil.Saya memiliki masalah yang sama, Cobalah Cocoapod UICollectionViewLeftAlignedLayout . Cukup sertakan dalam proyek Anda dan lakukan inisialisasi seperti ini:
UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init]; UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
sumber
Inilah jawaban asli di Swift. Sebagian besar masih berfungsi dengan baik.
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 } }
Pengecualian: Mengotomatiskan Sel
Sayangnya, ada satu pengecualian besar. Jika Anda menggunakan
UICollectionViewFlowLayout
'sestimatedItemSize
. Secara internalUICollectionViewFlowLayout
mengubah sedikit. Saya belum melacaknya sepenuhnya tetapi jelas memanggil metode lain setelahlayoutAttributesForElementsInRect
saat mengukur sel sendiri. Dari trial and error saya, saya menemukan tampaknya memanggillayoutAttributesForItemAtIndexPath
setiap sel secara individual selama autosizing lebih sering. Pembaruan iniLeftAlignedFlowLayout
berfungsi baik denganestimatedItemSize
. Ini berfungsi dengan sel berukuran statis juga, namun panggilan tata letak tambahan mengarahkan saya untuk menggunakan jawaban asli kapan saja saya tidak memerlukan sel ukuran otomatis.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 } }
sumber
Berdasarkan jawaban Michael Sand , saya membuat
UICollectionViewFlowLayout
perpustakaan subclass untuk melakukan justifikasi horizontal kiri, kanan, atau penuh (pada dasarnya default) —ini juga memungkinkan Anda menyetel jarak absolut antara setiap sel. Saya berencana menambahkan justifikasi tengah horizontal dan justifikasi vertikal juga.https://github.com/eroth/ERJustifiedFlowLayout
sumber
Dengan cepat. Menurut jawaban Michaels
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 }
sumber
Berdasarkan semua jawaban, saya berubah sedikit dan itu bekerja dengan baik untuk saya
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 }
sumber
Jika ada di antara Anda yang menghadapi masalah - beberapa sel di sebelah kanan tampilan koleksi melebihi batas tampilan koleksi . Kalau begitu tolong gunakan ini -
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin : CGFloat = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in if Int(layoutAttribute.frame.origin.y) >= Int(maxY) { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes } }
Gunakan INT sebagai ganti membandingkan nilai CGFloat .
sumber
Berdasarkan jawaban di sini, tetapi perbaiki masalah mogok dan penyelarasan saat tampilan koleksi Anda juga memiliki header atau footer. Menyejajarkan sel hanya kiri:
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 } }
sumber
Terima kasih atas jawaban Michael Sand . Saya memodifikasinya menjadi solusi beberapa baris (perataan yang sama Atas y dari setiap baris) yaitu Left Alignment, bahkan memberi jarak ke setiap item.
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; }
sumber
Inilah perjalanan saya untuk menemukan kode terbaik yang bekerja dengan Swift 5. Saya telah bergabung dengan beberapa jawaban dari utas ini dan beberapa utas lainnya untuk memecahkan peringatan dan masalah yang saya hadapi. Saya mendapat peringatan dan beberapa perilaku tidak normal saat menggulir melalui tampilan koleksi saya. Konsol mencetak yang berikut ini:
Masalah lain yang saya hadapi adalah beberapa sel yang panjang dipotong di sisi kanan layar. Juga, saya mengatur insets bagian dan minimumInteritemSpacing di fungsi delegasi yang menghasilkan nilai tidak tercermin di kelas kustom. Perbaikan untuk itu adalah menyetel atribut tersebut ke instance tata letak sebelum menerapkannya ke tampilan koleksi saya.
Inilah cara saya menggunakan tata letak untuk tampilan koleksi saya:
let layout = LeftAlignedCollectionViewFlowLayout() layout.minimumInteritemSpacing = 5 layout.minimumLineSpacing = 7.5 layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) super.init(frame: frame, collectionViewLayout: layout)
Berikut kelas tata letak aliran
class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes } var leftMargin = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in guard layoutAttribute.representedElementCategory == .cell else { return } if Int(layoutAttribute.frame.origin.y) >= Int(maxY) || layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } if layoutAttribute.frame.origin.x == sectionInset.left { leftMargin = sectionInset.left } else { layoutAttribute.frame.origin.x = leftMargin } leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes } }
sumber
Masalah dengan UICollectionView adalah mencoba menyesuaikan sel secara otomatis di area yang tersedia. Saya telah melakukan ini dengan terlebih dahulu menentukan jumlah baris dan kolom dan kemudian menentukan ukuran sel untuk baris dan kolom itu
1) Untuk menentukan Bagian (Baris) dari UICollectionView saya:
(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
2) Untuk menentukan jumlah item dalam suatu bagian. Anda dapat menentukan jumlah item yang berbeda untuk setiap bagian. Anda bisa mendapatkan nomor bagian menggunakan parameter 'bagian'.
(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
3) Untuk menentukan ukuran sel untuk setiap bagian dan baris secara terpisah. Anda bisa mendapatkan nomor bagian dan nomor baris menggunakan parameter 'indexPath' yaitu
[indexPath section]
untuk nomor bagian dan[indexPath row]
nomor baris.(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
4) Kemudian Anda dapat menampilkan sel Anda dalam baris dan bagian menggunakan:
(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
CATATAN: Di UICollectionView
sumber
Jawaban Mike Sand bagus tetapi saya mengalami beberapa masalah dengan kode ini (Seperti sel yang panjang terpotong). Dan kode baru:
#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; }
sumber
Diedit jawaban Angel García Olloqui tentang rasa hormat
minimumInteritemSpacing
dari delegasicollectionView(_:layout:minimumInteritemSpacingForSectionAt:)
, jika itu mengimplementasikannya.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 }
sumber
Kode di atas berfungsi untuk saya. Saya ingin membagikan kode Swift 3.0 masing-masing.
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 } }
sumber