Last active
August 29, 2015 14:07
-
-
Save hamin/84b74ebd8e5bec38641b to your computer and use it in GitHub Desktop.
Vertical Scrolling UICollectionViewLayout Subclass with paging and peeking for next page enabled based on https://github.com/betzerra/EBCardCollectionViewLayout
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
#import <UIKit/UIKit.h> | |
@interface VerticalPeekingPagingLayout : UICollectionViewLayout | |
@property (readonly) NSInteger currentPage; | |
@property (assign, nonatomic) NSInteger startPage; | |
@property (nonatomic, assign) UIOffset offset; | |
@property (nonatomic, strong) NSDictionary *layoutInfo; | |
@end |
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
#import "VerticalPeekingPagingLayout.h" | |
@interface VerticalPeekingPagingLayout() | |
- (NSString *)keyForIndexPath:(NSIndexPath *)indexPath; | |
- (CGRect)frameForCardAtIndexPath:(NSIndexPath *)indexPath; | |
- (NSInteger)cardWidth; | |
- (CGFloat)pageWidth; | |
- (void)setup; | |
@end | |
@implementation VerticalPeekingPagingLayout | |
#pragma mark - Private | |
static NSString * const CellKind = @"CardCell"; | |
- (void)setup { | |
[self addObserver:self forKeyPath:@"collectionView.contentOffset" options:NSKeyValueObservingOptionNew context:nil]; | |
} | |
- (NSInteger)cardWidth { | |
NSInteger retVal = self.collectionView.bounds.size.width - _offset.horizontal * 2; | |
return retVal; | |
} | |
- (NSInteger)cardHeight { | |
NSInteger retVal = self.collectionView.bounds.size.height - _offset.vertical * 2; | |
return retVal; | |
} | |
- (CGFloat)pageWidth { | |
CGFloat retVal = [self cardWidth] + _offset.horizontal/2; | |
return retVal; | |
} | |
- (CGFloat)pageHeight { | |
CGFloat retVal = [self cardHeight] + _offset.vertical/2; | |
return retVal; | |
} | |
- (NSString *)keyForIndexPath:(NSIndexPath *)indexPath { | |
NSString *retVal = [NSString stringWithFormat:@"%d-%d", indexPath.section, indexPath.row]; | |
return retVal; | |
} | |
- (CGRect)frameForCardAtIndexPath:(NSIndexPath *)indexPath { | |
NSInteger posY = _offset.vertical / 2 + [self pageHeight] * indexPath.row; | |
if ([self.collectionView numberOfItemsInSection:0] == 1) { | |
// If there's just an only item. Center it. | |
posY = _offset.vertical + [self pageHeight] * indexPath.row; | |
} | |
CGRect retVal = CGRectMake(_offset.horizontal, | |
posY, | |
self.collectionView.bounds.size.width - _offset.horizontal * 2, | |
[self cardHeight]); | |
return retVal; | |
} | |
#pragma mark - Public | |
- (id)init { | |
self = [super init]; | |
if (self) { | |
[self setup]; | |
} | |
return self; | |
} | |
- (void)awakeFromNib { | |
[self setup]; | |
} | |
- (void)prepareLayout{ | |
NSMutableDictionary *newLayoutInfo = [NSMutableDictionary dictionary]; | |
NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary]; | |
NSInteger sectionCount = [self.collectionView numberOfSections]; | |
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; | |
for (NSInteger section = 0; section < sectionCount; section++) { | |
NSInteger itemCount = [self.collectionView numberOfItemsInSection:section]; | |
for (NSInteger item = 0; item < itemCount; item++) { | |
indexPath = [NSIndexPath indexPathForItem:item inSection:section]; | |
UICollectionViewLayoutAttributes *itemAttributes = | |
[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; | |
itemAttributes.frame = [self frameForCardAtIndexPath:indexPath]; | |
NSString *key = [self keyForIndexPath:indexPath]; | |
cellLayoutInfo[key] = itemAttributes; | |
} | |
} | |
newLayoutInfo[@"CellKind"] = cellLayoutInfo; | |
self.layoutInfo = newLayoutInfo; | |
} | |
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ | |
return YES; | |
} | |
-(BOOL)shouldInvalidateLayoutForPreferredLayoutAttributes:(UICollectionViewLayoutAttributes *)preferredAttributes withOriginalAttributes:(UICollectionViewLayoutAttributes *)originalAttributes{ | |
return YES; | |
} | |
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ | |
NSString *key = [self keyForIndexPath:indexPath]; | |
UICollectionViewLayoutAttributes *retVal = self.layoutInfo[@"CellKind"][key]; | |
if ([indexPath item]) { | |
retVal.alpha = 0.6; | |
retVal.transform = CGAffineTransformMakeScale(0.8, 0.8); | |
}else{ | |
retVal.alpha = 1.0; | |
retVal.transform = CGAffineTransformMakeScale(1, 1); | |
} | |
return retVal; | |
} | |
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ | |
NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count]; | |
[self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, | |
NSDictionary *elementsInfo, | |
BOOL *stop) { | |
[elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, | |
UICollectionViewLayoutAttributes *attributes, | |
BOOL *innerStop) { | |
if (CGRectIntersectsRect(rect, attributes.frame)) { | |
[allAttributes addObject:attributes]; | |
} | |
}]; | |
}]; | |
return allAttributes; | |
} | |
- (CGSize)collectionViewContentSize { | |
CGSize retVal = CGSizeMake(self.collectionView.bounds.size.width, | |
[self pageHeight] * [self.collectionView numberOfItemsInSection:0] + (_offset.vertical*2)); | |
return retVal; | |
} | |
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { | |
CGPoint retVal = proposedContentOffset; | |
CGFloat rawPageValue = self.collectionView.contentOffset.y / [self pageHeight]; | |
CGFloat currentPage = 0; | |
if (velocity.y > 0.0) { | |
currentPage = floor(rawPageValue); | |
} else { | |
currentPage = ceil(rawPageValue); | |
} | |
CGFloat nextPage = 0; | |
if (velocity.y > 0.0) { | |
nextPage = ceil(rawPageValue); | |
} else { | |
nextPage = floor(rawPageValue); | |
} | |
if (nextPage < 0) { | |
nextPage = 0; | |
} | |
BOOL pannedLessThanAPage = fabs(1 + currentPage - rawPageValue) > 0.5; | |
BOOL flicked = fabs(velocity.y) > [self flickVelocity]; | |
if (pannedLessThanAPage && flicked) { | |
// Change UICollectionViewCell | |
retVal.y = nextPage * [self pageHeight]; | |
NSLog(@"THIS IS NEXT PAGE: %f", nextPage); | |
// if (nextPage != [self.collectionView numberOfItemsInSection:0]-1) { | |
retVal.y = retVal.y + _offset.vertical/2; | |
// } | |
NSLog(@"THIS IS retVAL.y %f", retVal.y); | |
} else { | |
// Bounces | |
if (nextPage == 0) { | |
retVal.y = nextPage * [self pageHeight]; | |
NSLog(@"THIS IS NEXT PAGE: %f", nextPage); | |
// if (nextPage != [self.collectionView numberOfItemsInSection:0]-1) { | |
retVal.y = retVal.y + _offset.vertical/2; | |
}else{ | |
CGFloat posY = round(rawPageValue) * [self pageHeight] + _offset.vertical/2; | |
posY = MAX(posY, 0); | |
retVal.y = posY; | |
} | |
} | |
return retVal; | |
} | |
- (CGFloat)flickVelocity { | |
return 0.05; | |
} | |
- (void)observeValueForKeyPath:(NSString *)keyPath | |
ofObject:(id)object | |
change:(NSDictionary *)change | |
context:(void *)context { | |
if ([keyPath isEqualToString:@"collectionView.contentOffset"]) { | |
CGFloat floatPage = self.collectionView.contentOffset.y / [self pageHeight]; | |
NSInteger newPage = round(floatPage); | |
if (_currentPage != newPage) { | |
_currentPage = newPage; | |
} | |
} | |
} | |
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath | |
{ | |
UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; | |
attributes.frame = CGRectInset(attributes.frame, 20, 20); | |
return attributes; | |
} | |
- (void)dealloc { | |
@try { | |
[self removeObserver:self forKeyPath:@"collectionView.contentOffset"]; | |
} @catch (NSException * exception) { | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment