diff --git a/CHTCollectionViewWaterfallLayout.h b/CHTCollectionViewWaterfallLayout.h index 5d3d4a8..4788aed 100644 --- a/CHTCollectionViewWaterfallLayout.h +++ b/CHTCollectionViewWaterfallLayout.h @@ -186,6 +186,25 @@ extern NSString *const CHTCollectionElementKindSectionFooter; */ - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; +/** + * Asks the delegate for the minimum spacing between colums in a secified section. If this method is not implemented, the + * minimumColumnSpacing property is used for all sections. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose minimum interitem spacing is being requested. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its minimumColumnSpacing property to determine the amount of space between columns in each section. + * + * @return + * The minimum spacing between each column. + */ +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumColumnSpacingForSectionAtIndex:(NSInteger)section; + @end #pragma mark - CHTCollectionViewWaterfallLayout @@ -287,6 +306,15 @@ extern NSString *const CHTCollectionElementKindSectionFooter; */ @property (nonatomic, assign) CHTCollectionViewWaterfallLayoutItemRenderDirection itemRenderDirection; +/** + * @brief The minimum height of the collection view's content. + * @discussion + * The minimum height of the collection view's content. This could be used to allow hidden headers with no content. + * + * Default: 0.f + */ +@property (nonatomic, assign) CGFloat minimumContentHeight; + /** * @brief The calculated width of an item in the specified section. * @discussion diff --git a/CHTCollectionViewWaterfallLayout.m b/CHTCollectionViewWaterfallLayout.m index 194659f..59c6536 100644 --- a/CHTCollectionViewWaterfallLayout.m +++ b/CHTCollectionViewWaterfallLayout.m @@ -6,6 +6,7 @@ // #import "CHTCollectionViewWaterfallLayout.h" +#import "tgmath.h" NSString *const CHTCollectionElementKindSectionHeader = @"CHTCollectionElementKindSectionHeader"; NSString *const CHTCollectionElementKindSectionFooter = @"CHTCollectionElementKindSectionFooter"; @@ -30,7 +31,12 @@ @interface CHTCollectionViewWaterfallLayout () @implementation CHTCollectionViewWaterfallLayout /// How many items to be union into a single rectangle -const NSInteger unionSize = 20; +static const NSInteger unionSize = 20; + +static CGFloat CHTFloorCGFloat(CGFloat value) { + CGFloat scale = [UIScreen mainScreen].scale; + return floor(value * scale) / scale; +} #pragma mark - Public Accessors - (void)setColumnCount:(NSInteger)columnCount { @@ -113,7 +119,13 @@ - (CGFloat)itemWidthInSectionAtIndex:(NSInteger)section { } CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; NSInteger columnCount = [self columnCountForSection:section]; - return floorf((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); + + CGFloat columnSpacing = self.minimumColumnSpacing; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumColumnSpacingForSectionAtIndex:)]) { + columnSpacing = [self.delegate collectionView:self.collectionView layout:self minimumColumnSpacingForSectionAtIndex:section]; + } + + return CHTFloorCGFloat((width - (columnCount - 1) * columnSpacing) / columnCount); } #pragma mark - Private Accessors @@ -194,6 +206,13 @@ - (id)initWithCoder:(NSCoder *)aDecoder { - (void)prepareLayout { [super prepareLayout]; + [self.headersAttribute removeAllObjects]; + [self.footersAttribute removeAllObjects]; + [self.unionRects removeAllObjects]; + [self.columnHeights removeAllObjects]; + [self.allItemAttributes removeAllObjects]; + [self.sectionItemAttributes removeAllObjects]; + NSInteger numberOfSections = [self.collectionView numberOfSections]; if (numberOfSections == 0) { return; @@ -205,13 +224,6 @@ - (void)prepareLayout { // Initialize variables NSInteger idx = 0; - [self.headersAttribute removeAllObjects]; - [self.footersAttribute removeAllObjects]; - [self.unionRects removeAllObjects]; - [self.columnHeights removeAllObjects]; - [self.allItemAttributes removeAllObjects]; - [self.sectionItemAttributes removeAllObjects]; - for (NSInteger section = 0; section < numberOfSections; section++) { NSInteger columnCount = [self columnCountForSection:section]; NSMutableArray *sectionColumnHeights = [NSMutableArray arrayWithCapacity:columnCount]; @@ -235,6 +247,11 @@ - (void)prepareLayout { minimumInteritemSpacing = self.minimumInteritemSpacing; } + CGFloat columnSpacing = self.minimumColumnSpacing; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumColumnSpacingForSectionAtIndex:)]) { + columnSpacing = [self.delegate collectionView:self.collectionView layout:self minimumColumnSpacingForSectionAtIndex:section]; + } + UIEdgeInsets sectionInset; if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; @@ -244,7 +261,7 @@ - (void)prepareLayout { CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; NSInteger columnCount = [self columnCountForSection:section]; - CGFloat itemWidth = floorf((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); + CGFloat itemWidth = CHTFloorCGFloat((width - (columnCount - 1) * columnSpacing) / columnCount); /* * 2. Section header @@ -293,12 +310,12 @@ - (void)prepareLayout { for (idx = 0; idx < itemCount; idx++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; NSUInteger columnIndex = [self nextColumnIndexForItem:idx inSection:section]; - CGFloat xOffset = sectionInset.left + (itemWidth + self.minimumColumnSpacing) * columnIndex; + CGFloat xOffset = sectionInset.left + (itemWidth + columnSpacing) * columnIndex; CGFloat yOffset = [self.columnHeights[section][columnIndex] floatValue]; CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath]; CGFloat itemHeight = 0; if (itemSize.height > 0 && itemSize.width > 0) { - itemHeight = floorf(itemSize.height * itemWidth / itemSize.width); + itemHeight = CHTFloorCGFloat(itemSize.height * itemWidth / itemSize.width); } attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; @@ -354,11 +371,16 @@ - (void)prepareLayout { idx = 0; NSInteger itemCounts = [self.allItemAttributes count]; while (idx < itemCounts) { - CGRect rect1 = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; - idx = MIN(idx + unionSize, itemCounts) - 1; - CGRect rect2 = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; - [self.unionRects addObject:[NSValue valueWithCGRect:CGRectUnion(rect1, rect2)]]; - idx++; + CGRect unionRect = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; + NSInteger rectEndIndex = MIN(idx + unionSize, itemCounts); + + for (NSInteger i = idx + 1; i < rectEndIndex; i++) { + unionRect = CGRectUnion(unionRect, ((UICollectionViewLayoutAttributes *)self.allItemAttributes[i]).frame); + } + + idx = rectEndIndex; + + [self.unionRects addObject:[NSValue valueWithCGRect:unionRect]]; } } @@ -371,6 +393,10 @@ - (CGSize)collectionViewContentSize { CGSize contentSize = self.collectionView.bounds.size; contentSize.height = [[[self.columnHeights lastObject] firstObject] floatValue]; + if (contentSize.height < self.minimumContentHeight) { + contentSize.height = self.minimumContentHeight; + } + return contentSize; } diff --git a/CHTCollectionViewWaterfallLayout.podspec b/CHTCollectionViewWaterfallLayout.podspec index 3f67cae..5935658 100644 --- a/CHTCollectionViewWaterfallLayout.podspec +++ b/CHTCollectionViewWaterfallLayout.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CHTCollectionViewWaterfallLayout" - s.version = "0.8" + s.version = "0.9" s.summary = "The waterfall (i.e., Pinterest-like) layout for UICollectionView." s.homepage = "https://github.com/chiahsien/CHTCollectionViewWaterfallLayout" s.screenshots = "https://raw.github.com/chiahsien/UICollectionViewWaterfallLayout/master/Screenshots/2-columns.png" diff --git a/CHTCollectionViewWaterfallLayout.swift b/CHTCollectionViewWaterfallLayout.swift old mode 100644 new mode 100755 index 5f355de..54ad956 --- a/CHTCollectionViewWaterfallLayout.swift +++ b/CHTCollectionViewWaterfallLayout.swift @@ -11,19 +11,19 @@ import UIKit @objc protocol CHTCollectionViewDelegateWaterfallLayout: UICollectionViewDelegate{ - func collectionView (collectionView: UICollectionView!,layout collectionViewLayout: UICollectionViewLayout, + func collectionView (collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize - optional func colletionView (collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout, + optional func colletionView (collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, heightForHeaderInSection section: NSInteger) -> CGFloat - optional func colletionView (collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout, + optional func colletionView (collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, heightForFooterInSection section: NSInteger) -> CGFloat - optional func colletionView (collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout, + optional func colletionView (collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: NSInteger) -> UIEdgeInsets - optional func colletionView (collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout, + optional func colletionView (collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: NSInteger) -> CGFloat } @@ -186,11 +186,11 @@ class CHTCollectionViewWaterfallLayout : UICollectionViewLayout{ self.headersAttributes.setObject(attributes, forKey: (section)) self.allItemAttributes.addObject(attributes) - top = CGRectGetMaxX(attributes.frame) + top = CGRectGetMaxY(attributes.frame) } top += sectionInset.top for var idx = 0; idx < self.columnCount; idx++ { - self.columnHeights.setObject(top, atIndexedSubscript: idx) + self.columnHeights[idx]=top; } /* @@ -216,7 +216,7 @@ class CHTCollectionViewWaterfallLayout : UICollectionViewLayout{ attributes.frame = CGRectMake(xOffset, CGFloat(yOffset), itemWidth, itemHeight) itemAttributes.addObject(attributes) self.allItemAttributes.addObject(attributes) - self.columnHeights.setObject(CGRectGetMaxY(attributes.frame) + minimumInteritemSpacing, atIndexedSubscript: columnIndex) + self.columnHeights[columnIndex]=CGRectGetMaxY(attributes.frame) + minimumInteritemSpacing; } self.sectionItemAttributes.addObject(itemAttributes) @@ -242,7 +242,7 @@ class CHTCollectionViewWaterfallLayout : UICollectionViewLayout{ } for var idx = 0; idx < self.columnCount; idx++ { - self.columnHeights.setObject(top, atIndexedSubscript: idx) + self.columnHeights[idx] = top } } @@ -280,7 +280,7 @@ class CHTCollectionViewWaterfallLayout : UICollectionViewLayout{ return list.objectAtIndex(indexPath.item) as UICollectionViewLayoutAttributes } - override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes!{ + override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes{ var attribute = UICollectionViewLayoutAttributes() if elementKind == CHTCollectionElementKindSectionHeader{ attribute = self.headersAttributes.objectForKey(indexPath.section) as UICollectionViewLayoutAttributes diff --git a/README.md b/README.md index 6ae91d6..66d2d5e 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ Please let me know if your app is using this library. I'm glad to put your app o F3PiX is a series of apps which gives you a concise, curated collection of pictures by professional (Dutch) photographers according to a specific theme. You can use the pictures freely for your own work. * [GroupMe for iOS](https://itunes.apple.com/us/app/groupme/id392796698?mt=8) GroupMe - A Home for All the Groups in Your Life. +* [Flickr](https://itunes.apple.com/us/app/id328407587) +Access and organize your photos from anywhere. License ------- diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..371ad52 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj @@ -0,0 +1,439 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 62C26FDF1ABE01840027F8D4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C26FDE1ABE01840027F8D4 /* AppDelegate.swift */; }; + 62C26FE61ABE01840027F8D4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 62C26FE51ABE01840027F8D4 /* Images.xcassets */; }; + 62C26FE91ABE01840027F8D4 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62C26FE71ABE01840027F8D4 /* LaunchScreen.xib */; }; + 62C26FF51ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C26FF41ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.swift */; }; + 62C26FFF1ABE01B20027F8D4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 62C26FFE1ABE01B20027F8D4 /* Main.storyboard */; }; + 62C270011ABE01BA0027F8D4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C270001ABE01BA0027F8D4 /* ViewController.swift */; }; + 62C270031ABE01D70027F8D4 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C270021ABE01D70027F8D4 /* Model.swift */; }; + 62C270061ABE01E70027F8D4 /* ImageUICollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C270041ABE01E70027F8D4 /* ImageUICollectionViewCell.swift */; }; + 62C270071ABE01E70027F8D4 /* ImageUICollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62C270051ABE01E70027F8D4 /* ImageUICollectionViewCell.xib */; }; + 62C2700B1ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 62C2700A1ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 62C26FEF1ABE01850027F8D4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 62C26FD11ABE01840027F8D4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 62C26FD81ABE01840027F8D4; + remoteInfo = CHTWaterfallSwiftDemo; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 62C26FD91ABE01840027F8D4 /* CHTWaterfallSwiftDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CHTWaterfallSwiftDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 62C26FDD1ABE01840027F8D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 62C26FDE1ABE01840027F8D4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 62C26FE51ABE01840027F8D4 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 62C26FE81ABE01840027F8D4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 62C26FEE1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CHTWaterfallSwiftDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 62C26FF31ABE01850027F8D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 62C26FF41ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CHTWaterfallSwiftDemoTests.swift; sourceTree = ""; }; + 62C26FFE1ABE01B20027F8D4 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + 62C270001ABE01BA0027F8D4 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 62C270021ABE01D70027F8D4 /* Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; + 62C270041ABE01E70027F8D4 /* ImageUICollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageUICollectionViewCell.swift; sourceTree = ""; }; + 62C270051ABE01E70027F8D4 /* ImageUICollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImageUICollectionViewCell.xib; sourceTree = ""; }; + 62C270081ABE020D0027F8D4 /* CHTWaterfallSwiftDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CHTWaterfallSwiftDemo-Bridging-Header.h"; sourceTree = ""; }; + 62C270091ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHTCollectionViewWaterfallLayout.h; sourceTree = ""; }; + 62C2700A1ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHTCollectionViewWaterfallLayout.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 62C26FD61ABE01840027F8D4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 62C26FEB1ABE01850027F8D4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 62C26FD01ABE01840027F8D4 = { + isa = PBXGroup; + children = ( + 62C26FDB1ABE01840027F8D4 /* CHTWaterfallSwiftDemo */, + 62C26FF11ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests */, + 62C26FDA1ABE01840027F8D4 /* Products */, + ); + sourceTree = ""; + }; + 62C26FDA1ABE01840027F8D4 /* Products */ = { + isa = PBXGroup; + children = ( + 62C26FD91ABE01840027F8D4 /* CHTWaterfallSwiftDemo.app */, + 62C26FEE1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 62C26FDB1ABE01840027F8D4 /* CHTWaterfallSwiftDemo */ = { + isa = PBXGroup; + children = ( + 62C26FFE1ABE01B20027F8D4 /* Main.storyboard */, + 62C26FDE1ABE01840027F8D4 /* AppDelegate.swift */, + 62C270001ABE01BA0027F8D4 /* ViewController.swift */, + 62C270041ABE01E70027F8D4 /* ImageUICollectionViewCell.swift */, + 62C270051ABE01E70027F8D4 /* ImageUICollectionViewCell.xib */, + 62C270021ABE01D70027F8D4 /* Model.swift */, + 62C26FE71ABE01840027F8D4 /* LaunchScreen.xib */, + 62C270091ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.h */, + 62C2700A1ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.m */, + 62C26FE51ABE01840027F8D4 /* Images.xcassets */, + 62C26FDC1ABE01840027F8D4 /* Supporting Files */, + 62C270081ABE020D0027F8D4 /* CHTWaterfallSwiftDemo-Bridging-Header.h */, + ); + path = CHTWaterfallSwiftDemo; + sourceTree = ""; + }; + 62C26FDC1ABE01840027F8D4 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 62C26FDD1ABE01840027F8D4 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 62C26FF11ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests */ = { + isa = PBXGroup; + children = ( + 62C26FF41ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.swift */, + 62C26FF21ABE01850027F8D4 /* Supporting Files */, + ); + path = CHTWaterfallSwiftDemoTests; + sourceTree = ""; + }; + 62C26FF21ABE01850027F8D4 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 62C26FF31ABE01850027F8D4 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 62C26FD81ABE01840027F8D4 /* CHTWaterfallSwiftDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 62C26FF81ABE01850027F8D4 /* Build configuration list for PBXNativeTarget "CHTWaterfallSwiftDemo" */; + buildPhases = ( + 62C26FD51ABE01840027F8D4 /* Sources */, + 62C26FD61ABE01840027F8D4 /* Frameworks */, + 62C26FD71ABE01840027F8D4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CHTWaterfallSwiftDemo; + productName = CHTWaterfallSwiftDemo; + productReference = 62C26FD91ABE01840027F8D4 /* CHTWaterfallSwiftDemo.app */; + productType = "com.apple.product-type.application"; + }; + 62C26FED1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 62C26FFB1ABE01850027F8D4 /* Build configuration list for PBXNativeTarget "CHTWaterfallSwiftDemoTests" */; + buildPhases = ( + 62C26FEA1ABE01850027F8D4 /* Sources */, + 62C26FEB1ABE01850027F8D4 /* Frameworks */, + 62C26FEC1ABE01850027F8D4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 62C26FF01ABE01850027F8D4 /* PBXTargetDependency */, + ); + name = CHTWaterfallSwiftDemoTests; + productName = CHTWaterfallSwiftDemoTests; + productReference = 62C26FEE1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 62C26FD11ABE01840027F8D4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "Sophie Fader"; + TargetAttributes = { + 62C26FD81ABE01840027F8D4 = { + CreatedOnToolsVersion = 6.1.1; + }; + 62C26FED1ABE01850027F8D4 = { + CreatedOnToolsVersion = 6.1.1; + TestTargetID = 62C26FD81ABE01840027F8D4; + }; + }; + }; + buildConfigurationList = 62C26FD41ABE01840027F8D4 /* Build configuration list for PBXProject "CHTWaterfallSwiftDemo" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 62C26FD01ABE01840027F8D4; + productRefGroup = 62C26FDA1ABE01840027F8D4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 62C26FD81ABE01840027F8D4 /* CHTWaterfallSwiftDemo */, + 62C26FED1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 62C26FD71ABE01840027F8D4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 62C26FFF1ABE01B20027F8D4 /* Main.storyboard in Resources */, + 62C26FE91ABE01840027F8D4 /* LaunchScreen.xib in Resources */, + 62C270071ABE01E70027F8D4 /* ImageUICollectionViewCell.xib in Resources */, + 62C26FE61ABE01840027F8D4 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 62C26FEC1ABE01850027F8D4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 62C26FD51ABE01840027F8D4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 62C2700B1ABE020D0027F8D4 /* CHTCollectionViewWaterfallLayout.m in Sources */, + 62C270031ABE01D70027F8D4 /* Model.swift in Sources */, + 62C270011ABE01BA0027F8D4 /* ViewController.swift in Sources */, + 62C270061ABE01E70027F8D4 /* ImageUICollectionViewCell.swift in Sources */, + 62C26FDF1ABE01840027F8D4 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 62C26FEA1ABE01850027F8D4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 62C26FF51ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 62C26FF01ABE01850027F8D4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 62C26FD81ABE01840027F8D4 /* CHTWaterfallSwiftDemo */; + targetProxy = 62C26FEF1ABE01850027F8D4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 62C26FE71ABE01840027F8D4 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 62C26FE81ABE01840027F8D4 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 62C26FF61ABE01850027F8D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 62C26FF71ABE01850027F8D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 62C26FF91ABE01850027F8D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = CHTWaterfallSwiftDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 62C26FFA1ABE01850027F8D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = CHTWaterfallSwiftDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo-Bridging-Header.h"; + }; + name = Release; + }; + 62C26FFC1ABE01850027F8D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = CHTWaterfallSwiftDemoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CHTWaterfallSwiftDemo.app/CHTWaterfallSwiftDemo"; + }; + name = Debug; + }; + 62C26FFD1ABE01850027F8D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = CHTWaterfallSwiftDemoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CHTWaterfallSwiftDemo.app/CHTWaterfallSwiftDemo"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 62C26FD41ABE01840027F8D4 /* Build configuration list for PBXProject "CHTWaterfallSwiftDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 62C26FF61ABE01850027F8D4 /* Debug */, + 62C26FF71ABE01850027F8D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 62C26FF81ABE01850027F8D4 /* Build configuration list for PBXNativeTarget "CHTWaterfallSwiftDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 62C26FF91ABE01850027F8D4 /* Debug */, + 62C26FFA1ABE01850027F8D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + 62C26FFB1ABE01850027F8D4 /* Build configuration list for PBXNativeTarget "CHTWaterfallSwiftDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 62C26FFC1ABE01850027F8D4 /* Debug */, + 62C26FFD1ABE01850027F8D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 62C26FD11ABE01840027F8D4 /* Project object */; +} diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/AppDelegate.swift b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/AppDelegate.swift new file mode 100644 index 0000000..28a7007 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// CHTWaterfallSwiftDemo +// +// Created by Sophie Fader on 3/21/15. +// Copyright (c) 2015 Sophie Fader. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Base.lproj/LaunchScreen.xib b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..1d2b2cb --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTCollectionViewWaterfallLayout.h b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTCollectionViewWaterfallLayout.h new file mode 100644 index 0000000..5d3d4a8 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTCollectionViewWaterfallLayout.h @@ -0,0 +1,297 @@ +// +// UICollectionViewWaterfallLayout.h +// +// Created by Nelson on 12/11/19. +// Copyright (c) 2012 Nelson Tai. All rights reserved. +// + +#import + +/** + * Enumerated structure to define direction in which items can be rendered. + */ +typedef NS_ENUM (NSUInteger, CHTCollectionViewWaterfallLayoutItemRenderDirection) { + CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst, + CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight, + CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft +}; + +/** + * Constants that specify the types of supplementary views that can be presented using a waterfall layout. + */ + +/// A supplementary view that identifies the header for a given section. +extern NSString *const CHTCollectionElementKindSectionHeader; +/// A supplementary view that identifies the footer for a given section. +extern NSString *const CHTCollectionElementKindSectionFooter; + +#pragma mark - CHTCollectionViewDelegateWaterfallLayout + +@class CHTCollectionViewWaterfallLayout; + +/** + * The CHTCollectionViewDelegateWaterfallLayout protocol defines methods that let you coordinate with a + * CHTCollectionViewWaterfallLayout object to implement a waterfall-based layout. + * The methods of this protocol define the size of items. + * + * The waterfall layout object expects the collection view’s delegate object to adopt this protocol. + * Therefore, implement this protocol on object assigned to your collection view’s delegate property. + */ +@protocol CHTCollectionViewDelegateWaterfallLayout +@required +/** + * Asks the delegate for the size of the specified item’s cell. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param indexPath + * The index path of the item. + * + * @return + * The original size of the specified item. Both width and height must be greater than 0. + */ +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; + +@optional +/** + * Asks the delegate for the column count in a section + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The section. + * + * @return + * The original column count for that section. Must be greater than 0. + */ +- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout columnCountForSection:(NSInteger)section; + +/** + * Asks the delegate for the height of the header view in the specified section. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose header size is being requested. + * + * @return + * The height of the header. If you return 0, no header is added. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its headerHeight property to set the size of the header. + * + * @see + * headerHeight + */ +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForHeaderInSection:(NSInteger)section; + +/** + * Asks the delegate for the height of the footer view in the specified section. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose header size is being requested. + * + * @return + * The height of the footer. If you return 0, no footer is added. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its footerHeight property to set the size of the footer. + * + * @see + * footerHeight + */ +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForFooterInSection:(NSInteger)section; + +/** + * Asks the delegate for the insets in the specified section. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose insets are being requested. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its sectionInset property. + * + * @return + * The insets for the section. + */ +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; + +/** + * Asks the delegate for the header insets in the specified section. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose header insets are being requested. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its headerInset property. + * + * @return + * The headerInsets for the section. + */ +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForHeaderInSection:(NSInteger)section; + +/** + * Asks the delegate for the footer insets in the specified section. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose footer insets are being requested. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its footerInset property. + * + * @return + * The footerInsets for the section. + */ +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForFooterInSection:(NSInteger)section; + +/** + * Asks the delegate for the minimum spacing between two items in the same column + * in the specified section. If this method is not implemented, the + * minimumInteritemSpacing property is used for all sections. + * + * @param collectionView + * The collection view object displaying the waterfall layout. + * @param collectionViewLayout + * The layout object requesting the information. + * @param section + * The index of the section whose minimum interitem spacing is being requested. + * + * @discussion + * If you do not implement this method, the waterfall layout uses the value in its minimumInteritemSpacing property to determine the amount of space between items in the same column. + * + * @return + * The minimum interitem spacing. + */ +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; + +@end + +#pragma mark - CHTCollectionViewWaterfallLayout + +/** + * The CHTCollectionViewWaterfallLayout class is a concrete layout object that organizes items into waterfall-based grids + * with optional header and footer views for each section. + * + * A waterfall layout works with the collection view’s delegate object to determine the size of items, headers, and footers + * in each section. That delegate object must conform to the `CHTCollectionViewDelegateWaterfallLayout` protocol. + * + * Each section in a waterfall layout can have its own custom header and footer. To configure the header or footer for a view, + * you must configure the height of the header or footer to be non zero. You can do this by implementing the appropriate delegate + * methods or by assigning appropriate values to the `headerHeight` and `footerHeight` properties. + * If the header or footer height is 0, the corresponding view is not added to the collection view. + * + * @note CHTCollectionViewWaterfallLayout doesn't support decoration view, and it supports vertical scrolling direction only. + */ +@interface CHTCollectionViewWaterfallLayout : UICollectionViewLayout + +/** + * @brief How many columns for this layout. + * @discussion Default: 2 + */ +@property (nonatomic, assign) NSInteger columnCount; + +/** + * @brief The minimum spacing to use between successive columns. + * @discussion Default: 10.0 + */ +@property (nonatomic, assign) CGFloat minimumColumnSpacing; + +/** + * @brief The minimum spacing to use between items in the same column. + * @discussion Default: 10.0 + * @note This spacing is not applied to the space between header and columns or between columns and footer. + */ +@property (nonatomic, assign) CGFloat minimumInteritemSpacing; + +/** + * @brief Height for section header + * @discussion + * If your collectionView's delegate doesn't implement `collectionView:layout:heightForHeaderInSection:`, + * then this value will be used. + * + * Default: 0 + */ +@property (nonatomic, assign) CGFloat headerHeight; + +/** + * @brief Height for section footer + * @discussion + * If your collectionView's delegate doesn't implement `collectionView:layout:heightForFooterInSection:`, + * then this value will be used. + * + * Default: 0 + */ +@property (nonatomic, assign) CGFloat footerHeight; + +/** + * @brief The margins that are used to lay out the header for each section. + * @discussion + * These insets are applied to the headers in each section. + * They represent the distance between the top of the collection view and the top of the content items + * They also indicate the spacing on either side of the header. They do not affect the size of the headers or footers themselves. + * + * Default: UIEdgeInsetsZero + */ +@property (nonatomic, assign) UIEdgeInsets headerInset; + +/** + * @brief The margins that are used to lay out the footer for each section. + * @discussion + * These insets are applied to the footers in each section. + * They represent the distance between the top of the collection view and the top of the content items + * They also indicate the spacing on either side of the footer. They do not affect the size of the headers or footers themselves. + * + * Default: UIEdgeInsetsZero + */ +@property (nonatomic, assign) UIEdgeInsets footerInset; + +/** + * @brief The margins that are used to lay out content in each section. + * @discussion + * Section insets are margins applied only to the items in the section. + * They represent the distance between the header view and the columns and between the columns and the footer view. + * They also indicate the spacing on either side of columns. They do not affect the size of the headers or footers themselves. + * + * Default: UIEdgeInsetsZero + */ +@property (nonatomic, assign) UIEdgeInsets sectionInset; + +/** + * @brief The direction in which items will be rendered in subsequent rows. + * @discussion + * The direction in which each item is rendered. This could be left to right (CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight), right to left (CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft), or shortest column fills first (CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst). + * + * Default: CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst + */ +@property (nonatomic, assign) CHTCollectionViewWaterfallLayoutItemRenderDirection itemRenderDirection; + +/** + * @brief The calculated width of an item in the specified section. + * @discussion + * The width of an item is calculated based on number of columns, the collection view width, and the horizontal insets for that section. + */ +- (CGFloat)itemWidthInSectionAtIndex:(NSInteger)section; + +@end diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTCollectionViewWaterfallLayout.m b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTCollectionViewWaterfallLayout.m new file mode 100644 index 0000000..194659f --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTCollectionViewWaterfallLayout.m @@ -0,0 +1,502 @@ +// +// UICollectionViewWaterfallLayout.m +// +// Created by Nelson on 12/11/19. +// Copyright (c) 2012 Nelson Tai. All rights reserved. +// + +#import "CHTCollectionViewWaterfallLayout.h" + +NSString *const CHTCollectionElementKindSectionHeader = @"CHTCollectionElementKindSectionHeader"; +NSString *const CHTCollectionElementKindSectionFooter = @"CHTCollectionElementKindSectionFooter"; + +@interface CHTCollectionViewWaterfallLayout () +/// The delegate will point to collection view's delegate automatically. +@property (nonatomic, weak) id delegate; +/// Array to store height for each column +@property (nonatomic, strong) NSMutableArray *columnHeights; +/// Array of arrays. Each array stores item attributes for each section +@property (nonatomic, strong) NSMutableArray *sectionItemAttributes; +/// Array to store attributes for all items includes headers, cells, and footers +@property (nonatomic, strong) NSMutableArray *allItemAttributes; +/// Dictionary to store section headers' attribute +@property (nonatomic, strong) NSMutableDictionary *headersAttribute; +/// Dictionary to store section footers' attribute +@property (nonatomic, strong) NSMutableDictionary *footersAttribute; +/// Array to store union rectangles +@property (nonatomic, strong) NSMutableArray *unionRects; +@end + +@implementation CHTCollectionViewWaterfallLayout + +/// How many items to be union into a single rectangle +const NSInteger unionSize = 20; + +#pragma mark - Public Accessors +- (void)setColumnCount:(NSInteger)columnCount { + if (_columnCount != columnCount) { + _columnCount = columnCount; + [self invalidateLayout]; + } +} + +- (void)setMinimumColumnSpacing:(CGFloat)minimumColumnSpacing { + if (_minimumColumnSpacing != minimumColumnSpacing) { + _minimumColumnSpacing = minimumColumnSpacing; + [self invalidateLayout]; + } +} + +- (void)setMinimumInteritemSpacing:(CGFloat)minimumInteritemSpacing { + if (_minimumInteritemSpacing != minimumInteritemSpacing) { + _minimumInteritemSpacing = minimumInteritemSpacing; + [self invalidateLayout]; + } +} + +- (void)setHeaderHeight:(CGFloat)headerHeight { + if (_headerHeight != headerHeight) { + _headerHeight = headerHeight; + [self invalidateLayout]; + } +} + +- (void)setFooterHeight:(CGFloat)footerHeight { + if (_footerHeight != footerHeight) { + _footerHeight = footerHeight; + [self invalidateLayout]; + } +} + +- (void)setHeaderInset:(UIEdgeInsets)headerInset { + if (!UIEdgeInsetsEqualToEdgeInsets(_headerInset, headerInset)) { + _headerInset = headerInset; + [self invalidateLayout]; + } +} + +- (void)setFooterInset:(UIEdgeInsets)footerInset { + if (!UIEdgeInsetsEqualToEdgeInsets(_footerInset, footerInset)) { + _footerInset = footerInset; + [self invalidateLayout]; + } +} + +- (void)setSectionInset:(UIEdgeInsets)sectionInset { + if (!UIEdgeInsetsEqualToEdgeInsets(_sectionInset, sectionInset)) { + _sectionInset = sectionInset; + [self invalidateLayout]; + } +} + +- (void)setItemRenderDirection:(CHTCollectionViewWaterfallLayoutItemRenderDirection)itemRenderDirection { + if (_itemRenderDirection != itemRenderDirection) { + _itemRenderDirection = itemRenderDirection; + [self invalidateLayout]; + } +} + +- (NSInteger)columnCountForSection:(NSInteger)section { + if ([self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)]) { + return [self.delegate collectionView:self.collectionView layout:self columnCountForSection:section]; + } else { + return self.columnCount; + } +} + +- (CGFloat)itemWidthInSectionAtIndex:(NSInteger)section { + UIEdgeInsets sectionInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { + sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; + } else { + sectionInset = self.sectionInset; + } + CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; + NSInteger columnCount = [self columnCountForSection:section]; + return floorf((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); +} + +#pragma mark - Private Accessors +- (NSMutableDictionary *)headersAttribute { + if (!_headersAttribute) { + _headersAttribute = [NSMutableDictionary dictionary]; + } + return _headersAttribute; +} + +- (NSMutableDictionary *)footersAttribute { + if (!_footersAttribute) { + _footersAttribute = [NSMutableDictionary dictionary]; + } + return _footersAttribute; +} + +- (NSMutableArray *)unionRects { + if (!_unionRects) { + _unionRects = [NSMutableArray array]; + } + return _unionRects; +} + +- (NSMutableArray *)columnHeights { + if (!_columnHeights) { + _columnHeights = [NSMutableArray array]; + } + return _columnHeights; +} + +- (NSMutableArray *)allItemAttributes { + if (!_allItemAttributes) { + _allItemAttributes = [NSMutableArray array]; + } + return _allItemAttributes; +} + +- (NSMutableArray *)sectionItemAttributes { + if (!_sectionItemAttributes) { + _sectionItemAttributes = [NSMutableArray array]; + } + return _sectionItemAttributes; +} + +- (id )delegate { + return (id )self.collectionView.delegate; +} + +#pragma mark - Init +- (void)commonInit { + _columnCount = 2; + _minimumColumnSpacing = 10; + _minimumInteritemSpacing = 10; + _headerHeight = 0; + _footerHeight = 0; + _sectionInset = UIEdgeInsetsZero; + _headerInset = UIEdgeInsetsZero; + _footerInset = UIEdgeInsetsZero; + _itemRenderDirection = CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst; +} + +- (id)init { + if (self = [super init]) { + [self commonInit]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self commonInit]; + } + return self; +} + +#pragma mark - Methods to Override +- (void)prepareLayout { + [super prepareLayout]; + + NSInteger numberOfSections = [self.collectionView numberOfSections]; + if (numberOfSections == 0) { + return; + } + + NSAssert([self.delegate conformsToProtocol:@protocol(CHTCollectionViewDelegateWaterfallLayout)], @"UICollectionView's delegate should conform to CHTCollectionViewDelegateWaterfallLayout protocol"); + NSAssert(self.columnCount > 0 || [self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)], @"UICollectionViewWaterfallLayout's columnCount should be greater than 0, or delegate must implement columnCountForSection:"); + + // Initialize variables + NSInteger idx = 0; + + [self.headersAttribute removeAllObjects]; + [self.footersAttribute removeAllObjects]; + [self.unionRects removeAllObjects]; + [self.columnHeights removeAllObjects]; + [self.allItemAttributes removeAllObjects]; + [self.sectionItemAttributes removeAllObjects]; + + for (NSInteger section = 0; section < numberOfSections; section++) { + NSInteger columnCount = [self columnCountForSection:section]; + NSMutableArray *sectionColumnHeights = [NSMutableArray arrayWithCapacity:columnCount]; + for (idx = 0; idx < columnCount; idx++) { + [sectionColumnHeights addObject:@(0)]; + } + [self.columnHeights addObject:sectionColumnHeights]; + } + // Create attributes + CGFloat top = 0; + UICollectionViewLayoutAttributes *attributes; + + for (NSInteger section = 0; section < numberOfSections; ++section) { + /* + * 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset) + */ + CGFloat minimumInteritemSpacing; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) { + minimumInteritemSpacing = [self.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section]; + } else { + minimumInteritemSpacing = self.minimumInteritemSpacing; + } + + UIEdgeInsets sectionInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { + sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; + } else { + sectionInset = self.sectionInset; + } + + CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; + NSInteger columnCount = [self columnCountForSection:section]; + CGFloat itemWidth = floorf((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); + + /* + * 2. Section header + */ + CGFloat headerHeight; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForHeaderInSection:)]) { + headerHeight = [self.delegate collectionView:self.collectionView layout:self heightForHeaderInSection:section]; + } else { + headerHeight = self.headerHeight; + } + + UIEdgeInsets headerInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForHeaderInSection:)]) { + headerInset = [self.delegate collectionView:self.collectionView layout:self insetForHeaderInSection:section]; + } else { + headerInset = self.headerInset; + } + + top += headerInset.top; + + if (headerHeight > 0) { + attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + attributes.frame = CGRectMake(headerInset.left, + top, + self.collectionView.frame.size.width - (headerInset.left + headerInset.right), + headerHeight); + + self.headersAttribute[@(section)] = attributes; + [self.allItemAttributes addObject:attributes]; + + top = CGRectGetMaxY(attributes.frame) + headerInset.bottom; + } + + top += sectionInset.top; + for (idx = 0; idx < columnCount; idx++) { + self.columnHeights[section][idx] = @(top); + } + + /* + * 3. Section items + */ + NSInteger itemCount = [self.collectionView numberOfItemsInSection:section]; + NSMutableArray *itemAttributes = [NSMutableArray arrayWithCapacity:itemCount]; + + // Item will be put into shortest column. + for (idx = 0; idx < itemCount; idx++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; + NSUInteger columnIndex = [self nextColumnIndexForItem:idx inSection:section]; + CGFloat xOffset = sectionInset.left + (itemWidth + self.minimumColumnSpacing) * columnIndex; + CGFloat yOffset = [self.columnHeights[section][columnIndex] floatValue]; + CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath]; + CGFloat itemHeight = 0; + if (itemSize.height > 0 && itemSize.width > 0) { + itemHeight = floorf(itemSize.height * itemWidth / itemSize.width); + } + + attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + attributes.frame = CGRectMake(xOffset, yOffset, itemWidth, itemHeight); + [itemAttributes addObject:attributes]; + [self.allItemAttributes addObject:attributes]; + self.columnHeights[section][columnIndex] = @(CGRectGetMaxY(attributes.frame) + minimumInteritemSpacing); + } + + [self.sectionItemAttributes addObject:itemAttributes]; + + /* + * 4. Section footer + */ + CGFloat footerHeight; + NSUInteger columnIndex = [self longestColumnIndexInSection:section]; + top = [self.columnHeights[section][columnIndex] floatValue] - minimumInteritemSpacing + sectionInset.bottom; + + if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForFooterInSection:)]) { + footerHeight = [self.delegate collectionView:self.collectionView layout:self heightForFooterInSection:section]; + } else { + footerHeight = self.footerHeight; + } + + UIEdgeInsets footerInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForFooterInSection:)]) { + footerInset = [self.delegate collectionView:self.collectionView layout:self insetForFooterInSection:section]; + } else { + footerInset = self.footerInset; + } + + top += footerInset.top; + + if (footerHeight > 0) { + attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + attributes.frame = CGRectMake(footerInset.left, + top, + self.collectionView.frame.size.width - (footerInset.left + footerInset.right), + footerHeight); + + self.footersAttribute[@(section)] = attributes; + [self.allItemAttributes addObject:attributes]; + + top = CGRectGetMaxY(attributes.frame) + footerInset.bottom; + } + + for (idx = 0; idx < columnCount; idx++) { + self.columnHeights[section][idx] = @(top); + } + } // end of for (NSInteger section = 0; section < numberOfSections; ++section) + + // Build union rects + idx = 0; + NSInteger itemCounts = [self.allItemAttributes count]; + while (idx < itemCounts) { + CGRect rect1 = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; + idx = MIN(idx + unionSize, itemCounts) - 1; + CGRect rect2 = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; + [self.unionRects addObject:[NSValue valueWithCGRect:CGRectUnion(rect1, rect2)]]; + idx++; + } +} + +- (CGSize)collectionViewContentSize { + NSInteger numberOfSections = [self.collectionView numberOfSections]; + if (numberOfSections == 0) { + return CGSizeZero; + } + + CGSize contentSize = self.collectionView.bounds.size; + contentSize.height = [[[self.columnHeights lastObject] firstObject] floatValue]; + + return contentSize; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path { + if (path.section >= [self.sectionItemAttributes count]) { + return nil; + } + if (path.item >= [self.sectionItemAttributes[path.section] count]) { + return nil; + } + return (self.sectionItemAttributes[path.section])[path.item]; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attribute = nil; + if ([kind isEqualToString:CHTCollectionElementKindSectionHeader]) { + attribute = self.headersAttribute[@(indexPath.section)]; + } else if ([kind isEqualToString:CHTCollectionElementKindSectionFooter]) { + attribute = self.footersAttribute[@(indexPath.section)]; + } + return attribute; +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { + NSInteger i; + NSInteger begin = 0, end = self.unionRects.count; + NSMutableArray *attrs = [NSMutableArray array]; + + for (i = 0; i < self.unionRects.count; i++) { + if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) { + begin = i * unionSize; + break; + } + } + for (i = self.unionRects.count - 1; i >= 0; i--) { + if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) { + end = MIN((i + 1) * unionSize, self.allItemAttributes.count); + break; + } + } + for (i = begin; i < end; i++) { + UICollectionViewLayoutAttributes *attr = self.allItemAttributes[i]; + if (CGRectIntersectsRect(rect, attr.frame)) { + [attrs addObject:attr]; + } + } + + return [NSArray arrayWithArray:attrs]; +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { + CGRect oldBounds = self.collectionView.bounds; + if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { + return YES; + } + return NO; +} + +#pragma mark - Private Methods + +/** + * Find the shortest column. + * + * @return index for the shortest column + */ +- (NSUInteger)shortestColumnIndexInSection:(NSInteger)section { + __block NSUInteger index = 0; + __block CGFloat shortestHeight = MAXFLOAT; + + [self.columnHeights[section] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + CGFloat height = [obj floatValue]; + if (height < shortestHeight) { + shortestHeight = height; + index = idx; + } + }]; + + return index; +} + +/** + * Find the longest column. + * + * @return index for the longest column + */ +- (NSUInteger)longestColumnIndexInSection:(NSInteger)section { + __block NSUInteger index = 0; + __block CGFloat longestHeight = 0; + + [self.columnHeights[section] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + CGFloat height = [obj floatValue]; + if (height > longestHeight) { + longestHeight = height; + index = idx; + } + }]; + + return index; +} + +/** + * Find the index for the next column. + * + * @return index for the next column + */ +- (NSUInteger)nextColumnIndexForItem:(NSInteger)item inSection:(NSInteger)section { + NSUInteger index = 0; + NSInteger columnCount = [self columnCountForSection:section]; + switch (self.itemRenderDirection) { + case CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst: + index = [self shortestColumnIndexInSection:section]; + break; + + case CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight: + index = (item % columnCount); + break; + + case CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft: + index = (columnCount - 1) - (item % columnCount); + break; + + default: + index = [self shortestColumnIndexInSection:section]; + break; + } + return index; +} + +@end diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo-Bridging-Header.h b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo-Bridging-Header.h new file mode 100644 index 0000000..999b90f --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "CHTCollectionViewWaterfallLayout.h" \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ImageUICollectionViewCell.swift b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ImageUICollectionViewCell.swift new file mode 100644 index 0000000..f0846b5 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ImageUICollectionViewCell.swift @@ -0,0 +1,20 @@ +// +// ImageUICollectionViewCell.swift +// CHTWaterfallSwift +// +// Created by Sophie Fader on 3/21/15. +// Copyright (c) 2015 Sophie Fader. All rights reserved. +// + +import UIKit + +class ImageUICollectionViewCell: UICollectionViewCell { + + @IBOutlet weak var image: UIImageView! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + +} diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ImageUICollectionViewCell.xib b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ImageUICollectionViewCell.xib new file mode 100644 index 0000000..cbcefc8 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ImageUICollectionViewCell.xib @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/AppIcon.appiconset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..36d2c80 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image1.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image1.imageset/Contents.json new file mode 100644 index 0000000..2f50291 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-black-white-vintage-suitcase-girl-railroadtracks-walking-1.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image1.imageset/public-domain-images-free-stock-photos-black-white-vintage-suitcase-girl-railroadtracks-walking-1.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image1.imageset/public-domain-images-free-stock-photos-black-white-vintage-suitcase-girl-railroadtracks-walking-1.jpg new file mode 100644 index 0000000..8dee084 Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image1.imageset/public-domain-images-free-stock-photos-black-white-vintage-suitcase-girl-railroadtracks-walking-1.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image2.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image2.imageset/Contents.json new file mode 100644 index 0000000..3c520b2 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-brick-wall-rustic-old-metal-doors-private-parking-1000x664.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image2.imageset/public-domain-images-free-stock-photos-brick-wall-rustic-old-metal-doors-private-parking-1000x664.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image2.imageset/public-domain-images-free-stock-photos-brick-wall-rustic-old-metal-doors-private-parking-1000x664.jpg new file mode 100644 index 0000000..2cacc42 Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image2.imageset/public-domain-images-free-stock-photos-brick-wall-rustic-old-metal-doors-private-parking-1000x664.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image3.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image3.imageset/Contents.json new file mode 100644 index 0000000..61ad8fe --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-down-town-chicago-blue-sky-6.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image3.imageset/public-domain-images-free-stock-photos-down-town-chicago-blue-sky-6.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image3.imageset/public-domain-images-free-stock-photos-down-town-chicago-blue-sky-6.jpg new file mode 100644 index 0000000..0f3fc6a Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image3.imageset/public-domain-images-free-stock-photos-down-town-chicago-blue-sky-6.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image4.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image4.imageset/Contents.json new file mode 100644 index 0000000..eeb1337 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-down-town-chicago-tribune-building-1-1000x666.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image4.imageset/public-domain-images-free-stock-photos-down-town-chicago-tribune-building-1-1000x666.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image4.imageset/public-domain-images-free-stock-photos-down-town-chicago-tribune-building-1-1000x666.jpg new file mode 100644 index 0000000..fd4c947 Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image4.imageset/public-domain-images-free-stock-photos-down-town-chicago-tribune-building-1-1000x666.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image5.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image5.imageset/Contents.json new file mode 100644 index 0000000..7b291a3 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-5.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image5.imageset/public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-5.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image5.imageset/public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-5.jpg new file mode 100644 index 0000000..ff36a2d Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image5.imageset/public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-5.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image6.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image6.imageset/Contents.json new file mode 100644 index 0000000..251d8fc --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-10.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image6.imageset/public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-10.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image6.imageset/public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-10.jpg new file mode 100644 index 0000000..2d1201a Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image6.imageset/public-domain-images-free-stock-photos-high-quality-resolution-downloads-public-domain-archive-10.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image7.imageset/Contents.json b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image7.imageset/Contents.json new file mode 100644 index 0000000..da2900a --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "public-domain-images-free-stock-photos-sky-scrapers-bricks-chicago-161-1000x666.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image7.imageset/public-domain-images-free-stock-photos-sky-scrapers-bricks-chicago-161-1000x666.jpg b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image7.imageset/public-domain-images-free-stock-photos-sky-scrapers-bricks-chicago-161-1000x666.jpg new file mode 100644 index 0000000..21580ca Binary files /dev/null and b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Images.xcassets/image7.imageset/public-domain-images-free-stock-photos-sky-scrapers-bricks-chicago-161-1000x666.jpg differ diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Info.plist b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Info.plist new file mode 100644 index 0000000..a78e892 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.sophiefader.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Main.storyboard b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Main.storyboard new file mode 100644 index 0000000..a385866 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Main.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Model.swift b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Model.swift new file mode 100644 index 0000000..d1bafba --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/Model.swift @@ -0,0 +1,39 @@ +// +// Model.swift +// CHTWaterfallSwift +// +// Created by Sophie Fader on 3/21/15. +// Copyright (c) 2015 Sophie Fader. All rights reserved. +// + +import UIKit + +class Model: NSObject { + + var images : [UIImage] = [] + + + // Assemble an array of images to use for sample content for the collectionView + func buildDataSource(){ + + var image1 = UIImage(named: "image1") + var image2 = UIImage(named: "image2") + var image3 = UIImage(named: "image3") + var image4 = UIImage(named: "image4") + var image5 = UIImage(named: "image5") + var image6 = UIImage(named: "image6") + var image7 = UIImage(named: "image7") + + images.append(image1!) + images.append(image2!) + images.append(image3!) + images.append(image4!) + images.append(image5!) + images.append(image6!) + images.append(image7!) + + + } + + +} diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ViewController.swift b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ViewController.swift new file mode 100644 index 0000000..2871eaf --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemo/ViewController.swift @@ -0,0 +1,103 @@ +// +// ViewController.swift +// CHTWaterfallSwift +// +// Created by Sophie Fader on 3/21/15. +// Copyright (c) 2015 Sophie Fader. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, CHTCollectionViewDelegateWaterfallLayout { + + @IBOutlet weak var collectionView: UICollectionView! + let model = Model() + + + //MARK: - View Controller Lifecycle + override func viewDidLoad() { + + super.viewDidLoad() + model.buildDataSource() + + + // Attach datasource and delegate + self.collectionView.dataSource = self + self.collectionView.delegate = self + + //Layout setup + setupCollectionView() + + //Register nibs + registerNibs() + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + //MARK: - CollectionView UI Setup + func setupCollectionView(){ + + // Create a waterfall layout + var layout = CHTCollectionViewWaterfallLayout() + + // Change individual layout attributes for the spacing between cells + layout.minimumColumnSpacing = 1.0 + layout.minimumInteritemSpacing = 1.0 + + // Collection view attributes + self.collectionView.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth + self.collectionView.alwaysBounceVertical = true + + // Add the waterfall layout to your collection view + self.collectionView.collectionViewLayout = layout + } + + // Register CollectionView Nibs + func registerNibs(){ + + // attach the UI nib file for the ImageUICollectionViewCell to the collectionview + var viewNib = UINib(nibName: "ImageUICollectionViewCell", bundle: nil) + collectionView.registerNib(viewNib, forCellWithReuseIdentifier: "cell") + } + + + + + //MARK: - CollectionView Delegate Methods + + //** Number of Cells in the CollectionView */ + func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return model.images.count + } + + + //** Create a basic CollectionView Cell */ + func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + + // Create the cell and return the cell + var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageUICollectionViewCell + + // Add image to cell + cell.image.image = model.images[indexPath.row] + return cell + } + + + //MARK: - CollectionView Waterfall Layout Delegate Methods (Required) + + //** Size for the cells in the Waterfall Layout */ + func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout!, sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize { + + // create a cell size from the image size, and return the size + let imageSize = model.images[indexPath.row].size + + return imageSize + } + + + +} + diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemoTests/CHTWaterfallSwiftDemoTests.swift b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemoTests/CHTWaterfallSwiftDemoTests.swift new file mode 100644 index 0000000..98ecd36 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemoTests/CHTWaterfallSwiftDemoTests.swift @@ -0,0 +1,36 @@ +// +// CHTWaterfallSwiftDemoTests.swift +// CHTWaterfallSwiftDemoTests +// +// Created by Sophie Fader on 3/21/15. +// Copyright (c) 2015 Sophie Fader. All rights reserved. +// + +import UIKit +import XCTest + +class CHTWaterfallSwiftDemoTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + XCTAssert(true, "Pass") + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock() { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemoTests/Info.plist b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemoTests/Info.plist new file mode 100644 index 0000000..1dc4500 --- /dev/null +++ b/SwiftDemo/CHTWaterfallSwiftDemo/CHTWaterfallSwiftDemoTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.sophiefader.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + +