ios - tutorial - uicollectionview swift 4
UICollectionView Decoration View (5)
¿Alguien ha implementado una vista de decoración para 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 varias secciones, y solo quería mostrar una vista de decoración detrás de cada sección. Esto debería ser fácil de implementar pero no estoy teniendo suerte. Esto me está volviendo loco ... Gracias.
Aquí está 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:
Aquí hay un tutorial de vista de decoración de diseño de vista de colección en Swift (esto es Swift 3, Xcode 8 seed 6).
Las vistas de decoración no son una característica de UICollectionView; esencialmente pertenecen a la UICollectionViewLayout. Ningún método UICollectionView (o métodos de fuente de datos o delegado) menciona vistas de decoración. El UICollectionView no sabe nada de ellos; simplemente hace lo que se le dice.
Para suministrar vistas de decoración, necesitará una subclase UICollectionViewLayout; esta subclase es libre de definir sus propias propiedades y de los métodos de protocolo delegado que personalizan la configuración de sus vistas de decoración, pero eso depende totalmente de usted.
Para ilustrar, subclasificaré UICollectionViewFlowLayout para imponer una etiqueta 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, comenzaré por codificar todo el asunto, sin que el cliente tenga la posibilidad de personalizar ningún aspecto de esta vista.
Hay cuatro pasos para implementar una vista de decoración en una subclase de diseño:
Defina una subclase UICollectionReusableView.
Registre la subclase UICollectionReusableView con el diseño ( no la vista de colección), llamando al
register(_:forDecorationViewOfKind:)
. El inicializador del diseño es un buen lugar para hacer esto.Implementar
layoutAttributesForDecorationView(ofKind:at:)
para devolver los atributos de diseño que posicionan el UICollectionReusableView. Para construir los atributos de diseño, llame ainit(forDecorationViewOfKind:with:)
y configure los atributos.Reemplace
layoutAttributesForElements(in:)
para que el resultado delayoutAttributesForDecorationView(ofKind:at:)
se incluya en la matriz devuelta.
El último paso es lo que hace que la vista de decoración aparezca en la vista de colección. Cuando la vista de colección llama a layoutAttributesForElements(in:)
, encuentra que la matriz resultante incluye atributos de diseño 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 vuelve al diseño y solicita una instancia real de este tipo de vista de decoración. Ha registrado este tipo de vista de decoración para corresponder a su subclase UICollectionReusableView, por lo que su subclase UICollectionReusableView se crea una instancia y esa instancia se devuelve, y la vista de colección la posiciona de acuerdo con los atributos de diseño.
Así que sigamos los pasos. Defina la subclase 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
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ahora pasamos a nuestra subclase UICollectionViewLayout, que llamaré MyFlowLayout. Registramos MyTitleView en el inicializador del diseño; También he definido algunas propiedades 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
}
Reemplace layoutAttributesForElements(in:)
; La ruta del índice aquí es arbitraria (la 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.intersects(decatts.frame) {
arr.append(decatts)
}
}
return arr
}
¡Esto funciona! En la parte superior de la vista de colección aparece una etiqueta de título que dice "Pruebas".
Ahora te mostraré cómo hacer la etiqueta personalizable. En lugar del título "Pruebas", permitiremos que el cliente establezca una propiedad que determine el título. Le daré a mi subclase de diseño una propiedad de title
público:
class MyFlowLayout : UICollectionViewFlowLayout {
var title = ""
// ...
}
Quien use este diseño debe establecer esta propiedad. Por ejemplo, supongamos 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 rompecabezas. Nuestro diseño tiene una propiedad de title
, cuyo valor debe comunicarse de alguna manera a nuestra instancia de MyTitleView. ¿Pero cuándo puede suceder eso? No nos encargamos de instanciar MyTitleView; sucede automáticamente, cuando la vista de colección solicita la instancia detrás de escena. No hay un momento en que la instancia de MyFlowLayout y la instancia de MyTitleView se encuentren.
La solución es utilizar los atributos de diseño como un mensajero. MyFlowLayout nunca cumple con MyTitleView, pero crea el objeto de atributos de diseño que se pasa a la vista de colección para configurar MyFlowLayout. Así que el objeto de atributos de diseño es como un sobre. Al subclasificar UICollectionViewLayoutAttributes, podemos incluir en ese sobre cualquier información que nos guste, como un título:
class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
var title = ""
}
¡Aquí está nuestro sobre! Ahora reescribimos nuestra implementación de layoutAttributesForDecorationView
. Cuando creamos una instancia del objeto de atributos de diseño, creamos una instancia de nuestra subclase 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(_:)
. Se llamará cuando la vista de colección configure la vista de decoración, ¡con el objeto de atributos de diseño como su parámetro! Así que sacamos el title
y lo usamos como el texto de nuestra etiqueta:
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 puede extender el ejemplo para hacer que las características de la etiqueta como la fuente y la altura se puedan personalizar. Ya que estamos subclasificando UICollectionViewFlowLayout, algunas modificaciones adicionales también podrían ser necesarias para hacer espacio para la vista de decoración al presionar los otros elementos. Además, técnicamente, deberíamos anular isEqual(_:)
en MyTitleView para diferenciar entre diferentes títulos. Todo eso queda como un ejercicio para el lector.
Conseguí esto trabajando con un diseño personalizado con lo siguiente:
Cree una subclase de UICollectionReusableView y, por ejemplo, agregue un 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 controlador en viewDidLoad registre esta subclase con el siguiente código (reemplace el código con su diseño personalizado)
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
[automationLayout registerClass:[AULYFloorPlanDecorationViewCell class] forDecorationViewOfKind:@"FloorPlan"];
En su diseño personalizado, a continuación, implemente los siguientes métodos (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 documentación para ello, pero el siguiente documento me guió por el camino correcto: Vista de colección, Guía de programación para iOS
ACTUALIZACIÓN: Probablemente sea mejor subclase UICollectionReusableView para una vista de decoración en lugar de UICollectionViewCell
En mi caso: quería actualizar de UITableView a UICollectionView.
uitableview secciones >>> vistas suplementarias
uitableview headerView >>> vista de decoración
En mi caso sentí el diseño de subclases y hago otras cosas, es "demasiado" para un simple "headerView" (decoración)
Así que mi solución fue crear la vista de cabecera (no la sección) como primera celda y la sección 1 como la primera sección (la sección 0 tenía el tamaño de cero)
si desea agregar decorationview (encabezado / pie de página) en collectionview
por favor revise este enlace en este encabezado y pie de página, ambos agregados como DecorationView