ios - Reordenar celdas de UICollectionView
ios6 uicollectionviewlayout (4)
¿No sería una solución simple tener 2 vistas de colección con standart UICollectionViewFlowLayout
?
O incluso mejor: tener un controlador de vista de página con desplazamiento horizontal, y cada página sería una vista de colección con diseño de flujo normal.
La idea es la siguiente: en su UICollectionViewController -init method
usted crea una segunda vista de colección con el marco desplazado a la derecha por el ancho de la vista de colección original. Luego lo agrega como subvista a la vista de colección original. Para cambiar entre las vistas de colección, simplemente agregue un reconocedor de barrido. Para calcular los valores de compensación, puede almacenar el marco original de la vista de colección en ivar cVFrame
. Para identificar tus vistas de colección puedes usar etiquetas.
Ejemplo de método init
:
CGRect cVFrame = self.collectionView.frame;
UICollectionView *secondView = [[UICollectionView alloc]
initWithFrame:CGRectMake(cVFrame.origin.x + cVFrame.size.width, 0,
cVFrame.size.width, cVFrame.size.height)
collectionViewLayout:[UICollectionViewFlowLayout new]];
[secondView setBackgroundColor:[UIColor greenColor]];
[secondView setTag:1];
[secondView setDelegate:self];
[secondView setDataSource:self];
[self.collectionView addSubview:secondView];
UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(swipedRight)];
[swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(swipedLeft)];
[swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.collectionView addGestureRecognizer:swipeRight];
[self.collectionView addGestureRecognizer:swipeLeft];
Ejemplo de los métodos swipeRight
y swipeLeft
:
-(void)swipedRight {
// Switch to left collection view
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}
-(void)swipedLeft {
// Switch to right collection view
[self.collectionView setContentOffset:CGPointMake(cVFrame.size.width, 0)
animated:YES];
}
Y entonces no es un gran problema implementar métodos de DataSource (en su caso, quiere tener 9 elementos en cada página):
-(NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
if (collectionView.tag == 1) {
// Second collection view
return self.dataArray.count % 9;
} else {
// Original collection view
return 9; // Or whatever
}
En el método -collectionView:cellForRowAtIndexPath
, necesitará obtener datos de su modelo con compensación, si es la segunda vista de colección.
Tampoco olvide registrar la clase para la celda reutilizable para su segunda vista de colección también. También puede crear un solo reconocedor de gestos y reconocer los movimientos hacia la izquierda y hacia la derecha. Tu decides.
Creo que, ahora debería funcionar, pruébalo :-)
Considere un UICollectionView
con diseño de flujo y dirección horizontal. Por defecto, las celdas están ordenadas de arriba a abajo, de izquierda a derecha. Me gusta esto:
1 4 7 10 13 16
2 5 8 11 14 17
3 6 9 12 15 18
En mi caso, la vista de colección está paginada y se ha diseñado para que se ajuste un número específico de celdas en cada página. Así, un ordenamiento más natural sería:
1 2 3 10 11 12
4 5 6 - 13 14 15
7 8 9 16 17 18
¿Qué sería lo más simple para lograr esto, sin implementar mi propio diseño personalizado? En particular, no quiero perder ninguna de las funcionalidades que vienen gratis con UICollectionViewFlowLayout
(como insertar / eliminar animaciones).
O, en general, ¿cómo implementa una función de reordenación f(n)
en un diseño de flujo? Lo mismo podría ser aplicable a un pedido de derecha a izquierda, por ejemplo.
Mi enfoque hasta ahora
Mi primer enfoque fue la subclase UICollectionViewFlowLayout
y anular layoutAttributesForItemAtIndexPath:
:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *reorderedIndexPath = [self reorderedIndexPathOfIndexPath:indexPath];
UICollectionViewLayoutAttributes *layout = [super layoutAttributesForItemAtIndexPath:reorderedIndexPath];
layout.indexPath = indexPath;
return layout;
}
Donde reorderedIndexPathOfIndexPath:
es f(n)
. Al llamar a super
, no tengo que calcular el diseño de cada elemento manualmente.
Además, tuve que anular layoutAttributesForElementsInRect:
que es el método que utiliza el diseño para elegir qué elementos mostrar.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *result = [NSMutableArray array];
NSInteger sectionCount = 1;
if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
{
sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
}
for (int s = 0; s < sectionCount; s++)
{
NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
for (int i = 0; i < itemCount; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
if (CGRectIntersectsRect(rect, layout.frame))
{
[result addObject:layout];
}
}
}
return result;
}
Aquí solo pruebo cada elemento y si está dentro del rect
dado, lo devuelvo.
Si este enfoque es el camino a seguir, tengo las siguientes preguntas más específicas:
- ¿Hay alguna manera de simplificar el
layoutAttributesForElementsInRect:
anular, o hacerlo más eficiente? - ¿Me estoy perdiendo de algo? Como mínimo, el intercambio de celdas de diferentes páginas produce resultados impares. Sospecho que está relacionado con
initialLayoutAttributesForAppearingItemAtIndexPath:
yfinalLayoutAttributesForDisappearingItemAtIndexPath:
pero no puedo identificar exactamente cuál es el problema. - En mi caso,
f(n)
depende del número de columnas y filas de cada página. ¿Hay alguna forma de extraer esta información deUICollectionViewFlowLayout
, sin tener que codificarla por mi mismo? Pensé en consultarlayoutAttributesForElementsInRect:
con los límites de la vista de colección, y deducir las filas y columnas de allí, pero esto también se siente ineficiente.
Esta es mi solución, no probé con animaciones pero creo que funcionará bien con algunos cambios, espero que sea útil
CELLS_PER_ROW = 4;
CELLS_PER_COLUMN = 5;
CELLS_PER_PAGE = CELLS_PER_ROW * CELLS_PER_COLUMN;
UICollectionViewFlowLayout* flowLayout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = new CGSize (size.width / CELLS_PER_ROW - delta, size.height / CELLS_PER_COLUMN - delta);
flowLayout.minimumInteritemSpacing = ..;
flowLayout.minimumLineSpacing = ..;
..
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = indexPath.row;
NSInteger page = index / CELLS_PER_PAGE;
NSInteger indexInPage = index - page * CELLS_PER_PAGE;
NSInteger row = indexInPage % CELLS_PER_COLUMN;
NSInteger column = indexInPage / CELLS_PER_COLUMN;
NSInteger dataIndex = row * CELLS_PER_ROW + column + page * CELLS_PER_PAGE;
id cellData = _data[dataIndex];
...
}
He pensado mucho sobre tu pregunta y he llegado a las siguientes consideraciones:
Subclasificar el FlowLayout parece ser la forma más correcta y efectiva de reordenar las celdas y hacer uso de las animaciones de diseño de flujo. Y su enfoque funciona, excepto por dos cosas importantes:
- Digamos que tiene una vista de colección con solo 2 celdas y ha diseñado su página para que pueda contener 9 celdas. La primera celda se ubicará en la esquina superior izquierda de la vista, como en el diseño de flujo original. Sin embargo, su segunda celda debe colocarse en la parte superior de la vista y tiene una ruta de índice [0, 1]. La ruta de índice reordenada sería [0, 3] (ruta de índice de la celda de diseño de flujo original que estaría en su lugar). Y en su reemplazo
layoutAttributesForItemAtIndexPath
enviaría el mensaje como[super layoutAttributesForItemAtIndexPath:[0, 3]]
, obtendría un objetonil
, solo porque solo hay 2 celdas: [0,0] y [0,1]. Y este sería el problema para tu última página. - Aunque puede implementar el comportamiento de paginación al anular
targetContentOffsetForProposedContentOffset:withScrollingVelocity:
y configurar manualmente propiedades comoitemSize
,minimumInteritemSpacing
yminimumInteritemSpacing
, es mucho trabajo hacer que sus elementos sean simétricos, definir los bordes de paginación y demás.
Creo que, la subclasificación del diseño de flujo está preparando mucha implementación para usted, porque lo que quiere ya no es un diseño de flujo. Pero pensémoslo juntos. Respecto a tus preguntas:
- su
layoutAttributesForElementsInRect:
override es exactamente cómo es la implementación original de Apple, por lo que no hay manera de simplificarla. Sin embargo, para su caso, podría considerar lo siguiente: si tiene 3 filas de elementos por página, y el marco del elemento en la primera fila se cruza con el marco correcto, entonces (si todos los elementos tienen el mismo tamaño) los marcos de los elementos de la segunda y tercera fila intersecta este rect. - perdon no entendi tu segunda pregunta
- en mi caso, la función de reordenación se ve así: (a es el número entero de filas / columnas en cada página, filas = columnas)
f (n) = (n% a²) + (a - 1) (col - fila) + a² (n / a²); col = (n% a²)% a; fila = (n% a²) / a;
Respondiendo a la pregunta, el diseño del flujo no tiene idea de cuántas filas hay en cada columna porque este número puede variar de una columna a otra según el tamaño de cada elemento. Tampoco puede decir nada sobre el número de columnas en cada página porque depende de la posición de desplazamiento y también puede variar. Por lo tanto, no hay mejor manera que consultar layoutAttributesForElementsInRect
, pero esto incluirá también celdas, que solo son particularmente visibles. Dado que sus celdas son iguales en tamaño, teóricamente podría averiguar cuántas filas tiene su vista de colección con dirección de desplazamiento horizontal: comenzando iterando cada celda contándolas y rompiendo si cambia su frame.origin.x
.
Entonces, creo que tienes dos opciones para lograr tu propósito:
Subclase
UICollectionViewLayout
. Parece que es mucho trabajo implementar todos esos métodos, pero es la única manera efectiva. Podría tener, por ejemplo, propiedades comoitemSize
,itemsInOneRow
. Luego, puede encontrar fácilmente una fórmula para calcular el marco de cada elemento basándose en su número (la mejor manera es hacerlo enprepareLayout
y almacenar todos los marcos en una matriz, de modo que pueda acceder al marco que necesita enlayoutAttributesForItemAtIndexPath
). ImplementarlayoutAttributesForItemAtIndexPath
,layoutAttributesForItemsInRect
ycollectionViewContentSize
sería muy simple. EninitialLayoutAttributesForAppearingItemAtIndexPath
yfinalLayoutAttributesForDisappearingItemAtIndexPath
puede establecer el atributo alfa en 0.0. Así es como funcionan las animaciones de diseño de flujo estándar. Al anulartargetContentOffsetForProposedContentOffset:withScrollingVelocity:
podría implementar el "comportamiento de paginación".Considere hacer una vista de colección con diseño de flujo,
pagingEnabled = YES
, dirección de desplazamiento horizontal y tamaño del elemento igual al tamaño de la pantalla. Un artículo por tamaño de pantalla. Para cada celda, puede establecer una nueva vista de colección como subvista con diseño de flujo vertical y la misma fuente de datos que otras vistas de colección pero con un desplazamiento. Es muy eficiente, porque luego reutiliza vistas de colección completas que contienen bloques de 9 (o lo que sea) celdas en lugar de reutilizar cada celda con un enfoque estándar. Todas las animaciones deberían funcionar correctamente.
Here puede descargar un proyecto de ejemplo utilizando el enfoque de subclases de diseño. (# 2)
Tiene un objeto que implementa el protocolo UICollectionViewDataSource
. Dentro de collectionView:cellForItemAtIndexPath:
simplemente devuelve el elemento correcto que desea devolver. No entiendo donde habría un problema.
Edit: ok, veo el problema. Aquí está la solución: http://www.skeuo.com/uicollectionview-custom-layout-tutorial , específicamente los pasos 17 a 25. No es una gran cantidad de trabajo, y se puede reutilizar muy fácilmente.