Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate a UICollectionView cell on selection

I have a basic grid in a UICollectionView. It's a simple 2 column, multiple row layout using the UICollectionViewDelegateFlowLayout. When a cell is selected, I want to dim the background, float the cell to the center of the screen and then have a workflow based on the selected cell. I'm fairly new to UICollectionViews, and I'm not sure of the best way to go about this.

Should I have a custom layout of the UICollectionView for when a cell is selected?

Or is there a way I can animate the selected cell without having to create a new layout

If anyone can just get me going on the right direction, I think I'll be good to research how to execute it.

like image 360
Mark Struzinski Avatar asked Feb 04 '13 19:02

Mark Struzinski


2 Answers

After trying several (and rather hacky) solutions, I solved this by writing my own UICollectionView layout. Previous solutions I attempted:

  • Add a custom UIView overtop the UICollectionView and attempt to fake the original cell moving by overlaying it, dimming the background, and animating the new UIView
  • Animate the cell without creating a brand new layout

I was trying to avoid it, but after I got into coding it, it was a lot less painful than I originally thought.

Here is my code, for anyone interested:

.h:

@interface FMStackedLayout : UICollectionViewLayout

@property(nonatomic,assign) CGPoint     center;
@property(nonatomic,assign) NSInteger   cellCount;

-(id)initWithSelectedCellIndexPath:(NSIndexPath *)indexPath;

@end

.m:

#define ITEM_WIDTH  128.0f
#define ITEM_HEIGHT 180.0f

static NSUInteger       const RotationCount         = 32;
static NSUInteger       const RotationStride        = 3;
static NSUInteger       const PhotoCellBaseZIndex   = 100;

@interface FMStackedLayout ()

@property(strong,nonatomic) NSArray     *rotations;
@property(strong,nonatomic) NSIndexPath *selectedIndexPath;

@end

@implementation FMStackedLayout

#pragma mark - Lifecycle
-(id)initWithSelectedCellIndexPath:(NSIndexPath *)indexPath{
    self = [super init];
    if (self) {
        self.selectedIndexPath = indexPath;
        [self setup];
    }
    return self;
}

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

-(void)setup{
    NSMutableArray *rotations = [NSMutableArray arrayWithCapacity:RotationCount];
    CGFloat percentage = 0.0f;

    for (NSUInteger i = 0; i < RotationCount; i++) {
        // Ensure that each angle is different enough to be seen
        CGFloat newPercentage = 0.0f;
        do {
            newPercentage = ((CGFloat)(arc4random() % 220) - 110) * 0.0001f;
        } while (fabsf(percentage - newPercentage) < 0.006);

        percentage = newPercentage;

        CGFloat angle = 2 * M_PI * (1.0f + percentage);
        CATransform3D transform = CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
        [rotations addObject:[NSValue valueWithCATransform3D:transform]];
    }

    self.rotations = rotations;
}

#pragma mark - Layout
-(void)prepareLayout{
    [super prepareLayout];

    CGSize size = self.collectionView.frame.size;
    self.cellCount = [self.collectionView numberOfItemsInSection:0];
    self.center = CGPointMake(size.width / 2.0, size.height / 2.0);
}

-(CGSize)collectionViewContentSize{
    return self.collectionView.frame.size;
}

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    attributes.size = CGSizeMake(ITEM_WIDTH, ITEM_HEIGHT);
    attributes.center = self.center;
    if (indexPath.item == self.selectedIndexPath.item) {
        attributes.zIndex = 100;
    }else{
        attributes.transform3D = [self transformForPersonViewAtIndex:indexPath];
    }

    return attributes;
}

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSMutableArray *attributes = [NSMutableArray array];
    for (NSInteger i = 0; i < self.cellCount; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i
                                                     inSection:0];
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    }
    return attributes;
}

#pragma mark - Private
-(CATransform3D)transformForPersonViewAtIndex:(NSIndexPath *)indexPath{
    NSInteger offset = (indexPath.section * RotationStride + indexPath.item);
    return [self.rotations[offset % RotationCount] CATransform3DValue];
}

@end

Then, on your UICollectionView, you call

MyLayout *stackedLayout = [[MyLayout alloc] initWithSelectedCellIndexPath:indexPath];
[stackedLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:stackedLayout
                                    animated:YES];

Animations between cells will be handled by the custom layout.

like image 140
Mark Struzinski Avatar answered Sep 23 '22 04:09

Mark Struzinski


Seems to be a multi-part question, so I'll answer part of it.

  1. UICollectionviewCells do not automatically adjust on highlight or selection. It will only tell you when the cell is highlighted or selected, with one exception:

For the most part, the collection view modifies only the properties of a cell to indicate that it is selected or highlighted; it does not change the visual appearance of your cells, with one exception. If a cell’s selectedBackgroundView property contains a valid view, the collection view shows that view when the cell is highlighted or selected.

Otherwise, you must do the visual highlighting manually. Usually by adjusting either the .alpha property of the entire cell, or swapping out the .image property of a background UIImageView in the cell itself using one or more of the following methods, which are accessible in the <UICollectionViewDelegate> Protocol:

collectionView:didDeselectItemAtIndexPath:
collectionView:didHighlightItemAtIndexPath:
collectionView:didSelectItemAtIndexPath:
collectionView:didUnhighlightItemAtIndexPath:
collectionView:shouldDeselectItemAtIndexPath:
collectionView:shouldHighlightItemAtIndexPath:
collectionView:shouldSelectItemAtIndexPath:

As for the rest of your question,I wish I could be of more help, but I'm not as fluent at the custom view animation.

like image 24
jhilgert00 Avatar answered Sep 20 '22 04:09

jhilgert00