11

I've implemented an UICollectionView with a custom layout. It adds a decoration view to the layout. I use the following code to add layout attributes of the decoration view:

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *allAttributes = [super layoutAttributesForElementsInRect:rect];
    return [allAttributes arrayByAddingObject:[self layoutAttributesForDecorationViewOfKind:kHeaderKind atIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]];
}

The data in the collection view is provided by a NSFetchedResultsController.

Now it looked likes it worked fine, but when the collection view is empty, it fails because there's section 0. Tried to use it without an index path, but fails too. Any thoughts on how to use decoration views in an empty UICollectionView? Should be possible since decoration views aren't data-driven.

Guido Hendriks
  • 5,706
  • 3
  • 27
  • 37
  • 1
    Hi ,have you solved this issue , please update your answer so that we can also get some help. – h.kishan Feb 07 '14 at 11:09
  • Can you please post some more code so that i can reproduce the issue easily – Dinesh Kaushik Jul 13 '14 at 19:54
  • 1
    Do you get the same error message in both cases? Because it works for me when I use `nil` as the index path (Xcode 5.1.1, iOS SDK 7.1 Simulator). If it fails silently instead, maybe the `super` call returns `nil` (`UICollectionViewLayout`s default)? – Jörn Eyrich Jul 13 '14 at 22:07

2 Answers2

4

When using a decoration view or a supplemental view not attached to a specific cell, use [NSIndexPath indexPathWithIndex:] to specify the index path. Here is a sample code:

@interface BBCollectionViewLayout : UICollectionViewFlowLayout

@end

@implementation BBCollectionViewLayout

- (void)BBCollectionViewLayout_commonInit {
    [self registerClass:[BBCollectionReusableView class] forDecorationViewOfKind:BBCollectionReusableViewKind];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self BBCollectionViewLayout_commonInit];
    }
    return self;
}

- (id)init {
    self = [super init];
    if (self) {
        [self BBCollectionViewLayout_commonInit];
    }
    return self;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSMutableArray *array = [NSMutableArray arrayWithArray:[super layoutAttributesForElementsInRect:rect]];

    UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForDecorationViewOfKind:BBCollectionReusableViewKind atIndexPath:[NSIndexPath indexPathWithIndex:0]];

    if (CGRectIntersectsRect(rect, attributes.frame)) {
        [array addObject:attributes];
    }

    return array;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:elementKind withIndexPath:indexPath];
    attributes.frame = CGRectMake(0., 60., 44., 44.);
    return attributes;
}

@end
Benoît
  • 99
  • 1
  • 9
  • Yep, using NSIndexPath that’s not based on sections/items seems to work. Makes me a bit uncomfortable though—I’d think that most of these API-s assume that the indexPath matches the section.item format, though this specific use doesn’t seem to have any ill effects. – Jaanus Jul 14 '14 at 14:32
  • @Jaanus If nil was an acceptable value, I think it would be mentioned in the documentation. It's normal to not make a index path with a section + item since the index path is not bound to a specific item. – Benoît Jul 14 '14 at 16:51
0

I created and tested this simple example that seems to work in iOS 7 in all possible situations (0 sections, 1 section with 0 items etc). This is my layout class, subclass of UICollectionViewFlowLayout. The rest of the project is just scaffolding.

#import "JKLayout.h"
#import "JKDecoration.h"

@implementation JKLayout

- (instancetype)init
{
    if (self = [super init]) {
        [self registerClass:[JKDecoration class] forDecorationViewOfKind:@"Decoration"];
    }
    return self;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *allAttributes = [super layoutAttributesForElementsInRect:rect];

    // It’s important to set indexPath to nil. If I had set it to indexPath 0-0, it crashed with InternalInconsistencyException
    // because I was trying to get decoration view for section 0 while there in reality was no section 0
    // I guess if you need to have several decoration views in this case, you’d identify them with a method other than indexpath
    return [allAttributes arrayByAddingObject:[self layoutAttributesForDecorationViewOfKind:@"Decoration" atIndexPath:nil]];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attr = [super layoutAttributesForDecorationViewOfKind:decorationViewKind atIndexPath:indexPath];
    if (!attr) {
        attr = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];
        attr.frame = CGRectMake(0, 200, 100, 100);
    }
    return attr;
}

@end
Jaanus
  • 17,688
  • 15
  • 65
  • 110
  • 1
    Not sure if the use of `nil` as an index path changed things, but with the code above it works perfectly indeed. It's also possible that it didn't work back then, `UICollectionView`s were kinda buggy back then. – Guido Hendriks Jul 14 '14 at 09:44
  • Yep, probably a combination of all of the above. The docs don’t provide much guidance about how to treat NSIndexPath for decoration views, other than saying “It is up to you to decide how to use the indexPath parameter to identify a given decoration view”. – Jaanus Jul 14 '14 at 14:25
  • 1
    Unfortunately this doesn't work anymore since `layoutAttributesForDecorationViewOfKind:indexPath:` expects a `nonnull` index path. – ianolito Apr 21 '16 at 17:35