W moim projekcie używam UICollectionView, w którym w wierszu znajduje się wiele komórek o różnych szerokościach. Według: https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
rozprowadza komórki wzdłuż linii z równym wypełnieniem. Dzieje się to zgodnie z oczekiwaniami, z wyjątkiem tego, że chcę je wyrównać i na stałe zakodować szerokość dopełnienia.
Wydaje mi się, że muszę podklasę UICollectionViewFlowLayout, jednak po przeczytaniu niektórych samouczków itp. W Internecie po prostu nie wydaje się, aby rozumieć, jak to działa.
Odpowiedzi:
Pozostałe rozwiązania w tym wątku nie działają poprawnie, gdy linia składa się tylko z 1 pozycji lub jest zbyt skomplikowana.
Bazując na przykładzie podanym przez Ryana, zmieniłem kod, aby wykrywał nową linię, sprawdzając pozycję Y nowego elementu. Bardzo proste i szybkie w wykonaniu.
Szybki:
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 } }
Jeśli chcesz, aby dodatkowe widoki zachowały swój rozmiar, dodaj następujące informacje u góry zamknięcia w
forEach
wywołaniu:guard layoutAttribute.representedElementCategory == .cell else { return }
Cel 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; }
źródło
let attributes = super.layoutAttributesForElementsInRect(rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
Odpowiedzi na to pytanie zawierają wiele wspaniałych pomysłów. Jednak większość z nich ma pewne wady:
Większość rozwiązań tylko zastępuje tę
layoutAttributesForElements(in rect: CGRect)
funkcję. Nie zastępująlayoutAttributesForItem(at indexPath: IndexPath)
. Jest to problem, ponieważ widok kolekcji okresowo wywołuje tę ostatnią funkcję w celu pobrania atrybutów układu dla określonej ścieżki indeksu. Jeśli nie zwrócisz odpowiednich atrybutów z tej funkcji, prawdopodobnie napotkasz wszelkiego rodzaju wizualne błędy, np. Podczas wstawiania i usuwania animacji komórek lub podczas używania samoczynnie zmieniających się rozmiarów komórek przez ustawienie układu widoku kolekcjiestimatedItemSize
. Dokumentacja Apple stwierdza:Wiele rozwiązań przyjmuje również założenia dotyczące
rect
parametru przekazywanego dolayoutAttributesForElements(in rect: CGRect)
funkcji. Na przykład wiele z nich opiera się na założeniu, żerect
zawsze zaczyna się na początku nowej linii, co niekoniecznie ma miejsce.Więc innymi słowy:
Większość rozwiązań sugerowanych na tej stronie działa w określonych aplikacjach, ale nie działają one zgodnie z oczekiwaniami w każdej sytuacji.
AlignedCollectionViewFlowLayout
Aby rozwiązać te problemy, stworzyłem
UICollectionViewFlowLayout
podklasę, która kieruje się podobną ideą, jak sugerowali Matt i Chris Wagner w odpowiedziach na podobne pytanie. Może wyrównać komórki⬅︎ lewo :
lub w ➡︎ prawo :
a dodatkowo oferuje opcje pionowego wyrównania komórek w odpowiednich wierszach (w przypadku, gdy różnią się wysokością).
Możesz go po prostu pobrać tutaj:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
Użycie jest proste i wyjaśnione w pliku README. Zasadniczo tworzysz wystąpienie
AlignedCollectionViewFlowLayout
, określasz żądane wyrównanie i przypisujesz je docollectionViewLayout
właściwości widoku kolekcji :(Jest również dostępny na Cocoapods .)
Jak to działa (dla komórek wyrównanych do lewej):
Tutaj chodzi o poleganie wyłącznie na
layoutAttributesForItem(at indexPath: IndexPath)
funkcji . W tymlayoutAttributesForElements(in rect: CGRect)
po prostu pobieramy ścieżki indeksu wszystkich komórek w obrębie,rect
a następnie wywołujemy pierwszą funkcję dla każdej ścieżki indeksu, aby pobrać prawidłowe ramki: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()
Funkcja po prostu tworzy głęboką kopię wszystkich atrybutów układu w tablicy. Możesz zajrzeć do kodu źródłowego pod kątem jego implementacji).Więc teraz jedyne, co musimy zrobić, to
layoutAttributesForItem(at indexPath: IndexPath)
poprawnie zaimplementować tę funkcję. SuperklasaUICollectionViewFlowLayout
już umieszcza właściwą liczbę komórek w każdym wierszu, więc musimy tylko przesunąć je w lewo w odpowiednim wierszu. Trudność polega na obliczeniu ilości miejsca potrzebnego do przesunięcia każdej komórki w lewo.Ponieważ chcemy mieć stały odstęp między komórkami, podstawową ideą jest po prostu założenie, że poprzednia komórka (komórka na lewo od komórki, która jest obecnie ułożona) jest już prawidłowo ustawiona. Następnie musimy tylko dodać odstępy między komórkami do
maxX
wartości ramki poprzedniej komórki i to jestorigin.x
wartość ramki bieżącej komórki.Teraz musimy tylko wiedzieć, kiedy dotarliśmy do początku wiersza, aby nie wyrównać komórki do komórki w poprzednim wierszu. (Spowodowałoby to nie tylko nieprawidłowy układ, ale również byłoby bardzo opóźnione.) Musimy więc mieć kotwicę rekurencji. Podejście, którego używam do znalezienia tej kotwicy rekurencji, jest następujące:
Aby dowiedzieć się, czy komórka o indeksie i znajduje się w tym samym wierszu, co komórka o indeksie i-1 ...
... „Rysuję” prostokąt wokół bieżącej komórki i rozciągam go na szerokość całego widoku kolekcji. Ponieważ
UICollectionViewFlowLayout
centra wszystkie komórki w pionie, każda komórka w tej samej linii musi przecinać się z tym prostokątem.W ten sposób po prostu sprawdzam, czy komórka o indeksie i-1 przecina się z tym prostokątem linii utworzonym z komórki o indeksie i .
Jeśli przecina się, komórka o indeksie i nie jest ostatnią komórką w linii z lewej strony.
→ Pobierz ramkę z poprzedniej komórki (z indeksem i − 1 ) i przenieś do niej bieżącą komórkę.
Jeśli się nie przecina, komórka z indeksem i jest ostatnią komórką w linii z lewej strony.
→ Przenieś komórkę do lewej krawędzi widoku kolekcji (bez zmiany jej pozycji w pionie).
Nie będę tutaj umieszczał faktycznej implementacji
layoutAttributesForItem(at indexPath: IndexPath)
funkcji, ponieważ uważam, że najważniejsze jest zrozumienie pomysłu i zawsze możesz sprawdzić moją implementację w kodzie źródłowym . (Jest to trochę bardziej skomplikowane niż wyjaśniono tutaj, ponieważ zezwalam również na.right
wyrównanie i różne opcje wyrównania w pionie. Jednak jest to ta sama idea).Wow, myślę, że to najdłuższa odpowiedź, jaką kiedykolwiek napisałem w Stackoverflow. Mam nadzieję, że to pomoże. 😉
źródło
Dzięki Swift 4.1 i iOS 11, w zależności od potrzeb, możesz wybrać jedną z 2 następujących kompletnych implementacji , aby rozwiązać problem.
# 1. Wyrównanie do lewej autorezowanie
UICollectionViewCell
sPoniższa implementacja pokazuje, jak używać
UICollectionViewLayout
znaków 'slayoutAttributesForElements(in:)
,UICollectionViewFlowLayout
' sestimatedItemSize
iUILabel
'spreferredMaxLayoutWidth
, aby wyrównać do lewej autorezację komórek w aUICollectionView
: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") } }
Spodziewany wynik:
# 2. Wyrównanie do lewej
UICollectionViewCell
ze stałym rozmiaremPoniższa implementacja pokazuje, jak używać
UICollectionViewLayout
plikówlayoutAttributesForElements(in:)
iUICollectionViewFlowLayout
”itemSize
w celu wyrównania komórek do lewej strony ze wstępnie zdefiniowanym rozmiarem w plikuUICollectionView
: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") } }
Spodziewany wynik:
źródło
10
się dowolnej liczby?Proste rozwiązanie w 2019 roku
To jedno z tych przygnębiających pytań, w przypadku których na przestrzeni lat wiele się zmieniło. Teraz jest to łatwe.
Po prostu robisz to:
// as you move across one row ... a.frame.origin.x = x x += a.frame.width + minimumInteritemSpacing // and, obviously start fresh again each row
Teraz potrzebujesz tylko kodu standardowego:
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 }
Po prostu skopiuj i wklej to do pliku
UICollectionViewFlowLayout
- gotowe.W pełni działające rozwiązanie do kopiowania i wklejania:
To jest cała sprawa:
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 } }
I w końcu...
Podziękuj @AlexShubin powyżej, który jako pierwszy to wyjaśnił!
źródło
Pytanie trwa od jakiegoś czasu, ale nie ma odpowiedzi i jest to dobre pytanie. Odpowiedzią jest przesłonięcie jednej metody w podklasie 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
Zgodnie z zaleceniami Apple, uzyskujesz atrybuty układu z super i iterujesz po nich. Jeśli jest to pierwsza pozycja w wierszu (zdefiniowana przez origin.x znajdujący się na lewym marginesie), zostawiasz go w spokoju i resetujesz x do zera. Następnie dla pierwszej komórki i każdej komórki dodajesz szerokość tej komórki plus margines. Jest to przekazywane do następnego elementu w pętli. Jeśli nie jest to pierwszy element, ustawiasz jego origin.x na bieżący obliczony margines i dodajesz nowe elementy do tablicy.
źródło
attribute.center.x == self.collectionView?.bounds.midX
leftMargin
pomocąif (attributes.frame.origin.x == self.sectionInset.left) {
czeku. Zakłada się, że domyślny układ wyrównał komórkę nowego wiersza dosectionInset.left
. Jeśli tak nie jest, wynikałoby z opisanego przez Ciebie zachowania. Wstawiłbym punkt przerwania wewnątrz pętli i sprawdziłbym obie strony pod kątem komórki drugiego rzędu tuż przed porównaniem. Powodzenia.Miałem ten sam problem, wypróbuj Cocoapod UICollectionViewLeftAlignedLayout . Po prostu uwzględnij go w swoim projekcie i zainicjuj w następujący sposób:
UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init]; UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
źródło
Oto oryginalna odpowiedź w języku Swift. W większości nadal działa świetnie.
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 } }
Wyjątek: automatyczne określanie rozmiaru komórek
Niestety jest jeden duży wyjątek. Jeśli używasz
UICollectionViewFlowLayout
„sestimatedItemSize
. WewnętrznieUICollectionViewFlowLayout
to trochę zmienia. Nie wyśledziłem tego do końca, ale jasne jest, że wywołuje inne metody po tymlayoutAttributesForElementsInRect
, jak sam dopasowuję rozmiar komórek. Na podstawie moich prób i błędów stwierdziłem, żelayoutAttributesForItemAtIndexPath
podczas autodopasowania częściej dzwoni do każdej komórki indywidualnie. Ta aktualizacjaLeftAlignedFlowLayout
działa świetnie zestimatedItemSize
. Działa również z komórkami o statycznym rozmiarze, jednak dodatkowe wywołania układu prowadzą mnie do użycia oryginalnej odpowiedzi zawsze, gdy nie potrzebuję automatycznego określania rozmiaru komórek.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 } }
źródło
Opierając się na odpowiedzi Michaela Sanda , stworzyłem podklasę
UICollectionViewFlowLayout
bibliotekę z aby wykonać do lewej, prawej lub pełnej (w zasadzie domyślnej) poziomej - pozwala ona również ustawić bezwzględną odległość między każdą komórką. Planuję dodać do tego wyrównanie do środka w poziomie i wyrównanie w pionie.https://github.com/eroth/ERJustifiedFlowLayout
źródło
Szybko. Według odpowiedzi Michaelsa
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 }
źródło
Na podstawie wszystkich odpowiedzi trochę się zmieniam i to działa dobrze
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 }
źródło
Jeśli ktoś z was ma problem - niektóre komórki po prawej stronie widoku kolekcji przekraczają granice widoku kolekcji . Następnie użyj tego -
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 } }
Użyj INT zamiast porównywania wartości CGFloat .
źródło
Na podstawie odpowiedzi tutaj, ale naprawiono awarie i problemy z wyrównywaniem, gdy widok kolekcji zawiera również nagłówki lub stopki. Wyrównywanie tylko do lewej komórki:
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 } }
źródło
Dzięki za odpowiedź Michaela Sanda . Zmodyfikowałem go do rozwiązania wielu wierszy (to samo wyrównanie do góry y każdego wiersza), czyli wyrównanie do lewej, równe odstępy do każdego elementu.
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; }
źródło
Oto moja podróż w celu znalezienia najlepszego kodu, który działa ze Swift 5. Połączyłem kilka odpowiedzi z tego wątku i kilku innych wątków, aby rozwiązać ostrzeżenia i problemy, z którymi się spotkałem. Podczas przewijania widoku kolekcji wystąpiło ostrzeżenie i pewne nietypowe zachowanie. Konsola wyświetla następujące informacje:
Inną kwestią, z którą się spotkałem, jest to, że niektóre długie komórki są przycinane po prawej stronie ekranu. Ponadto ustawiłem wstawki sekcji i minimumInteritemSpacing w funkcjach delegatów, co spowodowało, że wartości nie zostały odzwierciedlone w klasie niestandardowej. Rozwiązaniem było ustawienie tych atrybutów w instancji układu przed zastosowaniem go w moim widoku kolekcji.
Oto jak użyłem układu widoku mojej kolekcji:
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)
Oto klasa układu przepływu
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 } }
źródło
Problem z UICollectionView polega na tym, że próbuje automatycznie dopasować komórki w dostępnym obszarze. Zrobiłem to, najpierw definiując liczbę wierszy i kolumn, a następnie definiując rozmiar komórki dla tego wiersza i kolumny
1) Aby zdefiniować sekcje (wiersze) mojego UICollectionView:
(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
2) Określenie liczby pozycji w sekcji. Możesz zdefiniować inną liczbę pozycji dla każdej sekcji. numer sekcji można uzyskać za pomocą parametru „sekcja”.
(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
3) Aby zdefiniować rozmiar komórki osobno dla każdej sekcji i wiersza. Numer sekcji i numer wiersza można uzyskać za pomocą parametru „indexPath”, tj.
[indexPath section]
Dla numeru sekcji i[indexPath row]
dla numeru wiersza.(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
4) Następnie możesz wyświetlić komórki w wierszach i sekcjach za pomocą:
(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
UWAGA: w UICollectionView
źródło
Odpowiedź Mike'a Sanda jest dobra, ale miałem pewne problemy z tym kodem (jak np. Wycięcie długiej komórki). I nowy kod:
#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; }
źródło
Zredagowana odpowiedź Angel García Olloqui na szacunek
minimumInteritemSpacing
delegatacollectionView(_:layout:minimumInteritemSpacingForSectionAt:)
, jeśli ją wdraża.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 }
źródło
Powyższy kod działa u mnie. Chciałbym udostępnić odpowiedni kod 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 } }
źródło