UICollectionView Decoration View

¿Alguien ha implementado una vista de decoración para el iOS 6 UICollectionView? Es imposible encontrar un tutorial sobre cómo implementar una vista de decoración en la web. Básicamente en mi aplicación tengo múltiples secciones, y solo quería mostrar una vista de decoración detrás de cada sección. Esto debería ser simple de implementar, pero no estoy teniendo suerte. Esto me está volviendo loco … Gracias.

Aquí hay un tutorial de vista de decoración de layout de vista de colección en Swift (esto es Swift 3, semilla 6 de Xcode 8).


Las vistas de decoración no son una característica de UICollectionView; esencialmente pertenecen a UICollectionViewLayout. Ningún método UICollectionView (o delegado o methods de origen de datos) menciona vistas de decoración. UICollectionView no sabe nada de ellos; Simplemente hace lo que se dice.

Para suministrar vistas de decoración, necesitará una subclass UICollectionViewLayout; esta subclass es libre de definir sus propias properties y delegar methods de protocolo que personalicen cómo se configuran las vistas de decoración, pero eso depende totalmente de usted.

Para ilustrar, subclare UICollectionViewFlowLayout para imponer una label de título en la parte superior del rectángulo de contenido de la vista de colección. Este es probablemente un uso tonto de una vista de decoración, pero ilustra perfectamente los principios básicos. Para simplificar, empezaré codificando todo, lo que no le permitirá al cliente personalizar ningún aspecto de esta vista.

Hay cuatro pasos para implementar una vista de decoración en una subclass de layout:

  1. Defina una subclass UICollectionReusableView.

  2. Registre la subclass UICollectionReusableView con el layout ( no la vista de colección), llamando a register(_:forDecorationViewOfKind:) . El inicializador del layout es un buen lugar para hacer esto.

  3. Implementar layoutAttributesForDecorationView(ofKind:at:) para devolver los attributes de layout que posicionan UICollectionReusableView. Para build los attributes de layout, init(forDecorationViewOfKind:with:) y configure los attributes.

  4. layoutAttributesForElements(in:) para que el resultado de layoutAttributesForDecorationView(ofKind:at:) se incluya en la matriz devuelta.

El último paso es qué hace que aparezca la vista de decoración en la vista de colección. Cuando la vista de colección llama layoutAttributesForElements(in:) , encuentra que la matriz resultante incluye attributes de layout para una vista de decoración de un tipo específico. La vista de colección no sabe nada acerca de las vistas de decoración, por lo que regresa al layout, pidiendo una instancia real de este tipo de vista de decoración. Has registrado este tipo de vista de decoración para que corresponda a tu subclass UICollectionReusableView, por lo que tu subclass UICollectionReusableView se instancia y esa instancia se devuelve, y la vista de colección lo posiciona de acuerdo con los attributes de layout.

Entonces sigamos los pasos. Defina la subclass UICollectionReusableView:

 class MyTitleView : UICollectionReusableView { weak var lab : UILabel! override init(frame: CGRect) { super.init(frame:frame) let lab = UILabel(frame:self.bounds) self.addSubview(lab) lab.autoresizingMask = [.flexibleWidth, .flexibleHeight] lab.font = UIFont(name: "GillSans-Bold", size: 40) lab.text = "Testing" self.lab = lab } requinetworking init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Ahora recurrimos a nuestra subclass UICollectionViewLayout, a la que llamaré MyFlowLayout. Registramos MyTitleView en el inicializador del layout; También he definido algunas properties privadas que necesitaré para los pasos restantes:

 private let titleKind = "title" private let titleHeight : CGFloat = 50 private var titleRect : CGRect { return CGRect(10,0,200,self.titleHeight) } override init() { super.init() self.register(MyTitleView.self, forDecorationViewOfKind:self.titleKind) } 

Implementar layoutAttributesForDecorationView(ofKind:at:) :

 override func layoutAttributesForDecorationView( ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { if elementKind == self.titleKind { let atts = UICollectionViewLayoutAttributes( forDecorationViewOfKind:self.titleKind, with:indexPath) atts.frame = self.titleRect return atts } return nil } 

Anular layoutAttributesForElements(in:) ; la ruta del índice aquí es arbitraria (lo ignoré en el código anterior):

 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var arr = super.layoutAttributesForElements(in: rect)! if let decatts = self.layoutAttributesForDecorationView( ofKind:self.titleKind, at: IndexPath(item: 0, section: 0)) { if rect.contains(decatts.frame) { arr.append(decatts) } } return arr } 

¡Esto funciona! Aparece una label de título que dice "Prueba" en la parte superior de la vista de la colección.


Ahora mostraré cómo hacer que la label sea personalizable. En lugar del título "Pruebas", permitiremos que el cliente establezca una propiedad que determine el título. Daré a mi subclass de layout una propiedad de title público:

 class MyFlowLayout : UICollectionViewFlowLayout { var title = "" // ... } 

Quien use este layout debería establecer esta propiedad. Por ejemplo, suponga que esta vista de colección muestra los 50 estados de EE. UU .:

 func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) { flow.headerReferenceSize = CGSize(50,50) flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10) (flow as? MyFlowLayout)?.title = "States" // * } 

Ahora llegamos a un curioso acertijo. Nuestro layout tiene una propiedad de title , cuyo valor debe comunicarse de alguna manera a nuestra instancia MyTitleView. ¿Pero cuándo puede suceder eso? No estamos a cargo de instanciar MyTitleView; Ocurre automáticamente, cuando la vista de colección solicita la instancia detrás de escena. No hay momento en que se encuentren la instancia MyFlowLayout y la instancia MyTitleView.

La solución es utilizar los attributes de layout como un postro. MyFlowLayout nunca cumple con MyTitleView, pero crea el object de attributes de layout que se pasa a la vista de colección para configurar MyFlowLayout. Entonces el object de attributes de layout es como un sobre. Al subclasificar UICollectionViewLayoutAttributes, podemos include en ese sobre cualquier información que nos guste, como un título:

 class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes { var title = "" } 

¡Ahí está nuestro sobre! Ahora reescribimos nuestra implementación de layoutAttributesForDecorationView . Cuando instancamos el object de attributes de layout, instancamos nuestra subclass y establecemos su propiedad de title :

 override func layoutAttributesForDecorationView( ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { if elementKind == self.titleKind { let atts = MyTitleViewLayoutAttributes( // * forDecorationViewOfKind:self.titleKind, with:indexPath) atts.title = self.title // * atts.frame = self.titleRect return atts } return nil } 

Finalmente, en MyTitleView, implementamos el método apply(_:) . Esto se llamará cuando la vista de colección configura la vista de decoración, ¡con el object de attributes de layout como su parámetro! Entonces, sacamos el title y lo utilizamos como el text de nuestra label:

 class MyTitleView : UICollectionReusableView { weak var lab : UILabel! // ... the rest as before ... override func apply(_ atts: UICollectionViewLayoutAttributes) { if let atts = atts as? MyTitleViewLayoutAttributes { self.lab.text = atts.title } } } 

Es fácil ver cómo podría ampliar el ejemplo para hacer que las características de la label como la fuente y la altura sean personalizables. Como estamos subclassando UICollectionViewFlowLayout, también podrían ser necesarias algunas modificaciones adicionales para hacer espacio para la vista de decoración al empujar hacia abajo los otros elementos. Además, técnicamente, deberíamos anular isEqual(_:) en MyTitleView para diferenciar entre diferentes títulos. Todo eso se deja como un ejercicio para el lector.

Conseguí esto trabajando con un layout personalizado con lo siguiente:

Cree una subclass de UICollectionReusableView y, por ejemplo, agregue una UIImageView a ella:

 @implementation AULYFloorPlanDecorationViewCell - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { UIImage *backgroundImage = [UIImage imageNamed:@"Layout.png"]; UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; imageView.image = backgroundImage; [self addSubview:imageView]; } return self; } @end 

Luego, en su controller en viewDidLoad, registre esta subclass con el siguiente código (reemplace el código con su layout personalizado)

 AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout; [automationLayout registerClass:[AULYFloorPlanDecorationViewCell class] forDecorationViewOfKind:@"FloorPlan"]; 

En su layout personalizado, ejecute los siguientes methods (o similares):

 - (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath]; layoutAttributes.frame = CGRectMake(0.0, 0.0, self.collectionViewContentSize.width, self.collectionViewContentSize.height); layoutAttributes.zIndex = -1; return layoutAttributes; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *allAttributes = [[NSMutableArray alloc] initWithCapacity:4]; [allAttributes addObject:[self layoutAttributesForDecorationViewOfKind:@"FloorPlan" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]]; for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [allAttributes addObject:layoutAttributes]; } return allAttributes; } 

Parece que no hay documentation para ello, pero el siguiente documento me guió por el path correcto: Collection View Programming Guide for iOS

ACTUALIZACIÓN: Probablemente sea mejor subclass UICollectionReusableView para una vista de decoración en lugar de UICollectionViewCell

A continuación, le indicamos cómo hacerlo en MonoTouch:

 public class DecorationView : UICollectionReusableView { private static NSString classId = new NSString ("DecorationView"); public static NSString ClassId { get { return classId; } } UIImageView blueMarble; [Export("initWithFrame:")] public DecorationView (RectangleF frame) : base(frame) { blueMarble = new UIImageView (UIImage.FromBundle ("bluemarble.png")); AddSubview (blueMarble); } } public class SimpleCollectionViewController : UICollectionViewController { public override void ViewDidLoad () { base.ViewDidLoad (); //Register the cell class (code for AnimalCell snipped) CollectionView.RegisterClassForCell (typeof(AnimalCell), AnimalCell.ClassId); //Register the supplementary view class (code for SideSupplement snipped) CollectionView.RegisterClassForSupplementaryView (typeof(SideSupplement), UICollectionElementKindSection.Header, SideSupplement.ClassId); //Register the decoration view CollectionView.CollectionViewLayout.RegisterClassForDecorationView (typeof(DecorationView), DecorationView.ClassId); } //...snip... } public class LineLayout : UICollectionViewFlowLayout { public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (RectangleF rect) { var array = base.LayoutAttributesForElementsInRect (rect); /* ...snip content relating to cell layout... */ //Add decoration view var attributesWithDecoration = new List<UICollectionViewLayoutAttributes> (array.Length + 1); attributesWithDecoration.AddRange (array); var decorationIndexPath = NSIndexPath.FromIndex (0); var decorationAttributes = LayoutAttributesForDecorationView (DecorationView.ClassId, decorationIndexPath); attributesWithDecoration.Add (decorationAttributes); var extended = attributesWithDecoration.ToArray<UICollectionViewLayoutAttributes> (); return extended; } public override UICollectionViewLayoutAttributes LayoutAttributesForDecorationView (NSString kind, NSIndexPath indexPath) { var layoutAttributes = UICollectionViewLayoutAttributes.CreateForDecorationView (kind, indexPath); layoutAttributes.Frame = new RectangleF (0, 0, CollectionView.ContentSize.Width, CollectionView.ContentSize.Height); layoutAttributes.ZIndex = -1; return layoutAttributes; } //...snip... } 

Con un resultado final similar a: introduzca la descripción de la imagen aquí

si desea agregar decorationview (Encabezado / pie de página) en la vista de colección

por favor marque este enlace en este encabezado y pie de página, ambos agregados como DecorationView

http://www.appcoda.com/ios-collection-view-tutorial/