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.
After trying several (and rather hacky) solutions, I solved this by writing my own UICollectionView
layout. Previous solutions I attempted:
UICollectionView
and attempt to fake the original cell moving by overlaying it, dimming the background, and animating the new UIViewI 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.
Seems to be a multi-part question, so I'll answer part of it.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With