Created
March 15, 2013 11:11
-
-
Save leeprobert/5169093 to your computer and use it in GitHub Desktop.
UICollectionViewFlowLayout sub-class that creates layout attributes for items so they render like a carousel. The point of view is from the center of the carousel so is suited for lots of items. It also changes its behaviour based on orientation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CollectionViewCarouselFlowLayout.m | |
// iP2 | |
// | |
// Created by Lee Probert on 07/03/2013. | |
// | |
// | |
#import "CollectionViewCarouselFlowLayout.h" | |
#define ITEM_SIZE 300.0f | |
#define ACTIVE_DISTANCE 200 | |
#define ZOOM_FACTOR 0.2 | |
#define PADDING 20 | |
#define MAX_ANGLE 80 | |
#define PERSPECTIVE 1.0/-400 | |
@interface CollectionViewCarouselFlowLayout (){ | |
UIInterfaceOrientation _orientation; | |
} | |
-(void)initLayoutProperties; | |
@end | |
@implementation CollectionViewCarouselFlowLayout | |
-(void)prepareLayout { | |
[super prepareLayout]; | |
[self initLayoutProperties]; | |
} | |
-(void)initLayoutProperties { | |
_orientation = [[UIApplication sharedApplication] statusBarOrientation]; | |
self.itemSize = CGSizeMake(ITEM_SIZE, ITEM_SIZE); | |
self.scrollDirection = UIInterfaceOrientationIsPortrait(_orientation)? UICollectionViewScrollDirectionVertical : UICollectionViewScrollDirectionHorizontal; | |
self.sectionInset = UIInterfaceOrientationIsPortrait(_orientation)? UIEdgeInsetsMake(0, 200, 0, 200) : UIEdgeInsetsMake(200, 0, 200, 0); | |
self.minimumLineSpacing = PADDING; | |
self.minimumInteritemSpacing = PADDING; | |
} | |
/* | |
Rotate in 3D based on position on screen | |
*/ | |
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect { | |
NSArray* array = [super layoutAttributesForElementsInRect:rect]; | |
CGRect visibleRect; | |
visibleRect.origin = self.collectionView.contentOffset; | |
visibleRect.size = self.collectionView.bounds.size; | |
int debug = 1; | |
for(UICollectionViewLayoutAttributes* attributes in array) { | |
if(CGRectIntersectsRect(attributes.frame, rect)){ | |
CGFloat d = UIInterfaceOrientationIsPortrait(_orientation)? | |
CGRectGetMidY(visibleRect)-attributes.center.y : | |
CGRectGetMidX(visibleRect)-attributes.center.x; | |
CGFloat w = visibleRect.size.width; | |
CGFloat h = visibleRect.size.height; | |
CGFloat dRatio = UIInterfaceOrientationIsPortrait(_orientation)? d/(h/2) : d/(w/2); | |
CGFloat angle = MAX_ANGLE*dRatio; // an angle between 0 and MAX_ANGLE based on proximity to center | |
CGFloat radians = DEGREES_TO_RADIANS(angle); | |
debug = 0; | |
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity; | |
rotationAndPerspectiveTransform.m34 = PERSPECTIVE; | |
rotationAndPerspectiveTransform = UIInterfaceOrientationIsPortrait(_orientation)? | |
CATransform3DRotate(rotationAndPerspectiveTransform, radians, 1.0f, 0.0f, 0.0f) : | |
CATransform3DRotate(rotationAndPerspectiveTransform, radians, 0.0f, 1.0f, 0.0f); | |
attributes.transform3D = rotationAndPerspectiveTransform; | |
} | |
} | |
return array; | |
} | |
/* | |
Scale based on position on screen | |
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect { | |
NSArray* array = [super layoutAttributesForElementsInRect:rect]; | |
CGRect visibleRect; | |
visibleRect.origin = self.collectionView.contentOffset; | |
visibleRect.size = self.collectionView.bounds.size; | |
for(UICollectionViewLayoutAttributes* attributes in array) { | |
if(CGRectIntersectsRect(attributes.frame, rect)){ | |
CGFloat distance = CGRectGetMidX(visibleRect)-attributes.center.x; | |
CGFloat normalizedDistance = distance/ACTIVE_DISTANCE; | |
if(ABS(distance)<ACTIVE_DISTANCE){ | |
CGFloat zoom = 1+ZOOM_FACTOR*(1-ABS(normalizedDistance)); | |
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0); | |
attributes.zIndex = round(zoom); | |
} | |
} | |
} | |
return array; | |
} | |
*/ | |
-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { | |
CGFloat offsetAdjustment = MAXFLOAT; | |
CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds)/2.0); | |
CGFloat verticalCenter = proposedContentOffset.y + (CGRectGetHeight(self.collectionView.bounds)/2.0); | |
CGRect targetRectHorizontal = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); | |
CGRect targetRectVertical = CGRectMake(0.0, proposedContentOffset.y, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); | |
NSArray *array = UIInterfaceOrientationIsPortrait(_orientation)? | |
[super layoutAttributesForElementsInRect:targetRectVertical] : | |
[super layoutAttributesForElementsInRect:targetRectHorizontal]; | |
for(UICollectionViewLayoutAttributes* layoutAttributes in array) { | |
CGFloat itemHorizontalCenter = layoutAttributes.center.x; | |
CGFloat itemVerticalCenter = layoutAttributes.center.y; | |
if(UIInterfaceOrientationIsPortrait(_orientation)){ | |
if(ABS(itemVerticalCenter-verticalCenter) < ABS(offsetAdjustment)){ | |
offsetAdjustment = itemVerticalCenter-verticalCenter; | |
} | |
}else{ | |
if(ABS(itemHorizontalCenter-horizontalCenter) < ABS(offsetAdjustment)){ | |
offsetAdjustment = itemHorizontalCenter-horizontalCenter; | |
} | |
} | |
} | |
return UIInterfaceOrientationIsPortrait(_orientation)? | |
CGPointMake(proposedContentOffset.x, proposedContentOffset.y+offsetAdjustment) : | |
CGPointMake(proposedContentOffset.x+offsetAdjustment, proposedContentOffset.y); | |
} | |
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { | |
return YES; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment