Has anyone implemented a decoration view for the iOS 6 UICollectionView? It's impossible to find any tutorial on implementing a decoration view on the web. Basically in my app I have multiple sections, and I just wanted to display a decoration view behind each section. This should be simple to implement but I'm having no luck. This is driving me nuts... Thanks.
-
Have you checked this tutorial http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12 . I am not sure whether they have already done that. But it's worth checking there. – iDev Oct 10 '12 at 02:39
-
sorry but can you explain what you mean by decoration view? – geraldWilliam Oct 10 '12 at 04:21
-
@geraldWilliam, Check the above link in my comment. They have a nice tutorial which explains. – iDev Oct 10 '12 at 04:51
-
1@ACB There's no decoration view in that tutorial, just header and cells. – Guido Hendriks Oct 10 '12 at 13:19
-
3Yup, the tutorial at raywnderlich has no decoration view, even though the comp they're using has a decoration view shown. Maybe they decided not to show the decoration view in the finished project at the last minute. Wow...decoration view is a big mystery. I wish apple would provide some sample code on this subject. – vtruong Oct 10 '12 at 13:58
-
You might wanna check the WWDC sessions. I did a few months ago and remember that they were talking about that, but don't remember details. – Guido Hendriks Oct 10 '12 at 14:57
-
Note that the standard flow layout class supports only section header and section footer views and no decoration views. To support decoration views you need to subclass UICollectionViewFlowLayout. – masam Nov 12 '12 at 10:25
4 Answers
Here's a collection view layout decoration view tutorial in Swift (this is Swift 3, Xcode 8 seed 6).
Decoration views are not a UICollectionView feature; they essentially belong to the UICollectionViewLayout. No UICollectionView methods (or delegate or data source methods) mention decoration views. The UICollectionView knows nothing about them; it simply does what it is told.
To supply any decoration views, you will need a UICollectionViewLayout subclass; this subclass is free to define its own properties and delegate protocol methods that customize how its decoration views are configured, but that's entirely up to you.
To illustrate, I'll subclass UICollectionViewFlowLayout to impose a title label at the top of the collection view's content rectangle. This is probably a silly use of a decoration view, but it illustrates the basic principles perfectly. For simplicity, I'll start by hard-coding the whole thing, giving the client no ability to customize any aspect of this view.
There are four steps to implementing a decoration view in a layout subclass:
Define a UICollectionReusableView subclass.
Register the UICollectionReusableView subclass with the layout (not the collection view), by calling
register(_:forDecorationViewOfKind:)
. The layout's initializer is a good place to do this.Implement
layoutAttributesForDecorationView(ofKind:at:)
to return layout attributes that position the UICollectionReusableView. To construct the layout attributes, callinit(forDecorationViewOfKind:with:)
and configure the attributes.Override
layoutAttributesForElements(in:)
so that the result oflayoutAttributesForDecorationView(ofKind:at:)
is included in the returned array.
The last step is what causes the decoration view to appear in the collection view. When the collection view calls layoutAttributesForElements(in:)
, it finds that the resulting array includes layout attributes for a decoration view of a specified kind. The collection view knows nothing about decoration views, so it comes back to the layout, asking for an actual instance of this kind of decoration view. You've registered this kind of decoration view to correspond to your UICollectionReusableView subclass, so your UICollectionReusableView subclass is instantiated and that instance is returned, and the collection view positions it in accordance with the layout attributes.
So let's follow the steps. Define the UICollectionReusableView subclass:
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")
}
}
Now we turn to our UICollectionViewLayout subclass, which I'll call MyFlowLayout. We register MyTitleView in the layout's initializer; I've also defined some private properties that I'll need for the remaining steps:
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)
}
Implement 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
}
Override layoutAttributesForElements(in:)
; the index path here is arbitrary (I ignored it in the preceding code):
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
}
This works! A title label reading ``Testing'' appears at the top of the collection view.
Now I'll show how to make the label customizable. Instead of the title "Testing," we'll allow the client to set a property that determines the title. I'll give my layout subclass a public title
property:
class MyFlowLayout : UICollectionViewFlowLayout {
var title = ""
// ...
}
Whoever uses this layout should set this property. For example, suppose this collection view is displaying the 50 U.S. states:
func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) {
flow.headerReferenceSize = CGSize(50,50)
flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10)
(flow as? MyFlowLayout)?.title = "States" // *
}
We now come to a curious puzzle. Our layout has a title
property, the value of which needs to be communicated somehow to our MyTitleView instance. But when can that possibly happen? We are not in charge of instantiating MyTitleView; it happens automatically, when the collection view asks for the instance behind the scenes. There is no moment when the MyFlowLayout instance and the MyTitleView instance meet.
The solution is to use the layout attributes as a messenger. MyFlowLayout never meets MyTitleView, but it does create the layout attributes object that gets passed to the collection view to configure MyFlowLayout. So the layout attributes object is like an envelope. By subclassing UICollectionViewLayoutAttributes, we can include in that envelope any information we like — such as a title:
class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
var title = ""
}
There's our envelope! Now we rewrite our implementation of layoutAttributesForDecorationView
. When we instantiate the layout attributes object, we instantiate our subclass and set its title
property:
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
}
Finally, in MyTitleView, we implement the apply(_:)
method. This will be called when the collection view configures the decoration view — with the layout attributes object as its parameter! So we pull out the title
and use it as the text of our 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
}
}
}
It's easy to see how you might extend the example to make such label features as font and height customizable. Since we are subclassing UICollectionViewFlowLayout, some further modifications might also be needed to make room for the decoration view by pushing down the other elements. Also, technically, we should override isEqual(_:)
in MyTitleView to differentiate between different titles. All of that is left as an exercise for the reader.

- 515,959
- 87
- 875
- 1,141
-
2@matt Thank you so much for the section about using the layout attributes as a messenger to communicate with the decoration view! It's an interesting API - you do not directly instantiate or have a reference to the decoration view. – s.ka Mar 01 '17 at 21:30
-
-
I am doing for SupplementaryViews act as header is each section. I am using UICollectionViewLayout. Everything is working fine. But, unable to add header. **viewForSupplementaryElementOfKind** this method is in UICollectionViewcontroller. This method is not getting called when we going for CustomLayout. When we are going for FlowLayout, section header is Visible. Kindly help me on this. I have searched a lot. But, couldn't find. – McDonal_11 Feb 09 '18 at 05:06
-
Now we turn to our **UICollectionViewLayout** subclass, which I'll call MyFlowLayout. But, "class MyFlowLayout : **UICollectionViewFlowLayout**" . Little confused. UICollectionViewFlowLayout or UICollectionViewLayout ?? – McDonal_11 Feb 09 '18 at 05:22
-
-
Thanks! I'm so disappointed of Apple's docs, which often too unclear or even incomplete, it makes me crazy. – surfrider Jul 04 '18 at 21:44
-
I found I issue. Instead of `if rect.contains(decatts.frame)` you should use `if rect.intersects(decatts.frame)`. – Marián Černý Aug 13 '18 at 12:26
-
-
@matt First of all thank you so much the post. I have followed through all the steps that you have outlined. I have come to know that `layoutAttributesForDecorationView` never gets called nor does `layoutAttributesForItem`. Only `layoutAttributesForElements` gets called. What could be the reason? Have been battling with this for hours now but no use. – Adeel Miraj Sep 12 '18 at 11:15
-
There is a mistake in: override func layoutAttributesForElements(..): never use row:0 and section:0 for your decoration view. Otherwise early or later it will crash since one of your cells will have the same index path. I am using 'row' starting from 1000 to avoid it. – yoooriii Mar 24 '19 at 13:22
-
@yoooriii I don't understand why having a cell with the same index path would cause a crash. Of course I have a cell with index path 0,0; it's the first cell. If I was going to crash because of that wouldn't I crash immediately? – matt Mar 24 '19 at 15:04
-
It does not crash immediately, but it crashes from time to time, sometimes when scrolling. The point is: the collection view cannot have 2 different items (a cell view and a decoration view) with the same index path. I discovered it just a couple of days ago, before that my app worked well and I did not know about such a thing. Since I set my index path to (item:1000, section:0) I cannot reproduce the crash.That's it. Just experience. – yoooriii Mar 25 '19 at 22:11
-
@yoooriii Obviously I'm extremely interested! I want to get this right. I'm grateful for the info. I just would like to have seen such a crash, or the crash log, myself in order to understand the issue (and why I have not encountered it). – matt Mar 25 '19 at 22:32
-
1If you subclass and implement any custom layout attributes, you must also override the inherited isEqual: method to compare the values of your properties. In iOS 7 and later, the collection view does not apply layout attributes if those attributes have not changed. It determines whether the attributes have changed by comparing the old and new attribute objects using the isEqual: method – Igor Vasilev Jan 24 '21 at 22:57
-
I got this working with a custom layout with the following:
Create a subclass of UICollectionReusableView and for example add an UIImageView to it:
@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
Then in your controller in viewDidLoad register this subclass with the following code (replace code with your custom layout)
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
[automationLayout registerClass:[AULYFloorPlanDecorationViewCell class] forDecorationViewOfKind:@"FloorPlan"];
In your custom layout then implement the following methods (or similar):
- (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;
}
There seems to be no documentation for it, but the following document got me on the right track: Collection View Programming Guide for iOS
UPDATE: It is probably better to subclass UICollectionReusableView for a decoration view instead of UICollectionViewCell
-
Thanks this put me on the right track. Looks like you're just displaying a single decoration view for section 0. My collection view has multiple sections. I still need to figure out how to display the same decoration view for each section with different number of items. – vtruong Oct 10 '12 at 20:02
-
Yes I am only displaying one decoration view, but you could display further decoration views by adding further lines in the layoutAttributesForElementsInRect: `[allAttributes addObject:[self layoutAttributesForDecorationViewOfKind:@"FloorPlan" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]];` (and you can add further for section 2, 3, 4, etc. You then have to make sure to set the layout attributes so that the bounds are correct for that section. To find out the number of sections and how many elements they contain you should be able to access `self.collectionView.dataSource` – dominikk Oct 11 '12 at 05:31
-
1Do I have to subclass `UICollectionViewLayout` in order to use Decoration Views? Can it be done from FlowLayout? – Drew C Mar 25 '13 at 23:03
-
1@DrewC you can subclass UICollectionViewFlowLayout and override the methods mentioned above. – SimpsOff May 29 '13 at 16:14
-
1Why is this answer not accepted? It is complete and correct. The only thing it doesn't explain is how to communicate custom layout attributes from the layout to the decoration view, and that's easy enough to figure out. – matt Aug 29 '16 at 20:58
Here's how to do it in 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...
}
With an end result similar to:

- 8,484
- 1
- 41
- 75
In my case : I wanted to upgrade from UITableView to UICollectionView.
uitableview sections >>> supplementary views
uitableview headerView >>> decoration view
In my case I felt subclassing layout and do other stuff, it's "too much" for just simple "headerView" (decoration)
So my solution was just to create the headerview (not section) as first cell and section 1 as the first section ( section 0 was size of zero)

- 2,259
- 2
- 34
- 55