uicollectionviewcontroller tutorial example collection ios uicollectionview

ios - tutorial - ¿Cómo centrar las celdas de un UICollectionView?



uicollectionviewcell swift (18)

Aquí es cómo puedes hacerlo y funciona bien

func refreshCollectionView(_ count: Int) { let collectionViewHeight = collectionView.bounds.height let collectionViewWidth = collectionView.bounds.width let numberOfItemsThatCanInCollectionView = Int(collectionViewWidth / collectionViewHeight) if numberOfItemsThatCanInCollectionView > count { let totalCellWidth = collectionViewHeight * CGFloat(count) let totalSpacingWidth: CGFloat = CGFloat(count) * (CGFloat(count) - 1) // leftInset, rightInset are the global variables which I am passing to the below function leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2; rightInset = -leftInset } else { leftInset = 0.0 rightInset = -collectionViewHeight } collectionView.reloadData() } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsetsMake(0, leftInset, 0, rightInset) }

Actualmente estoy usando UICollectionView para la grilla de la interfaz de usuario, y funciona bien. Sin embargo, me gustaría habilitar el desplazamiento horizontal. La cuadrícula admite 8 elementos por página y cuando el número total de elementos es, digamos 4, así es como deben organizarse los elementos con la dirección de desplazamiento horizontal habilitada:

0 0 x x 0 0 x x

Aquí 0 -> Artículo de colección y x -> Celdas vacías

¿Hay alguna manera de hacer que se centren en el centro como:

x 0 0 x x 0 0 x

¿Para que el contenido se vea más limpio?

Además, el arreglo a continuación también podría ser una solución que estoy esperando.

0 0 0 0 x x x x


Aquí está mi solución con algunas suposiciones:

  • solo hay una sección
  • las inserciones izquierda y derecha son iguales
  • la altura de la celda es la misma

Siéntase libre de ajustarse para satisfacer sus necesidades.

Diseño centrado con ancho de celda variable:

protocol HACenteredLayoutDelegate: UICollectionViewDataSource { func getCollectionView() -> UICollectionView func sizeOfCell(at index: IndexPath) -> CGSize func contentInsets() -> UIEdgeInsets } class HACenteredLayout: UICollectionViewFlowLayout { weak var delegate: HACenteredLayoutDelegate? private var cache = [UICollectionViewLayoutAttributes]() private var contentSize = CGSize.zero override var collectionViewContentSize: CGSize { return self.contentSize } required init(delegate: HACenteredLayoutDelegate) { self.delegate = delegate super.init() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func invalidateLayout() { cache.removeAll() super.invalidateLayout() } override func prepare() { if cache.isEmpty && self.delegate != nil && self.delegate!.collectionView(self.delegate!.getCollectionView(), numberOfItemsInSection: 0) > 0 { let insets = self.delegate?.contentInsets() ?? UIEdgeInsets.zero var rows: [(width: CGFloat, count: Int)] = [(0, 0)] let viewWidth: CGFloat = UIScreen.main.bounds.width var y = insets.top var unmodifiedIndexes = [IndexPath]() for itemNumber in 0 ..< self.delegate!.collectionView(self.delegate!.getCollectionView(), numberOfItemsInSection: 0) { let indexPath = IndexPath(item: itemNumber, section: 0) let cellSize = self.delegate!.sizeOfCell(at: indexPath) let potentialRowWidth = rows.last!.width + (rows.last!.count > 0 ? self.minimumInteritemSpacing : 0) + cellSize.width + insets.right + insets.left if potentialRowWidth > viewWidth { let leftOverSpace = max((viewWidth - rows[rows.count - 1].width)/2, insets.left) for i in unmodifiedIndexes { self.cache[i.item].frame.origin.x += leftOverSpace } unmodifiedIndexes = [] rows.append((0, 0)) y += cellSize.height + self.minimumLineSpacing } unmodifiedIndexes.append(indexPath) let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath) rows[rows.count - 1].count += 1 rows[rows.count - 1].width += rows[rows.count - 1].count > 1 ? self.minimumInteritemSpacing : 0 attribute.frame = CGRect(x: rows[rows.count - 1].width, y: y, width: cellSize.width, height: cellSize.height) rows[rows.count - 1].width += cellSize.width cache.append(attribute) } let leftOverSpace = max((viewWidth - rows[rows.count - 1].width)/2, insets.left) for i in unmodifiedIndexes { self.cache[i.item].frame.origin.x += leftOverSpace } self.contentSize = CGSize(width: viewWidth, height: y + self.delegate!.sizeOfCell(at: IndexPath(item: 0, section: 0)).height + insets.bottom) } } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var layoutAttributes = [UICollectionViewLayoutAttributes]() for attributes in cache { if attributes.frame.intersects(rect) { layoutAttributes.append(attributes) } } return layoutAttributes } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { if indexPath.item < self.cache.count { return self.cache[indexPath.item] } return nil } }

Resultado:


Así es como obtuve una vista de colección alineada al centro con un espaciado Interitem fijo

#define maxInteritemSpacing 6 #define minLineSpacing 3 @interface MyFlowLayout()<UICollectionViewDelegateFlowLayout> @end @implementation MyFlowLayout - (instancetype)init { self = [super init]; if (self) { self.minimumInteritemSpacing = 3; self.minimumLineSpacing = minLineSpacing; self.scrollDirection = UICollectionViewScrollDirectionVertical; } return self; } - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { NSArray *answer = [super layoutAttributesForElementsInRect:rect]; if (answer.count > 0) { NSMutableArray<NSMutableArray<UICollectionViewLayoutAttributes *> *> *rows = [NSMutableArray array]; CGFloat maxY = -1.0f; NSInteger currentRow = 0; //add first item to row and set its maxY [rows addObject:[@[answer[0]] mutableCopy]]; maxY = CGRectGetMaxY(((UICollectionViewLayoutAttributes *)answer[0]).frame); for(int i = 1; i < [answer count]; ++i) { //handle maximum spacing UICollectionViewLayoutAttributes *currentLayoutAttributes = answer[i]; UICollectionViewLayoutAttributes *prevLayoutAttributes = answer[i - 1]; NSInteger maximumSpacing = maxInteritemSpacing; NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame); if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) { CGRect frame = currentLayoutAttributes.frame; frame.origin.x = origin + maximumSpacing; currentLayoutAttributes.frame = frame; } //handle center align CGFloat currentY = currentLayoutAttributes.frame.origin.y; if (currentY >= maxY) { [self shiftRowToCenterForCurrentRow:rows[currentRow]]; //next row [rows addObject:[@[currentLayoutAttributes] mutableCopy]]; currentRow++; } else { //same row [rows[currentRow] addObject:currentLayoutAttributes]; } maxY = MAX(CGRectGetMaxY(currentLayoutAttributes.frame), maxY); } //mark sure to shift 1 row items if (currentRow == 0) { [self shiftRowToCenterForCurrentRow:rows[currentRow]]; } } return answer; } - (void)shiftRowToCenterForCurrentRow:(NSMutableArray<UICollectionViewLayoutAttributes *> *)currentRow { //shift row to center CGFloat endingX = CGRectGetMaxX(currentRow.lastObject.frame); CGFloat shiftX = (self.collectionViewContentSize.width - endingX) / 2.f; for (UICollectionViewLayoutAttributes *attr in currentRow) { CGRect shiftedFrame = attr.frame; shiftedFrame.origin.x += shiftX; attr.frame = shiftedFrame; } } @end


Basado en la respuesta user3676011, puedo sugerir uno más detallado con una pequeña corrección. Esta solución funciona muy bien en Swift 2.0 .

enum CollectionViewContentPosition { case Left case Center } var collectionViewContentPosition: CollectionViewContentPosition = .Left func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { if collectionViewContentPosition == .Left { return UIEdgeInsetsZero } // Center collectionView content let itemsCount: CGFloat = CGFloat(collectionView.numberOfItemsInSection(section)) let collectionViewWidth: CGFloat = collectionView.bounds.width let itemWidth: CGFloat = 40.0 let itemsMargin: CGFloat = 10.0 let edgeInsets = (collectionViewWidth - (itemsCount * itemWidth) - ((itemsCount-1) * itemsMargin)) / 2 return UIEdgeInsetsMake(0, edgeInsets, 0, 0) }

Hubo un problema en

(CGFloat (elements.count) * 10))

donde debe ser adicional -1 mencionado.


Creo que puedes lograr el estilo de línea única implementando algo como esto:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { return UIEdgeInsetsMake(0, 100, 0, 0); }

Tendrá que jugar con ese número para descubrir cómo forzar el contenido en una sola línea. El primer 0, es el argumento del borde superior, puede ajustar ese también, si quiere centrar el contenido verticalmente en la pantalla.


De forma similar a otras respuestas, esta es una respuesta dinámica que debería funcionar para celdas de tamaño estático. La única modificación que hice fue poner el relleno en ambos lados. Si no hice esto, tuve problemas.

C objetivo

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { NSInteger numberOfItems = [collectionView numberOfItemsInSection:section]; CGFloat combinedItemWidth = (numberOfItems * collectionViewLayout.itemSize.width) + ((numberOfItems - 1) * collectionViewLayout.minimumInteritemSpacing); CGFloat padding = (collectionView.frame.size.width - combinedItemWidth) / 2; return UIEdgeInsetsMake(0, padding, 0, padding); }

Rápido

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout let numberOfItems = CGFloat(collectionView.numberOfItems(inSection: section)) let combinedItemWidth = (numberOfItems * flowLayout.itemSize.width) + ((numberOfItems - 1) * flowLayout.minimumInteritemSpacing) let padding = (collectionView.frame.width - combinedItemWidth) / 2 return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding) }

Además, si todavía tiene problemas, asegúrese de establecer tanto el minimumInteritemSpacing como el minimumInteritemSpacing en los mismos valores, ya que estos valores parecen estar relacionados entre sí.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; flowLayout.minimumInteritemSpacing = 20.0; flowLayout.minimumLineSpacing = 20.0;


En Swift, lo siguiente distribuirá las células de manera uniforme aplicando la cantidad correcta de relleno en los lados de cada celda. Por supuesto, primero debe conocer / establecer el ancho de su celda.

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { var cellWidth : CGFloat = 110; var numberOfCells = floor(self.view.frame.size.width / cellWidth); var edgeInsets = (self.view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1); return UIEdgeInsetsMake(0, edgeInsets, 60.0, edgeInsets); }


Es fácil calcular las inserciones dinámicamente, este código siempre centrará sus celdas:

NSInteger const SMEPGiPadViewControllerCellWidth = 332; ... - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { NSInteger numberOfCells = self.view.frame.size.width / SMEPGiPadViewControllerCellWidth; NSInteger edgeInsets = (self.view.frame.size.width - (numberOfCells * SMEPGiPadViewControllerCellWidth)) / (numberOfCells + 1); return UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets); } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; [self.collectionView.collectionViewLayout invalidateLayout]; }


Las mejores soluciones aquí no me funcionaron de inmediato, así que se me ocurrió esto, que debería funcionar para cualquier vista de colección de desplazamiento horizontal con diseño de flujo y solo una sección:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { // Add inset to the collection view if there are not enough cells to fill the width. CGFloat cellSpacing = ((UICollectionViewFlowLayout *) collectionViewLayout).minimumLineSpacing; CGFloat cellWidth = ((UICollectionViewFlowLayout *) collectionViewLayout).itemSize.width; NSInteger cellCount = [collectionView numberOfItemsInSection:section]; CGFloat inset = (collectionView.bounds.size.width - (cellCount * cellWidth) - ((cellCount - 1)*cellSpacing)) * 0.5; inset = MAX(inset, 0.0); return UIEdgeInsetsMake(0.0, inset, 0.0, 0.0); }


Mi solución para las celdas de vista de colección de tamaño estático que necesitan tener relleno en los lados izquierdo y derecho.

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { let flowLayout = (collectionViewLayout as! UICollectionViewFlowLayout) let cellSpacing = flowLayout.minimumInteritemSpacing let cellWidth = flowLayout.itemSize.width let cellCount = CGFloat(collectionView.numberOfItemsInSection(section)) let collectionViewWidth = collectionView.bounds.size.width let totalCellWidth = cellCount * cellWidth let totalCellSpacing = cellSpacing * (cellCount - 1) let totalCellsWidth = totalCellWidth + totalCellSpacing let edgeInsets = (collectionViewWidth - totalCellsWidth) / 2.0 return edgeInsets > 0 ? UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets) : UIEdgeInsetsMake(0, cellSpacing, 0, cellSpacing) }


No estoy seguro de si esto es nuevo en Xcode 5 o no, pero ahora puede abrir el Inspector de tamaños a través de Interface Builder y establecer allí un recuadro. Eso evitará que tenga que escribir código personalizado para hacer esto por usted y debería aumentar drásticamente la velocidad a la que encuentra los desplazamientos adecuados.


Otra forma de hacerlo es configurar collectionView.center.x , de esta forma:

override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if let collectionView = collectionView, layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { collectionView.center.x = view.center.x + layout.sectionInset.right / 2.0 } }

En este caso, solo respetando el recuadro derecho que funciona para mí.


Para aquellos que buscan una solución para las celdas de vista de colección alineadas a la UICollectionViewFlowLayout y de ancho dinámico , como yo lo estuve, terminé modificando la respuesta de Angel para una versión alineada a la izquierda para crear una subclase alineada en el centro para UICollectionViewFlowLayout .

CenterAlignedCollectionViewFlowLayout

// NOTE: Doesn''t work for horizontal layout! class CenterAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let superAttributes = super.layoutAttributesForElements(in: rect) else { return nil } // Copy each item to prevent "UICollectionViewFlowLayout has cached frame mismatch" warning guard let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil } // Constants let leftPadding: CGFloat = 8 let interItemSpacing = minimumInteritemSpacing // Tracking values var leftMargin: CGFloat = leftPadding // Modified to determine origin.x for each item var maxY: CGFloat = -1.0 // Modified to determine origin.y for each item var rowSizes: [[CGFloat]] = [] // Tracks the starting and ending x-values for the first and last item in the row var currentRow: Int = 0 // Tracks the current row attributes.forEach { layoutAttribute in // Each layoutAttribute represents its own item if layoutAttribute.frame.origin.y >= maxY { // This layoutAttribute represents the left-most item in the row leftMargin = leftPadding // Register its origin.x in rowSizes for use later if rowSizes.count == 0 { // Add to first row rowSizes = [[leftMargin, 0]] } else { // Append a new row rowSizes.append([leftMargin, 0]) currentRow += 1 } } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + interItemSpacing maxY = max(layoutAttribute.frame.maxY, maxY) // Add right-most x value for last item in the row rowSizes[currentRow][1] = leftMargin - interItemSpacing } // At this point, all cells are left aligned // Reset tracking values and add extra left padding to center align entire row leftMargin = leftPadding maxY = -1.0 currentRow = 0 attributes.forEach { layoutAttribute in // Each layoutAttribute is its own item if layoutAttribute.frame.origin.y >= maxY { // This layoutAttribute represents the left-most item in the row leftMargin = leftPadding // Need to bump it up by an appended margin let rowWidth = rowSizes[currentRow][1] - rowSizes[currentRow][0] // last.x - first.x let appendedMargin = (collectionView!.frame.width - leftPadding - rowWidth - leftPadding) / 2 leftMargin += appendedMargin currentRow += 1 } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + interItemSpacing maxY = max(layoutAttribute.frame.maxY, maxY) } return attributes } }


Pon esto en tu delegado de vista de colección. Considera más la configuración de diseño de flujo básico que las otras respuestas y, por lo tanto, es más genérico.

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { NSInteger cellCount = [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:section]; if( cellCount >0 ) { CGFloat cellWidth = ((UICollectionViewFlowLayout*)collectionViewLayout).itemSize.width+((UICollectionViewFlowLayout*)collectionViewLayout).minimumInteritemSpacing; CGFloat totalCellWidth = cellWidth*cellCount + spacing*(cellCount-1); CGFloat contentWidth = collectionView.frame.size.width-collectionView.contentInset.left-collectionView.contentInset.right; if( totalCellWidth<contentWidth ) { CGFloat padding = (contentWidth - totalCellWidth) / 2.0; return UIEdgeInsetsMake(0, padding, 0, padding); } } return UIEdgeInsetsZero; }

versión rápida (gracias g0ld2k):

extension CommunityConnectViewController: UICollectionViewDelegateFlowLayout { func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { // Translated from Objective-C version at: http://.com/a/27656363/309736 let cellCount = CGFloat(viewModel.getNumOfItemsInSection(0)) if cellCount > 0 { let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout let cellWidth = flowLayout.itemSize.width + flowLayout.minimumInteritemSpacing let totalCellWidth = cellWidth*cellCount + spacing*(cellCount-1) let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right if (totalCellWidth < contentWidth) { let padding = (contentWidth - totalCellWidth) / 2.0 return UIEdgeInsetsMake(0, padding, 0, padding) } } return UIEdgeInsetsZero } }



Swift 2.0 ¡Funciona bien para mí!

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { let edgeInsets = (screenWight - (CGFloat(elements.count) * 50) - (CGFloat(elements.count) * 10)) / 2 return UIEdgeInsetsMake(0, edgeInsets, 0, 0); }

Donde: screenWight : básicamente es el ancho de mi colección (ancho de pantalla completa) - He hecho constantes: let screenWight: CGFloat = UIScreen.mainScreen (). Bounds.width porque self.view.bounds muestra cada vez 600 - coz de elementos de SizeClasses - conjunto de celdas 50 - mi ancho de celda manual 10 - mi distancia entre celdas


Tengo una barra de etiquetas en mi aplicación que usa UICollectionView y UICollectionViewFlowLayout , con una fila de celdas centradas.

Para obtener la sangría correcta, resta el ancho total de todas las celdas (incluido el espaciado) del ancho de tu UICollectionView y divide por dos.

[........Collection View.........] [..Cell..][..Cell..] [____indent___] / 2 = [_____][..Cell..][..Cell..][_____]

El problema es esta función -

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

se llama antes ...

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

... por lo que no puede iterar sobre sus celdas para determinar el ancho total.

En su lugar, necesita calcular el ancho de cada celda nuevamente, en mi caso utilizo [NSString sizeWithFont: ... ] ya que el ancho de mis celdas está determinado por el mismo UILabel.

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { CGFloat rightEdge = 0; CGFloat interItemSpacing = [(UICollectionViewFlowLayout*)collectionViewLayout minimumInteritemSpacing]; for(NSString * tag in _tags) rightEdge += [tag sizeWithFont:[UIFont systemFontOfSize:14]].width+interItemSpacing; // To center the inter spacing too rightEdge -= interSpacing/2; // Calculate the inset CGFloat inset = collectionView.frame.size.width-rightEdge; // Only center align if the inset is greater than 0 // That means that the total width of the cells is less than the width of the collection view and need to be aligned to the center. // Otherwise let them align left with no indent. if(inset > 0) return UIEdgeInsetsMake(0, inset/2, 0, 0); else return UIEdgeInsetsMake(0, 0, 0, 0); }


Versión de trabajo de la respuesta Objective C de kgaidis usando Swift 3.0:

let flow = UICollectionViewFlowLayout() func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { let numberOfItems = collectionView.numberOfItems(inSection: 0) let combinedItemWidth:CGFloat = (CGFloat(numberOfItems) * flow.itemSize.width) + ((CGFloat(numberOfItems) - 1) * flow.minimumInteritemSpacing) let padding = (collectionView.frame.size.width - combinedItemWidth) / 2 return UIEdgeInsetsMake(0, padding, 0, padding) }