Skip to content

Instantly share code, notes, and snippets.

@hamin
Last active August 29, 2015 14:07
Show Gist options
  • Save hamin/84b74ebd8e5bec38641b to your computer and use it in GitHub Desktop.
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
#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
#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