Quelqu'un a-t-il mis en place une vue de décoration pour iOS 6 UICollectionView? Il est impossible de trouver un didacticiel sur la mise en œuvre d’une vue de décoration sur le Web. En gros, dans mon application, j'ai plusieurs sections et je voulais simplement afficher une vue de la décoration derrière chaque section. Cela devrait être simple à mettre en œuvre mais je n’ai aucune chance. Cela me rend fou ... Merci.
Voici un didacticiel de vue de décoration de présentation de collectiondans Swift (il s’agit de Swift 3, Xcode 8 seed 6).
Les vues de décoration ne constituent pas une fonctionnalité UICollectionView; ils appartiennent essentiellement à UICollectionViewLayout. Aucune méthode UICollectionView (ni méthode déléguée ni source de données) ne mentionne les vues de décoration. UICollectionView n'en sait rien. il fait simplement ce qu'on lui dit.
Pour fournir des vues de décoration, vous aurez besoin d’une sous-classe UICollectionViewLayout; cette sous-classe est libre de définir ses propres propriétés et de déléguer des méthodes de protocole qui personnalisent la configuration de ses vues de décoration, mais cela vous appartient entièrement.
Pour illustrer cela, je vais sous-classer UICollectionViewFlowLayout pour imposer une étiquette de titre en haut du rectangle de contenu de la vue de collection. C'est probablement une utilisation idiote d'une vue de décoration, mais elle illustre parfaitement les principes de base. Pour des raisons de simplicité, je commencerai par coder en dur, sans donner au client la possibilité de personnaliser aucun aspect de cette vue.
L'implémentation d'une vue de décoration dans une sous-classe de présentation nécessite quatre étapes:
Définissez une sous-classe UICollectionReusableView.
Enregistrez la sous-classe UICollectionReusableView avec la présentation (not la vue de collection) en appelant register(_:forDecorationViewOfKind:)
. L'initialiseur de la mise en page est un bon endroit pour le faire.
Implémentez layoutAttributesForDecorationView(ofKind:at:)
pour renvoyer les attributs de format qui positionnent UICollectionReusableView. Pour construire les attributs de présentation, appelez init(forDecorationViewOfKind:with:)
et configurez les attributs.
Remplacez layoutAttributesForElements(in:)
pour que le résultat de layoutAttributesForDecorationView(ofKind:at:)
soit inclus dans le tableau renvoyé.
La dernière étape explique ce qui fait apparaître la vue de décoration dans la vue de collection. Lorsque la vue de collection appelle layoutAttributesForElements(in:)
, elle constate que le tableau résultant inclut des attributs de présentation pour une vue de décoration d'un type spécifié. La vue de collection ne connaît rien des vues de décoration, elle revient donc à la mise en page, demandant un exemple concret de ce type de vue de décoration. Vous avez enregistré ce type de vue de décoration pour correspondre à votre sous-classe UICollectionReusableView. Par conséquent, votre sous-classe UICollectionReusableView est instanciée, cette instance est renvoyée et la vue de collection le positionne conformément aux attributs de présentation.
Alors suivons les étapes. Définissez la sous-classe 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")
}
}
Nous passons maintenant à notre sous-classe UICollectionViewLayout, que j'appellerai MyFlowLayout. Nous enregistrons MyTitleView dans l'initialiseur de la mise en page; J'ai également défini certaines propriétés privées dont j'ai besoin pour les étapes 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)
}
Implémenter 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
}
Remplacer layoutAttributesForElements(in:)
; le chemin d'index ici est arbitraire (je l'ai ignoré dans le code précédent):
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
}
Cela marche! Une étiquette de titre indiquant «Test» apparaît en haut de la vue Collection.
Je vais maintenant montrer comment rendre l'étiquette personnalisable. Au lieu du titre "Testing", nous autoriserons le client à définir une propriété qui détermine le titre. Je vais donner à ma sous-classe layout une propriété publique title
:
class MyFlowLayout : UICollectionViewFlowLayout {
var title = ""
// ...
}
Quiconque utilise cette disposition doit définir cette propriété. Par exemple, supposons que cette vue de collection affiche les 50 états américains:
func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) {
flow.headerReferenceSize = CGSize(50,50)
flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10)
(flow as? MyFlowLayout)?.title = "States" // *
}
Nous arrivons maintenant à un casse-tête curieux. Notre mise en page possède une propriété title
, dont la valeur doit être communiquée d'une manière ou d'une autre à notre instance MyTitleView. Mais quand cela peut-il arriver? Nous ne sommes pas en charge de l'instanciation de MyTitleView; cela se produit automatiquement lorsque la vue de collection demande l'instance en coulisse. Il n'y a pas de moment où l'instance MyFlowLayout et l'instance MyTitleView se rencontrent.
La solution consiste à utiliser les attributs de présentation en tant que messager. MyFlowLayout ne rencontre jamais MyTitleView, mais crée l'objet d'attributs de présentation qui est transmis à la vue de collection pour configurer MyFlowLayout. Ainsi, l'objet d'attributs de mise en page est comme une enveloppe. En sous-classant UICollectionViewLayoutAttributes, nous pouvons inclure dans cette enveloppe les informations souhaitées, telles que le titre:
class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
var title = ""
}
Voilà notre enveloppe! Nous réécrivons maintenant notre implémentation de layoutAttributesForDecorationView
. Lorsque nous instancions l'objet d'attributs de présentation, nous instancions notre sous-classe et définissons sa propriété 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
}
Enfin, dans MyTitleView, nous implémentons la méthode apply(_:)
. Cela sera appelé lorsque la vue de collection configure la vue de décoration - avec l'objet d'attributs de disposition en tant que paramètre! Nous extrayons donc la title
et l’utilisons comme texte de notre étiquette:
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
}
}
}
Il est facile de voir comment vous pouvez étendre l'exemple pour rendre personnalisables des caractéristiques telles que la police et la hauteur. Puisque nous sous-classons UICollectionViewFlowLayout, certaines modifications supplémentaires pourraient également être nécessaires pour faire de la place à la vue de la décoration en abaissant les autres éléments. De plus, techniquement, nous devrions remplacer isEqual(_:)
dans MyTitleView pour différencier les différents titres. Tout cela reste comme un exercice pour le lecteur.
Je travaille avec une mise en page personnalisée avec les éléments suivants:
Créez une sous-classe de UICollectionReusableView et ajoutez-y par exemple un UIImageView:
@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
Ensuite, dans votre contrôleur dans viewDidLoad, inscrivez cette sous-classe avec le code suivant (remplacez le code par votre présentation personnalisée)
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
[automationLayout registerClass:[AULYFloorPlanDecorationViewCell class] forDecorationViewOfKind:@"FloorPlan"];
Dans votre mise en page personnalisée, implémentez les méthodes suivantes (ou similaires):
- (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;
}
Il semble n'y avoir aucune documentation à ce sujet, mais le document suivant m'a permis de me mettre sur la bonne voie: Guide de programmation Collection View pour iOS
UPDATE: Il est probablement préférable de sous-classer UICollectionReusableView pour une vue de décoration au lieu de UICollectionViewCell.
Voici comment faire dans 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...
}
Avec un résultat final similaire à:
Dans mon cas: Je voulais passer de UITableView à UICollectionView.
uitableview sections >>> vues supplémentaires
uitableview headerView >>> view decoration
Dans mon cas, j’ai eu l’impression de sous-classer et de faire d’autres choses, c’est "trop" Pour un simple "headerView" (décoration)
Ma solution consistait donc simplement à créer la tête (pas de section) en tant que première cellule Et section 1 en tant que première section (la section 0 avait une taille de zéro)