iphone - tutorial - Cómo hacer flotar la vista suplementaria en UICollectionView como lo hacen los encabezados de sección en el estilo sencillo de UITableView
uicollectionview swift 4 (11)
Estoy luchando por lograr un efecto de "encabezado de sección flotante" con UICollectionView
. Algo que ha sido bastante fácil en UITableView
(comportamiento predeterminado para UITableViewStylePlain
) parece imposible en UICollectionView
sin mucho trabajo. ¿Me estoy perdiendo lo obvio?
Apple no proporciona documentación sobre cómo lograr esto. Parece que uno tiene que subclase UICollectionViewLayout
e implementar un diseño personalizado solo para lograr este efecto. Esto implica bastante trabajo, implementando los siguientes métodos:
Métodos para anular
Cada objeto de diseño debe implementar los siguientes métodos:
collectionViewContentSize
layoutAttributesForElementsInRect:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath: (if your layout supports supplementary views)
layoutAttributesForDecorationViewOfKind:atIndexPath: (if your layout supports decoration views)
shouldInvalidateLayoutForBoundsChange:
Sin embargo, no me queda claro cómo hacer que la vista suplementaria flote sobre las celdas y "pegarse" a la parte superior de la vista hasta que se llegue a la siguiente sección. ¿Hay una bandera para esto en los atributos de diseño?
Hubiera usado UITableView
pero necesito crear una jerarquía de colecciones bastante compleja que se logra fácilmente con una vista de colección.
¡Cualquier guía o código de muestra sería muy apreciado!
Aquí está mi opinión, creo que es mucho más simple que lo que se vio arriba. La principal fuente de simplicidad es que no estoy subclasando el diseño de flujo, sino que más bien distribuyo mi propio diseño (mucho más fácil, si me preguntas).
Tenga en cuenta que estoy asumiendo que ya es capaz de implementar su propio UICollectionViewLayout
que mostrará celdas y encabezados sin implementación flotante. Una vez que tenga la implementación escrita, solo entonces tendrá sentido el siguiente código. Una vez más, esto se debe a que el OP estaba preguntando específicamente sobre la parte de los encabezados flotantes.
algunas bonificaciones:
- Estoy flotando dos encabezados, no solo uno
- Los encabezados eliminan los encabezados anteriores del camino
- Mira, rápido!
Nota:
-
supplementaryLayoutAttributes
contiene todos los atributos de encabezado sin implementar flotante - Estoy usando este código en
prepareLayout
, ya que hago todo el cálculo por adelantado. - ¡no olvide anular
shouldInvalidateLayoutForBoundsChange
en true!
// float them headers
let yOffset = self.collectionView!.bounds.minY
let headersRect = CGRect(x: 0, y: yOffset, width: width, height: headersHeight)
var floatingAttributes = supplementaryLayoutAttributes.filter {
$0.frame.minY < headersRect.maxY
}
// This is three, because I am floating 2 headers
// so 2 + 1 extra that will be pushed away
var index = 3
var floatingPoint = yOffset + dateHeaderHeight
while index-- > 0 && !floatingAttributes.isEmpty {
let attribute = floatingAttributes.removeLast()
attribute.frame.origin.y = max(floatingPoint, attribute.frame.origin.y)
floatingPoint = attribute.frame.minY - dateHeaderHeight
}
Ejecuté esto con el código de codificación vigorosa . Sin embargo, ese código no consideró SectionInset.
Así que cambié este código para desplazamiento vertical
origin.y = MIN(
MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)),
(CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)
);
a
origin.y = MIN(
MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight - self.sectionInset.top)),
(CGRectGetMaxY(lastCellAttrs.frame) - headerHeight + self.sectionInset.bottom)
);
Si quieren código para scroll horizontal, refiéranse al código aove.
En caso de que alguien esté buscando una solución en Objective-C, ponga esto en viewDidload:
UICollectionViewFlowLayout *flowLayout =
(UICollectionViewFlowLayout*)_yourcollectionView.collectionViewLayout;
[flowLayout setSectionHeadersPinToVisibleBounds:YES];
En iOS9, Apple tuvo la amabilidad de agregar una propiedad simple en UICollectionViewFlowLayout
llamada sectionHeadersPinToVisibleBounds
.
Con esto, puedes hacer que los encabezados floten así en las vistas de tabla.
let layout = UICollectionViewFlowLayout()
layout.sectionHeadersPinToVisibleBounds = true
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
super.init(collectionViewLayout: layout)
Hay un error en la publicación de cocotouch. Cuando no hay elementos en la sección y el pie de página de la sección se configura para que no se muestre, el encabezado de la sección saldrá de la vista de colección y el usuario no podrá verla.
De hecho, cambio:
if (numberOfItemsInSection > 0) {
firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
} else {
firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
atIndexPath:lastObjectIndexPath];
}
dentro:
if (numberOfItemsInSection > 0) {
firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
} else {
firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
atIndexPath:lastObjectIndexPath];
if (lastObjectAttrs == nil) {
lastObjectAttrs = firstObjectAttrs;
}
}
resolverá este problema
He añadido una muestra en github que es bastante simple, creo.
Básicamente, la estrategia es proporcionar un diseño personalizado que invalide el cambio de límites y proporcionar atributos de diseño para la vista suplementaria que abraza los límites actuales. Como otros han sugerido. Espero que el código sea útil.
Implemente los siguientes métodos delegados:
– collectionView:layout:sizeForItemAtIndexPath:
– collectionView:layout:insetForSectionAtIndex:
– collectionView:layout:minimumLineSpacingForSectionAtIndex:
– collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
– collectionView:layout:referenceSizeForHeaderInSection:
– collectionView:layout:referenceSizeForFooterInSection:
En su controlador de vista que tiene su método :cellForItemAtIndexPath
(simplemente devuelva los valores correctos). O bien, en lugar de utilizar los métodos delegados, también puede establecer estos valores directamente en su objeto de diseño, por ejemplo [layout setItemSize:size];
.
El uso de cualquiera de estos métodos le permitirá establecer su configuración en Código en lugar de IB, ya que se eliminan cuando configura un Diseño personalizado. ¡Recuerde agregar <UICollectionViewDelegateFlowLayout>
a su archivo .h también!
Cree una nueva Subclase de UICollectionViewFlowLayout
, UICollectionViewFlowLayout
como desee y asegúrese de que el archivo H tenga:
#import <UIKit/UIKit.h>
@interface YourSubclassNameHere : UICollectionViewFlowLayout
@end
Dentro del archivo de implementación, asegúrese de que tenga lo siguiente:
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
UICollectionView * const cv = self.collectionView;
CGPoint const contentOffset = cv.contentOffset;
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section];
}
}
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[missingSections removeIndex:layoutAttributes.indexPath.section];
}
}
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[answer addObject:layoutAttributes];
}];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
NSInteger section = layoutAttributes.indexPath.section;
NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];
NSIndexPath *firstCellIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
UICollectionViewLayoutAttributes *firstObjectAttrs;
UICollectionViewLayoutAttributes *lastObjectAttrs;
if (numberOfItemsInSection > 0) {
firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
} else {
firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
atIndexPath:lastObjectIndexPath];
}
CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);
CGPoint origin = layoutAttributes.frame.origin;
origin.y = MIN(
MAX(
contentOffset.y + cv.contentInset.top,
(CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)
),
(CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
}
}
return answer;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
Seleccione "Personalizar" en el Creador de interfaces para el Diseño de flujo, elija su Clase "YourSubclassNameHere" que acaba de crear. ¡Y correr!
(Nota: el código anterior puede no respetar los valores de contentInset.bottom, o especialmente los objetos de pie de página grandes o pequeños, o las colecciones que tienen 0 objetos pero no el pie de página).
Me encontré con el mismo problema y encontré esto en mis resultados de google. Primero, me gustaría agradecer a cocotutch por compartir su solución. Sin embargo, quería que mi UICollectionView se desplazara horizontalmente y los encabezados se pegaran a la izquierda de la pantalla, así que tuve que cambiar la solución un poco.
Básicamente acabo de cambiar esto:
CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);
CGPoint origin = layoutAttributes.frame.origin;
origin.y = MIN(
MAX(
contentOffset.y,
(CGRectGetMinY(firstCellAttrs.frame) - headerHeight)
),
(CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
a esto:
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);
CGPoint origin = layoutAttributes.frame.origin;
origin.y = MIN(
MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)),
(CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
} else {
CGFloat headerWidth = CGRectGetWidth(layoutAttributes.frame);
CGPoint origin = layoutAttributes.frame.origin;
origin.x = MIN(
MAX(contentOffset.x, (CGRectGetMinX(firstCellAttrs.frame) - headerWidth)),
(CGRectGetMaxX(lastCellAttrs.frame) - headerWidth)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
}
Ver: https://gist.github.com/vigorouscoding/5155703 o http://www.vigorouscoding.com/2013/03/uicollectionview-with-sticky-headers/
Si tiene una única vista de encabezado que desea anclar en la parte superior de su UICollectionView, aquí hay una forma relativamente simple de hacerlo. Tenga en cuenta que esto debe ser lo más simple posible: asume que está usando un solo encabezado en una sola sección.
//Override UICollectionViewFlowLayout class
@interface FixedHeaderLayout : UICollectionViewFlowLayout
@end
@implementation FixedHeaderLayout
//Override shouldInvalidateLayoutForBoundsChange to require a layout update when we scroll
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
//Override layoutAttributesForElementsInRect to provide layout attributes with a fixed origin for the header
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *result = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
//see if there''s already a header attributes object in the results; if so, remove it
NSArray *attrKinds = [result valueForKeyPath:@"representedElementKind"];
NSUInteger headerIndex = [attrKinds indexOfObject:UICollectionElementKindSectionHeader];
if (headerIndex != NSNotFound) {
[result removeObjectAtIndex:headerIndex];
}
CGPoint const contentOffset = self.collectionView.contentOffset;
CGSize headerSize = self.headerReferenceSize;
//create new layout attributes for header
UICollectionViewLayoutAttributes *newHeaderAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
CGRect frame = CGRectMake(0, contentOffset.y, headerSize.width, headerSize.height); //offset y by the amount scrolled
newHeaderAttributes.frame = frame;
newHeaderAttributes.zIndex = 1024;
[result addObject:newHeaderAttributes];
return result;
}
@end
Si ya configuró el flujo en Storyboard
o archivo Xib
, intente esto,
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionHeadersPinToVisibleBounds = true
VCollectionViewGridLayout hace encabezados adhesivos. Es un diseño de cuadrícula simple de desplazamiento vertical basado en TLIndexPathTools . Intente ejecutar el proyecto de ejemplo Sticky Headers .
Este diseño también tiene un comportamiento de animación de actualización por lotes mucho mejor que UICollectionViewFlowLayout
. Hay un par de ejemplos de proyectos proporcionados que le permiten alternar entre los dos diseños para demostrar la mejora.