diff --git a/CarouselCollectionViewLayout.xcodeproj/project.pbxproj b/CarouselCollectionViewLayout.xcodeproj/project.pbxproj index 03f4140..f77c506 100644 --- a/CarouselCollectionViewLayout.xcodeproj/project.pbxproj +++ b/CarouselCollectionViewLayout.xcodeproj/project.pbxproj @@ -18,9 +18,9 @@ 43078F6A197BD9FD0025ECBF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43078F4F197BD9FD0025ECBF /* Foundation.framework */; }; 43078F6B197BD9FD0025ECBF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43078F53197BD9FD0025ECBF /* UIKit.framework */; }; 43078F75197BD9FD0025ECBF /* CarouselLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 43078F74197BD9FD0025ECBF /* CarouselLayoutTests.m */; }; - B27A913BFA596301C18A9698 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B27A966E3FC40E0996945370 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m */; }; B27A94D3F3143E09C71A455F /* PBDCarouselCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = B27A9F7C36FCCD1A28D832CE /* PBDCarouselCollectionViewLayout.m */; }; B27A96663940BFABA727FAC0 /* CarouselCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B27A954C849FCCE3E366BB70 /* CarouselCollectionViewController.m */; }; + B27A9AA6B040EA3A34248564 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B27A96430E04235FEF987259 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,10 +51,11 @@ 439841EE1C47E28600719676 /* CarouselCollectionViewLayoutTests-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CarouselCollectionViewLayoutTests-Prefix.pch"; sourceTree = ""; }; 439841EF1C47E28600719676 /* CarouselCollectionViewLayoutTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "CarouselCollectionViewLayoutTests-Info.plist"; sourceTree = ""; }; B27A900D6191713D16CE53F3 /* CarouselCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CarouselCollectionViewController.h; sourceTree = ""; }; - B27A92408CE32697829986F2 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h; sourceTree = ""; }; B27A92B41B76EEAE062EF3A4 /* PBDCarouselCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBDCarouselCollectionViewLayout.h; sourceTree = ""; }; B27A954C849FCCE3E366BB70 /* CarouselCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CarouselCollectionViewController.m; sourceTree = ""; }; - B27A966E3FC40E0996945370 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m; sourceTree = ""; }; + B27A96430E04235FEF987259 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m; sourceTree = ""; }; + B27A9D603D06657B3AFAFEF8 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h; sourceTree = ""; }; + B27A9E68A4FA40042C24F895 /* PBDCarouselCollectionViewLayoutPropertiesCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBDCarouselCollectionViewLayoutPropertiesCache.h; sourceTree = ""; }; B27A9F7C36FCCD1A28D832CE /* PBDCarouselCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBDCarouselCollectionViewLayout.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -179,13 +180,14 @@ path = Classes; sourceTree = ""; }; - B27A981EA672C5FE98221A8F /* Helpers */ = { + B27A9945B6C04DDA15B37511 /* Internal */ = { isa = PBXGroup; children = ( - B27A966E3FC40E0996945370 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m */, - B27A92408CE32697829986F2 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h */, + B27A96430E04235FEF987259 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m */, + B27A9D603D06657B3AFAFEF8 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h */, + B27A9E68A4FA40042C24F895 /* PBDCarouselCollectionViewLayoutPropertiesCache.h */, ); - path = Helpers; + path = Internal; sourceTree = ""; }; B27A9E6A011735C5D4F66808 /* Lib */ = { @@ -193,7 +195,7 @@ children = ( B27A92B41B76EEAE062EF3A4 /* PBDCarouselCollectionViewLayout.h */, B27A9F7C36FCCD1A28D832CE /* PBDCarouselCollectionViewLayout.m */, - B27A981EA672C5FE98221A8F /* Helpers */, + B27A9945B6C04DDA15B37511 /* Internal */, ); path = Lib; sourceTree = ""; @@ -296,7 +298,7 @@ 43078F5C197BD9FD0025ECBF /* main.m in Sources */, B27A94D3F3143E09C71A455F /* PBDCarouselCollectionViewLayout.m in Sources */, B27A96663940BFABA727FAC0 /* CarouselCollectionViewController.m in Sources */, - B27A913BFA596301C18A9698 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m in Sources */, + B27A9AA6B040EA3A34248564 /* PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CarouselCollectionViewLayout/Classes/Demo/CarouselCollectionViewController.m b/CarouselCollectionViewLayout/Classes/Demo/CarouselCollectionViewController.m index 324a4d6..c497235 100644 --- a/CarouselCollectionViewLayout/Classes/Demo/CarouselCollectionViewController.m +++ b/CarouselCollectionViewLayout/Classes/Demo/CarouselCollectionViewController.m @@ -12,6 +12,7 @@ - (id)init { PBDCarouselCollectionViewLayout *layout = [[PBDCarouselCollectionViewLayout alloc] init]; layout.itemSize = CGSizeMake(280, 240); layout.interItemSpace = 20; + layout.headerSize = CGSizeMake(100, 490); self = [super initWithCollectionViewLayout:layout]; if (self) { @@ -27,6 +28,9 @@ - (void)viewDidLoad { self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast; [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"]; + [self.collectionView registerClass:[UICollectionReusableView class] + forSupplementaryViewOfKind:PBDCollectionElementKindSectionHeader + withReuseIdentifier:@"Header"]; self.collectionView.backgroundColor = [UIColor greenColor]; } @@ -43,4 +47,14 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell return cell; } +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:PBDCollectionElementKindSectionHeader + withReuseIdentifier:@"Header" + forIndexPath:indexPath]; + + view.backgroundColor = [UIColor yellowColor]; + + return view; +} + @end diff --git a/CarouselCollectionViewLayout/Classes/Lib/Helpers/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h b/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h similarity index 79% rename from CarouselCollectionViewLayout/Classes/Lib/Helpers/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h rename to CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h index 656f9e9..6c2435b 100644 --- a/CarouselCollectionViewLayout/Classes/Lib/Helpers/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h +++ b/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h @@ -2,7 +2,9 @@ * Copyright (c) 2016 Pawel Dudek. All rights reserved. */ #import -#import "PBDCarouselCollectionViewLayout.h" +#import "PBDCarouselCollectionViewLayoutPropertiesCache.h" + +@class PBDCarouselCollectionViewLayout; @interface PBDCarouselCollectionViewLayoutHorizontalPropertiesCache : NSObject diff --git a/CarouselCollectionViewLayout/Classes/Lib/Helpers/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m b/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m similarity index 80% rename from CarouselCollectionViewLayout/Classes/Lib/Helpers/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m rename to CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m index 2b90286..06af64d 100644 --- a/CarouselCollectionViewLayout/Classes/Lib/Helpers/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m +++ b/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.m @@ -2,6 +2,7 @@ * Copyright (c) 2016 Pawel Dudek. All rights reserved. */ #import "PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h" +#import "PBDCarouselCollectionViewLayout.h" @interface PBDCarouselCollectionViewLayoutHorizontalPropertiesCache () @@ -54,11 +55,18 @@ - (void)calculateLayoutProperties { self.cellYPosition = CGRectGetMidY(self.contentRect) - collectionView.contentInset.top; } -#pragma mark - +#pragma mark - Center Calculation - (CGPoint)centerForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat x = self.contentStart + indexPath.row * (self.layout.itemSize.width + self.layout.interItemSpace) + self.layout.itemSize.width / 2; return CGPointMake(x, self.cellYPosition); } +- (CGPoint)centerForHeaderViewAtIndexPath:(NSIndexPath *)indexPath { + NSIndexPath *firstSectionItem = [NSIndexPath indexPathForItem:0 inSection:indexPath.section]; + CGPoint firstItemCenter = [self centerForItemAtIndexPath:firstSectionItem]; + firstItemCenter.x -= self.layout.itemSize.width / 2.0f + self.layout.interItemSpace + self.layout.headerSize.width / 2.0f; + return CGPointMake(firstItemCenter.x, self.cellYPosition); +} + @end diff --git a/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutPropertiesCache.h b/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutPropertiesCache.h new file mode 100644 index 0000000..5dc8cab --- /dev/null +++ b/CarouselCollectionViewLayout/Classes/Lib/Internal/PBDCarouselCollectionViewLayoutPropertiesCache.h @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2016 dudek. All rights reserved. +*/ +#import + +@protocol PBDCarouselCollectionViewLayoutPropertiesCache + +/* + * Content rect, considering content inset of collection view. + */ +@property(nonatomic, readonly) CGRect contentRect; + +/* + * Point on x axis that defines where actual content starts, considering size and content inset of collection view + */ +@property(nonatomic, readonly) CGFloat contentStart; + +#pragma mark - Obtaining Item Position + +- (CGPoint)centerForItemAtIndexPath:(NSIndexPath *)indexPath; + +- (CGPoint)centerForHeaderViewAtIndexPath:(NSIndexPath *)path; + +@end diff --git a/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.h b/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.h index 9e431a3..b1925a2 100644 --- a/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.h +++ b/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.h @@ -5,30 +5,20 @@ #import -@protocol PBDCarouselCollectionViewLayoutPropertiesCache - -/* - * Content rect, considering content inset of collection view. - */ -@property(nonatomic, readonly) CGRect contentRect; - -/* - * Point on x axis that defines where actual content starts, considering size and content inset of collection view - */ -@property(nonatomic, readonly) CGFloat contentStart; - -#pragma mark - Obtaining Item Position - -- (CGPoint)centerForItemAtIndexPath:(NSIndexPath *)indexPath; - -@end +extern NSString *PBDCollectionElementKindSectionHeader; @interface PBDCarouselCollectionViewLayout : UICollectionViewLayout @property(nonatomic) CGSize itemSize; @property(nonatomic) CGFloat interItemSpace; - -@property(nonatomic) CGSize headerFooterSize; +/* + * Settings this to non-nil value will enable collection header view. Header view is laid out before first item in collection view. + * It does not participate in centering, aka you cannot center collection view on it. + * + * Defaults to CGSizeZero; + * + */ +@property(nonatomic) CGSize headerSize; @end diff --git a/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.m b/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.m index 0503545..d17ac33 100644 --- a/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.m +++ b/CarouselCollectionViewLayout/Classes/Lib/PBDCarouselCollectionViewLayout.m @@ -4,8 +4,11 @@ #import "PBDCarouselCollectionViewLayout.h" +#import "PBDCarouselCollectionViewLayoutPropertiesCache.h" #import "PBDCarouselCollectionViewLayoutHorizontalPropertiesCache.h" +NSString *PBDCollectionElementKindSectionHeader = @"PBDCollectionElementKindSectionHeader"; + @interface PBDCarouselCollectionViewLayout () @property(nonatomic, strong) NSIndexPath *indexPathForCenteredItem; @@ -54,6 +57,13 @@ - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { [layoutAttributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; } + UICollectionViewLayoutAttributes *headerLayoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:PBDCollectionElementKindSectionHeader + atIndexPath:[NSIndexPath indexPathForItem:0 + inSection:0]]; + if (headerLayoutAttributes) { + [layoutAttributes addObject:headerLayoutAttributes]; + } + return layoutAttributes; } @@ -69,13 +79,25 @@ - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSInde return attributes; } +- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attributes; + if ([elementKind isEqualToString:PBDCollectionElementKindSectionHeader] && !CGSizeEqualToSize(self.headerSize, CGSizeZero)) { + attributes = [[[self class] layoutAttributesClass] layoutAttributesForSupplementaryViewOfKind:elementKind + withIndexPath:indexPath]; + attributes.size = self.headerSize; + attributes.center = [self.propertiesCache centerForHeaderViewAtIndexPath:indexPath]; + } + + return attributes; +} + #pragma mark - Target Content Offset - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { CGPoint targetContentOffset = proposedContentOffset; UICollectionViewLayoutAttributes *layoutAttributesForItemToCenterOn = [self layoutAttributesForUserFingerMovingWithVelocity:velocity proposedContentOffset:proposedContentOffset]; - + if (layoutAttributesForItemToCenterOn) { targetContentOffset.x = layoutAttributesForItemToCenterOn.center.x - self.collectionView.bounds.size.width / 2; targetContentOffset.y = 0; @@ -103,7 +125,9 @@ - (UICollectionViewLayoutAttributes *)layoutAttributesForUserFingerMovingWithVel UICollectionViewLayoutAttributes *layoutAttributesForItemToCenterOn = nil; CGRect nextVisibleBounds = [self collectionView].bounds; nextVisibleBounds.origin = offset; - NSArray *layoutAttributesInRect = [self layoutAttributesForElementsInRect:nextVisibleBounds]; + + NSPredicate *itemsPredicate = [NSPredicate predicateWithFormat:@"representedElementCategory == %d", UICollectionElementCategoryCell]; + NSArray *layoutAttributesInRect = [[self layoutAttributesForElementsInRect:nextVisibleBounds] filteredArrayUsingPredicate:itemsPredicate]; if (velocity.x > 0.0f) { layoutAttributesForItemToCenterOn = [layoutAttributesInRect lastObject]; @@ -145,4 +169,11 @@ - (void)setItemSize:(CGSize)itemSize { } } +- (void)setHeaderSize:(CGSize)headerSize { + if (!CGSizeEqualToSize(_headerSize, headerSize)) { + _headerSize = headerSize; + [self invalidateLayout]; + } +} + @end