Create a book shelf by using UICollectionView

I'm going to create a book shelf in one of my projects. The basic requirements are as follows:

  • similar to iBooks bookshelf
  • supports both orientations well
  • supports all kinds of iOS devices well (different resolutions)
  • supports deleting and inserting items
  • supports reordering of items by long press gesture
  • shows a hidden logo when first row is pulled down

The UICollectionView is the first option that occurred to me. It easily supports grid cells. So I googled it and found some very useful tutorials:

Bryan Hansen's UICollectionView custom layout tutorial

Mark Pospesel's How to Add a Decoration View to a UICollectionView

LXReorderableCollectionViewFlowLayout

And here is the result: (Please ignore the color inconsistency problem because of the graphics I chose are not perfect yet.)

enter image description here

What I did:

  1. Created a custom layout by creating a class inherited from LXReorderableCollectionViewFlowLayout (for reordering purposes) which inherited from UICollectionFlowLayout
  2. Added a decoration view for showing logo
  3. Added a decoration view for showing the bookshelfs

But I ran into a few problems:

1. I can't scroll at all if the items can be shown in one screen

Then I added the following code, to make the contentsize bigger

- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.collectionView.bounds.size.width,      self.collectionView.bounds.size.height+100);
}

Then I can scroll now. Here I pull down the first row:

enter image description here

You can see the the decoration view for logo is working.

2. But I got the second set of problems when I pull up the last row:

enter image description here

You can see the decoration view is not added at the green box part.

3. The background of decoration view for bookshelf is getting darker and darker. (Please refer to same problem here

4. The book shelf bar sometimes moves when I reorder the items

enter image description here

I list some of the important code here:

- (void)prepareLayout
{
    // call super so flow layout can do all the math for cells, headers, and footers
    [super prepareLayout];
    
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    NSMutableDictionary *shelfLayoutInfo = [NSMutableDictionary dictionary];
    
    // decoration view - emblem
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
    UICollectionViewLayoutAttributes *emblemAttributes =
        [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:[EmblemView kind]
            withIndexPath:indexPath];
    emblemAttributes.frame = [self frameForEmblem:YES];
    dictionary[[EmblemView kind]] = @{indexPath: emblemAttributes};

    // Calculate where shelves go in a vertical layout
    int sectionCount = [self.collectionView numberOfSections];
    
    CGFloat y = 0;
    CGFloat availableWidth = self.collectionViewContentSize.width - (self.sectionInset.left + self.sectionInset.right);
    int itemsAcross = floorf((availableWidth + self.minimumInteritemSpacing) / (self.itemSize.width + self.minimumInteritemSpacing));
    
    for (int section = 0; section < sectionCount; section++)
    {
        y += self.headerReferenceSize.height;
        //y += self.sectionInset.top;
        
        int itemCount = [self.collectionView numberOfItemsInSection:section];
        int rows = ceilf(itemCount/(float)itemsAcross)+1; // add 2 more empty row which doesn't have any data
        for (int row = 0; row < rows; row++)
        {
            indexPath = [NSIndexPath indexPathForItem:row inSection:section];
            shelfLayoutInfo[indexPath] = [NSValue valueWithCGRect:CGRectMake(0,y, self.collectionViewContentSize.width, self.itemSize.height + DECORATION_HEIGHT)];
            y += self.itemSize.height;
            
            if (row < rows - 1)
                y += self.minimumLineSpacing;
        }
        
        y += self.sectionInset.bottom;
        y += self.footerReferenceSize.height;
    }
    
    dictionary[[ShelfView kind]] = shelfLayoutInfo;
    
    self.shelfLayoutInfo = dictionary;
}


- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *attributesArrayInRect = [super layoutAttributesForElementsInRect:rect];
    
    // cell layout info
    for (BookShelfLayoutAttributes *attribs in attributesArrayInRect)
    {
        
        attribs.zIndex = 1;
        CATransform3D t = CATransform3DIdentity;
        t = CATransform3DTranslate(t, 0, 0, 40);
        attribs.transform3D = CATransform3DRotate(t, 15 * M_PI / 180, 1, 0, 0);
    }
    
    // Add our decoration views (shelves)
    NSMutableDictionary* shelfDictionary = self.shelfLayoutInfo[[ShelfView kind]];
    NSMutableArray *newArray = [attributesArrayInRect mutableCopy];
    
    [shelfDictionary enumerateKeysAndObjectsUsingBlock:^(id key, NSValue* obj, BOOL *stop) {
        
        if (CGRectIntersectsRect([obj CGRectValue], rect))
        {
            UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:[ShelfView kind] withIndexPath:key];
            attributes.frame = [obj CGRectValue];
            
            NSLog(@"decorationView rect = %@",NSStringFromCGRect(attributes.frame));
            attributes.zIndex = 0;
            //attributes.alpha = 0.5; // screenshots
            [newArray addObject:attributes];
        }
    }];
    
    attributesArrayInRect = [NSArray arrayWithArray:newArray];
    
    NSMutableDictionary* emblemDictionary = self.shelfLayoutInfo[[EmblemView kind]];
    NSMutableArray *newArray2 = [attributesArrayInRect mutableCopy];
    [emblemDictionary enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *innerStop) {
        if (CGRectIntersectsRect(rect, attributes.frame)) {
            [newArray2 addObject:attributes];
        }
    }];
    
    attributesArrayInRect = [NSArray arrayWithArray:newArray2];
    
    return attributesArrayInRect;
}

I'll appreciate if you're patient enough to read this post and provide any advice or suggestions. And I'll post the complete source code if I can fix all the issues. Thank you in advance.


Solution 1:

I would suggest to check your last row and do the [self.collectionView ScrollEnable:NO] same for first row, set background color clear of collectionView and collectionViewCell and that bookshelf image sets on background view.