diff --git a/CHTCollectionViewWaterfallLayout.podspec b/CHTCollectionViewWaterfallLayout.podspec index 7dc3ec5..e90a15a 100644 --- a/CHTCollectionViewWaterfallLayout.podspec +++ b/CHTCollectionViewWaterfallLayout.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CHTCollectionViewWaterfallLayout" - s.version = "0.9.7" + s.version = "0.9.8" 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" @@ -19,8 +19,9 @@ Pod::Spec.new do |s| ss.source_files = '*.{h,m}' end + s.swift_version = '4.2' s.subspec 'Swift' do |ss| ss.ios.deployment_target = '8.0' - ss.source_files = '*.swift' + ss.source_files = 'SwiftSources/**/*' end end diff --git a/CHTCollectionViewWaterfallLayout.swift b/CHTCollectionViewWaterfallLayout.swift deleted file mode 100755 index fbc0342..0000000 --- a/CHTCollectionViewWaterfallLayout.swift +++ /dev/null @@ -1,432 +0,0 @@ -// -// CHTCollectionViewWaterfallLayout.swift -// PinterestSwift -// -// Created by Nicholas Tau on 6/30/14. -// Copyright (c) 2014 Nicholas Tau. All rights reserved. -// - -import Foundation -import UIKit -fileprivate func < (lhs: T?, rhs: T?) -> Bool { - switch (lhs, rhs) { - case let (l?, r?): - return l < r - case (nil, _?): - return true - default: - return false - } -} - -fileprivate func > (lhs: T?, rhs: T?) -> Bool { - switch (lhs, rhs) { - case let (l?, r?): - return l > r - default: - return rhs < lhs - } -} - - -@objc public protocol CHTCollectionViewDelegateWaterfallLayout: UICollectionViewDelegate { - - func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize - - @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, - heightForHeaderInSection section: Int) -> CGFloat - - @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, - heightForFooterInSection section: Int) -> CGFloat - - @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, - insetForSectionAtIndex section: Int) -> UIEdgeInsets - - @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, - minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat - - @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, - columnCountForSection section: Int) -> Int -} - -public enum CHTCollectionViewWaterfallLayoutItemRenderDirection: Int { - case chtCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst - case chtCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight - case chtCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft -} - -public let CHTCollectionElementKindSectionHeader = "CHTCollectionElementKindSectionHeader" -public let CHTCollectionElementKindSectionFooter = "CHTCollectionElementKindSectionFooter" -public class CHTCollectionViewWaterfallLayout: UICollectionViewLayout { - public var columnCount: Int { - didSet { - invalidateLayout() - }} - - public var minimumColumnSpacing: CGFloat { - didSet { - invalidateLayout() - }} - - public var minimumInteritemSpacing: CGFloat { - didSet { - invalidateLayout() - }} - - public var headerHeight: CGFloat { - didSet { - invalidateLayout() - }} - - public var footerHeight: CGFloat { - didSet { - invalidateLayout() - }} - - public var sectionInset: UIEdgeInsets { - didSet { - invalidateLayout() - }} - - - public var itemRenderDirection: CHTCollectionViewWaterfallLayoutItemRenderDirection { - didSet { - invalidateLayout() - } - } - - - // private property and method above. - public weak var delegate: CHTCollectionViewDelegateWaterfallLayout? { - get { - return self.collectionView!.delegate as? CHTCollectionViewDelegateWaterfallLayout - } - } - public var columnHeights: [[CGFloat]] - public var sectionItemAttributes: [[UICollectionViewLayoutAttributes]] - public var allItemAttributes: [UICollectionViewLayoutAttributes] - public var headersAttributes: [Int: UICollectionViewLayoutAttributes] - public var footersAttributes: [Int: UICollectionViewLayoutAttributes] - public var unionRects: [NSValue] - public let unionSize = 20 - - override public init() { - self.headerHeight = 0.0 - self.footerHeight = 0.0 - self.columnCount = 2 - self.minimumInteritemSpacing = 10 - self.minimumColumnSpacing = 10 - self.sectionInset = UIEdgeInsets.zero - self.itemRenderDirection = - CHTCollectionViewWaterfallLayoutItemRenderDirection.chtCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst - - headersAttributes = [:] - footersAttributes = [:] - unionRects = [] - columnHeights = [] - allItemAttributes = [] - sectionItemAttributes = [] - - super.init() - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func columnCountForSection (_ section: Int) -> Int { - if let columnCount = self.delegate?.collectionView?(self.collectionView!, layout: self, columnCountForSection: section) { - return columnCount - } else { - return self.columnCount - } - } - - public func itemWidthInSectionAtIndex (_ section: Int) -> CGFloat { - var insets: UIEdgeInsets - if let sectionInsets = self.delegate?.collectionView?(self.collectionView!, layout: self, insetForSectionAtIndex: section) { - insets = sectionInsets - } else { - insets = self.sectionInset - } - let width: CGFloat = self.collectionView!.bounds.size.width - insets.left - insets.right - let columnCount = self.columnCountForSection(section) - let spaceColumCount: CGFloat = CGFloat(columnCount - 1) - return floor((width - (spaceColumCount * self.minimumColumnSpacing)) / CGFloat(columnCount)) - } - - override public func prepare() { - super.prepare() - - let numberOfSections = self.collectionView!.numberOfSections - if numberOfSections == 0 { - return - } - - self.headersAttributes = [:] - self.footersAttributes = [:] - self.unionRects = [] - self.columnHeights = [] - self.allItemAttributes = [] - self.sectionItemAttributes = [] - - for section in 0 ..< numberOfSections { - let columnCount = self.columnCountForSection(section) - var sectionColumnHeights: [CGFloat] = [] - for idx in 0 ..< columnCount { - sectionColumnHeights.append(CGFloat(idx)) - } - self.columnHeights.append(sectionColumnHeights) - } - - var top: CGFloat = 0.0 - var attributes = UICollectionViewLayoutAttributes() - - for section in 0 ..< numberOfSections { - /* - * 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset) - */ - var minimumInteritemSpacing: CGFloat - if let miniumSpaceing = self.delegate?.collectionView?(self.collectionView!, layout: self, minimumInteritemSpacingForSectionAtIndex: section) { - minimumInteritemSpacing = miniumSpaceing - } else { - minimumInteritemSpacing = self.minimumColumnSpacing - } - - var sectionInsets: UIEdgeInsets - if let insets = self.delegate?.collectionView?(self.collectionView!, layout: self, insetForSectionAtIndex: section) { - sectionInsets = insets - } else { - sectionInsets = self.sectionInset - } - - let width = self.collectionView!.bounds.size.width - sectionInsets.left - sectionInsets.right - let columnCount = self.columnCountForSection(section) - let spaceColumCount = CGFloat(columnCount - 1) - let itemWidth = floor((width - (spaceColumCount * self.minimumColumnSpacing)) / CGFloat(columnCount)) - - /* - * 2. Section header - */ - var heightHeader: CGFloat - if let height = self.delegate?.collectionView?(self.collectionView!, layout: self, heightForHeaderInSection: section) { - heightHeader = height - } else { - heightHeader = self.headerHeight - } - - if heightHeader > 0 { - attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: CHTCollectionElementKindSectionHeader, with: IndexPath(row: 0, section: section)) - attributes.frame = CGRect(x: 0, y: top, width: self.collectionView!.bounds.size.width, height: heightHeader) - self.headersAttributes[section] = attributes - self.allItemAttributes.append(attributes) - - top = attributes.frame.maxY - } - top += sectionInsets.top - for idx in 0 ..< columnCount { - self.columnHeights[section][idx]=top - } - - /* - * 3. Section items - */ - let itemCount = self.collectionView!.numberOfItems(inSection: section) - var itemAttributes: [UICollectionViewLayoutAttributes] = [] - - // Item will be put into shortest column. - for idx in 0 ..< itemCount { - let indexPath = IndexPath(item: idx, section: section) - - let columnIndex = self.nextColumnIndexForItem(idx, section: section) - let xOffset = sectionInsets.left + (itemWidth + self.minimumColumnSpacing) * CGFloat(columnIndex) - - let yOffset = ((self.columnHeights[section] as AnyObject).object (at: columnIndex) as AnyObject).doubleValue - let itemSize = self.delegate?.collectionView(self.collectionView!, layout: self, sizeForItemAtIndexPath: indexPath) - var itemHeight: CGFloat = 0.0 - if itemSize?.height > 0 && itemSize?.width > 0 { - itemHeight = floor(itemSize!.height * itemWidth / itemSize!.width) - } - - attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) - attributes.frame = CGRect(x: xOffset, y: CGFloat(yOffset!), width: itemWidth, height: itemHeight) - itemAttributes.append(attributes) - self.allItemAttributes.append(attributes) - - self.columnHeights[section][columnIndex] = attributes.frame.maxY + minimumInteritemSpacing - - } - self.sectionItemAttributes.append(itemAttributes) - - /* - * 4. Section footer - */ - var footerHeight: CGFloat = 0.0 - let columnIndex = self.longestColumnIndexInSection(section) - top = self.columnHeights[section][columnIndex] - minimumInteritemSpacing + sectionInsets.bottom - - if let height = self.delegate?.collectionView?(self.collectionView!, layout: self, heightForFooterInSection: section) { - footerHeight = height - } else { - footerHeight = self.footerHeight - } - - if footerHeight > 0 { - attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: CHTCollectionElementKindSectionFooter, with: IndexPath(item: 0, section: section)) - attributes.frame = CGRect(x: 0, y: top, width: self.collectionView!.bounds.size.width, height: footerHeight) - self.footersAttributes[section] = attributes - self.allItemAttributes.append(attributes) - top = attributes.frame.maxY - } - - for idx in 0 ..< columnCount { - self.columnHeights[section][idx] = top - } - } - - var idx = 0 - let itemCounts = self.allItemAttributes.count - while idx < itemCounts { - let rect1 = self.allItemAttributes[idx].frame - idx = min(idx + unionSize, itemCounts) - 1 - let rect2 = self.allItemAttributes[idx].frame - self.unionRects.append(NSValue(cgRect:rect1.union(rect2))) - idx += 1 - } - } - - override public var collectionViewContentSize: CGSize { - let numberOfSections = self.collectionView!.numberOfSections - if numberOfSections == 0 { - return CGSize.zero - } - - var contentSize = self.collectionView!.bounds.size as CGSize - - if columnHeights.count > 0 { - if let height = self.columnHeights[columnHeights.count - 1].first { - contentSize.height = height - return contentSize - } - } - return CGSize.zero - } - - override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { - if (indexPath as NSIndexPath).section >= self.sectionItemAttributes.count { - return nil - } - let list = self.sectionItemAttributes[indexPath.section] - if (indexPath as NSIndexPath).item >= list.count { - return nil - } - return list[indexPath.item] - } - - override public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes { - var attribute: UICollectionViewLayoutAttributes? - if elementKind == CHTCollectionElementKindSectionHeader { - attribute = self.headersAttributes[indexPath.section] - } else if elementKind == CHTCollectionElementKindSectionFooter { - attribute = self.footersAttributes[indexPath.section] - } - guard let returnAttribute = attribute else { - return UICollectionViewLayoutAttributes() - } - return returnAttribute - } - - override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { - var begin = 0, end = self.unionRects.count - var attrs: [UICollectionViewLayoutAttributes] = [] - - for i in 0 ..< end { - let unionRect = self.unionRects[i] - if rect.intersects(unionRect.cgRectValue) { - begin = i * unionSize - break - } - } - for i in (0 ..< self.unionRects.count).reversed() { - let unionRect = self.unionRects[i] - if rect.intersects(unionRect.cgRectValue) { - end = min((i + 1) * unionSize, self.allItemAttributes.count) - break - } - } - for i in begin ..< end { - let attr = self.allItemAttributes[i] - if rect.intersects(attr.frame) { - attrs.append(attr) - } - } - - return attrs - } - - override public func shouldInvalidateLayout (forBoundsChange newBounds: CGRect) -> Bool { - let oldBounds = self.collectionView!.bounds - if newBounds.width != oldBounds.width { - return true - } - return false - } - - - /** - * Find the shortest column. - * - * @return index for the shortest column - */ - public func shortestColumnIndexInSection (_ section: Int) -> Int { - var index = 0 - var shorestHeight = CGFloat.greatestFiniteMagnitude - for (idx, height) in self.columnHeights[section].enumerated() { - if height < shorestHeight { - shorestHeight = height - index = idx - } - } - return index - } - - /** - * Find the longest column. - * - * @return index for the longest column - */ - - public func longestColumnIndexInSection (_ section: Int) -> Int { - var index = 0 - var longestHeight: CGFloat = 0.0 - - for (idx, height) in self.columnHeights[section].enumerated() { - if height > longestHeight { - longestHeight = height - index = idx - } - } - return index - - } - - /** - * Find the index for the next column. - * - * @return index for the next column - */ - public func nextColumnIndexForItem (_ item: Int, section: Int) -> Int { - var index = 0 - let columnCount = self.columnCountForSection(section) - switch self.itemRenderDirection { - case .chtCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst : - index = self.shortestColumnIndexInSection(section) - case .chtCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight : - index = (item%columnCount) - case .chtCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft: - index = (columnCount - 1) - (item % columnCount) - } - return index - } -} diff --git a/CHTCollectionViewWaterfallLayout/CHTCollectionViewWaterfallLayout.xcodeproj/project.pbxproj b/CHTCollectionViewWaterfallLayout/CHTCollectionViewWaterfallLayout.xcodeproj/project.pbxproj index 1870fb9..12199ca 100644 --- a/CHTCollectionViewWaterfallLayout/CHTCollectionViewWaterfallLayout.xcodeproj/project.pbxproj +++ b/CHTCollectionViewWaterfallLayout/CHTCollectionViewWaterfallLayout.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 0C2340CA1DC7E6000077FA16 /* CHTCollectionViewWaterfallLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 30CCE2141C9A8F7E00429C17 /* CHTCollectionViewWaterfallLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0C37C5011DC8314900CA7DA8 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0C37C5001DC8314900CA7DA8 /* Info.plist */; }; 30CCE2171C9A8F7E00429C17 /* CHTCollectionViewWaterfallLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 30CCE2151C9A8F7E00429C17 /* CHTCollectionViewWaterfallLayout.m */; }; /* End PBXBuildFile section */ @@ -123,7 +122,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0C37C5011DC8314900CA7DA8 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Demo/Swift/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj b/Demo/Swift/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj index e5efd8d..7d9ac17 100644 --- a/Demo/Swift/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj +++ b/Demo/Swift/CHTWaterfallSwiftDemo.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 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 */; }; @@ -19,26 +18,13 @@ 62C270071ABE01E70027F8D4 /* ImageUICollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62C270051ABE01E70027F8D4 /* ImageUICollectionViewCell.xib */; }; /* 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 */ - 52FADF3C1CB650DC0097FB12 /* CHTCollectionViewWaterfallLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CHTCollectionViewWaterfallLayout.swift; path = ../../../CHTCollectionViewWaterfallLayout.swift; sourceTree = ""; }; + 52FADF3C1CB650DC0097FB12 /* CHTCollectionViewWaterfallLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CHTCollectionViewWaterfallLayout.swift; path = ../../../SwiftSources/CHTCollectionViewWaterfallLayout.swift; sourceTree = ""; }; 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 = ""; }; @@ -54,13 +40,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 62C26FEB1ABE01850027F8D4 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -76,7 +55,6 @@ isa = PBXGroup; children = ( 62C26FDB1ABE01840027F8D4 /* CHTWaterfallSwiftDemo */, - 62C26FF11ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests */, 62C26FDA1ABE01840027F8D4 /* Products */, ); sourceTree = ""; @@ -85,7 +63,6 @@ isa = PBXGroup; children = ( 62C26FD91ABE01840027F8D4 /* CHTWaterfallSwiftDemo.app */, - 62C26FEE1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests.xctest */, ); name = Products; sourceTree = ""; @@ -115,23 +92,6 @@ 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 */ @@ -152,24 +112,6 @@ 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 */ @@ -178,21 +120,18 @@ attributes = { LastSwiftMigration = 0730; LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0730; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Sophie Fader"; TargetAttributes = { 62C26FD81ABE01840027F8D4 = { CreatedOnToolsVersion = 6.1.1; - }; - 62C26FED1ABE01850027F8D4 = { - CreatedOnToolsVersion = 6.1.1; - TestTargetID = 62C26FD81ABE01840027F8D4; + DevelopmentTeam = KZ7D8X3PRK; }; }; }; buildConfigurationList = 62C26FD41ABE01840027F8D4 /* Build configuration list for PBXProject "CHTWaterfallSwiftDemo" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -204,7 +143,6 @@ projectRoot = ""; targets = ( 62C26FD81ABE01840027F8D4 /* CHTWaterfallSwiftDemo */, - 62C26FED1ABE01850027F8D4 /* CHTWaterfallSwiftDemoTests */, ); }; /* End PBXProject section */ @@ -221,13 +159,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 62C26FEC1ABE01850027F8D4 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -243,24 +174,8 @@ ); 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; @@ -277,17 +192,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -296,6 +222,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -321,17 +248,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -339,6 +277,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -348,6 +287,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -362,8 +302,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.sophiefader.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -376,43 +315,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.sophiefader.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = ""; - }; - 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_BUNDLE_IDENTIFIER = "com.sophiefader.$(PRODUCT_NAME:rfc1034identifier)"; - 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_BUNDLE_IDENTIFIER = "com.sophiefader.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CHTWaterfallSwiftDemo.app/CHTWaterfallSwiftDemo"; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -437,15 +340,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 62C26FFB1ABE01850027F8D4 /* Build configuration list for PBXNativeTarget "CHTWaterfallSwiftDemoTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 62C26FFC1ABE01850027F8D4 /* Debug */, - 62C26FFD1ABE01850027F8D4 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 62C26FD11ABE01840027F8D4 /* Project object */; diff --git a/Demo/Swift/CHTWaterfallSwiftDemo/AppDelegate.swift b/Demo/Swift/CHTWaterfallSwiftDemo/AppDelegate.swift index 28a7007..af29d6a 100644 --- a/Demo/Swift/CHTWaterfallSwiftDemo/AppDelegate.swift +++ b/Demo/Swift/CHTWaterfallSwiftDemo/AppDelegate.swift @@ -14,30 +14,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // Override point for customization after application launch. return true } - func applicationWillResignActive(application: UIApplication) { + 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) { + 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) { + 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) { + 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) { + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } diff --git a/Demo/Swift/CHTWaterfallSwiftDemo/Model.swift b/Demo/Swift/CHTWaterfallSwiftDemo/Model.swift index b649c0a..32a020d 100644 --- a/Demo/Swift/CHTWaterfallSwiftDemo/Model.swift +++ b/Demo/Swift/CHTWaterfallSwiftDemo/Model.swift @@ -12,28 +12,9 @@ class Model: NSObject { var images : [UIImage] = [] - // Assemble an array of images to use for sample content for the collectionView func buildDataSource(){ - - let image1 = UIImage(named: "image1") - let image2 = UIImage(named: "image2") - let image3 = UIImage(named: "image3") - let image4 = UIImage(named: "image4") - let image5 = UIImage(named: "image5") - let image6 = UIImage(named: "image6") - let 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!) - - + images = (1...7).map { UIImage(named: "image\($0)")! } } - } diff --git a/Demo/Swift/CHTWaterfallSwiftDemo/ViewController.swift b/Demo/Swift/CHTWaterfallSwiftDemo/ViewController.swift index 879e7fb..eb9539c 100644 --- a/Demo/Swift/CHTWaterfallSwiftDemo/ViewController.swift +++ b/Demo/Swift/CHTWaterfallSwiftDemo/ViewController.swift @@ -22,8 +22,8 @@ class ViewController: UIViewController, UICollectionViewDelegate, UICollectionVi // Attach datasource and delegate - self.collectionView.dataSource = self - self.collectionView.delegate = self + collectionView.dataSource = self + collectionView.delegate = self //Layout setup setupCollectionView() @@ -48,11 +48,11 @@ class ViewController: UIViewController, UICollectionViewDelegate, UICollectionVi layout.minimumInteritemSpacing = 1.0 // Collection view attributes - self.collectionView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth] - self.collectionView.alwaysBounceVertical = true + collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth] + collectionView.alwaysBounceVertical = true // Add the waterfall layout to your collection view - self.collectionView.collectionViewLayout = layout + collectionView.collectionViewLayout = layout } // Register CollectionView Nibs @@ -60,7 +60,7 @@ class ViewController: UIViewController, UICollectionViewDelegate, UICollectionVi // attach the UI nib file for the ImageUICollectionViewCell to the collectionview let viewNib = UINib(nibName: "ImageUICollectionViewCell", bundle: nil) - collectionView.registerNib(viewNib, forCellWithReuseIdentifier: "cell") + collectionView.register(viewNib, forCellWithReuseIdentifier: "cell") } @@ -69,16 +69,16 @@ class ViewController: UIViewController, UICollectionViewDelegate, UICollectionVi //MARK: - CollectionView Delegate Methods //** Number of Cells in the CollectionView */ - func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + 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 { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { // Create the cell and return the cell - let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageUICollectionViewCell + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ImageUICollectionViewCell // Add image to cell cell.image.image = model.images[indexPath.row] @@ -89,7 +89,7 @@ class ViewController: UIViewController, UICollectionViewDelegate, UICollectionVi //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 { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // create a cell size from the image size, and return the size let imageSize = model.images[indexPath.row].size diff --git a/Demo/Swift/CHTWaterfallSwiftDemoTests/CHTWaterfallSwiftDemoTests.swift b/Demo/Swift/CHTWaterfallSwiftDemoTests/CHTWaterfallSwiftDemoTests.swift deleted file mode 100644 index 98ecd36..0000000 --- a/Demo/Swift/CHTWaterfallSwiftDemoTests/CHTWaterfallSwiftDemoTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// 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/Demo/Swift/CHTWaterfallSwiftDemoTests/Info.plist b/Demo/Swift/CHTWaterfallSwiftDemoTests/Info.plist deleted file mode 100644 index ba72822..0000000 --- a/Demo/Swift/CHTWaterfallSwiftDemoTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..ce1f4a2 --- /dev/null +++ b/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version:5.0 +import PackageDescription + +let package = Package( + name: "CHTCollectionViewWaterfallLayout", + platforms: [ + .iOS(.v8), + .tvOS(.v9) + ], + products: [ + .library(name: "CHTCollectionViewWaterfallLayout", targets: ["CHTCollectionViewWaterfallLayout"]) + ], + targets: [ + .target( + name: "CHTCollectionViewWaterfallLayout", + path: "SwiftSources" + ) + ], + swiftLanguageVersions: [.v5] +) diff --git a/README.md b/README.md index 723a1bf..f258db8 100644 --- a/README.md +++ b/README.md @@ -22,23 +22,29 @@ Features * Support header and footer views. * Different column counts in different sections. -Prerequisite +Requirements ------------ -* ARC -* Xcode 4.4+, which supports literals syntax. -* iOS 6+, or -* iOS 4.x/5.x, with [PSTCollectionView]. +* iOS 8+ / tvOS 9+ +* Objective-C or Swift 4.2 How to install -------------- -* [CocoaPods] +* [CocoaPods] - Add `pod 'CHTCollectionViewWaterfallLayout'` to your Podfile. - If you prefer Swift, `pod 'CHTCollectionViewWaterfallLayout/Swift'` is ready for you. * [Carthage] - Add `github chiahsien/CHTCollectionViewWaterfallLayout` to your Cartfile. -* Manual +* [Swift Package Manager] + - Add it to the `dependencies` value of your `Package.swift`. + ``` + dependencies: [ + .package(url: "https://github.com/chiahsien/CHTCollectionViewWaterfallLayout.git", from: "0.9.8") + ] + ``` + +* Manual - Copy `CHTCollectionViewWaterfallLayout.h/m` or `CHTCollectionViewWaterfallLayout.swift` to your project. How to Use @@ -82,21 +88,21 @@ Who is using it --------------- Please let me know if your app is using this library. I'm glad to put your app on the list :-) -* [F3PiX](https://itunes.apple.com/us/app/samenwerken-f3pix/id897714553?mt=8) +* [F3PiX](https://itunes.apple.com/us/app/samenwerken-f3pix/id897714553?mt=8) 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 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) +* [Flickr](https://itunes.apple.com/us/app/id328407587) Access and organize your photos from anywhere. -* [Tumblr](https://www.tumblr.com/policy/en/ios-credits) +* [Tumblr](https://www.tumblr.com/policy/en/ios-credits) Post whatever you want to your Tumblr. Follow other people who are doing the same. You’ll probably never be bored again. -* [Funliday](https://itunes.apple.com/us/app/funlidays-lu-you-gui-hua/id905768387) +* [Funliday](https://itunes.apple.com/us/app/funlidays-lu-you-gui-hua/id905768387) The best trip planning app in the world! -* [Imgur](https://itunes.apple.com/us/app/imgur-funny-gifs-memes-images/id639881495?mt=8) +* [Imgur](https://itunes.apple.com/us/app/imgur-funny-gifs-memes-images/id639881495?mt=8) Funny GIFs, Memes, and Images! -* [DealPad](https://itunes.apple.com/us/app/dealpad-bargains-freebies/id949294107?mt=8) +* [DealPad](https://itunes.apple.com/us/app/dealpad-bargains-freebies/id949294107?mt=8) DealPad gives you access to the UK’s hottest Deals, Voucher Codes and Freebies in the palm of your hand. -* [Teespring Shopping](https://itunes.apple.com/app/apple-store/id1144693237?pt=117854047&ct=CHTCollectionViewWaterfallLayout%20README&mt=8) +* [Teespring Shopping](https://itunes.apple.com/app/apple-store/id1144693237?pt=117854047&ct=CHTCollectionViewWaterfallLayout%20README&mt=8) Browse and purchase shirts, mugs, totes and more! License @@ -113,3 +119,4 @@ Refer to the [Releases page](https://github.com/chiahsien/CHTCollectionViewWater [PSTCollectionView]: https://github.com/steipete/PSTCollectionView [CocoaPods]: http://cocoapods.org/ [Carthage]: https://github.com/Carthage/Carthage +[Swift Package Manager]: https://swift.org/package-manager/ diff --git a/SwiftSources/CHTCollectionViewWaterfallLayout.swift b/SwiftSources/CHTCollectionViewWaterfallLayout.swift new file mode 100755 index 0000000..635eee4 --- /dev/null +++ b/SwiftSources/CHTCollectionViewWaterfallLayout.swift @@ -0,0 +1,401 @@ +// +// CHTCollectionViewWaterfallLayout.swift +// PinterestSwift +// +// Created by Nicholas Tau on 6/30/14. +// Copyright (c) 2014 Nicholas Tau. All rights reserved. +// + +import UIKit + +private func < (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l < r + case (nil, _?): + return true + default: + return false + } +} + +private func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l > r + default: + return rhs < lhs + } +} + + +@objc public protocol CHTCollectionViewDelegateWaterfallLayout: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize + + @objc optional func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + heightForHeaderIn section: Int) -> CGFloat + + @objc optional func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + heightForFooterIn section: Int) -> CGFloat + + @objc optional func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + insetsFor section: Int) -> UIEdgeInsets + + @objc optional func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumInteritemSpacingFor section: Int) -> CGFloat + + @objc optional func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + columnCountFor section: Int) -> Int + + @available(*, unavailable, renamed: "collectionView(_:layout:sizeForItemAt:)") + @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize + + @available(*, unavailable, renamed: "collectionView(_:layout:heightForHeaderIn:)") + @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + heightForHeaderInSection section: Int) -> CGFloat + + @available(*, unavailable, renamed: "collectionView(_:layout:heightForFooterIn:)") + @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + heightForFooterInSection section: Int) -> CGFloat + + @available(*, unavailable, renamed: "collectionView(_:layout:insetsFor:)") + @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + insetForSectionAtIndex section: Int) -> UIEdgeInsets + + @available(*, unavailable, renamed: "collectionView(_:layout:minimumInteritemSpacingFor:)") + @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat + + @available(*, unavailable, renamed: "collectionView(_:layout:columnCountFor:)") + @objc optional func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + columnCountForSection section: Int) -> Int +} + +@available(*, unavailable, renamed: "CHTCollectionViewWaterfallLayout.ItemRenderDirection") +public enum CHTCollectionViewWaterfallLayoutItemRenderDirection { } + +public extension CHTCollectionViewWaterfallLayout.ItemRenderDirection { + @available(*, unavailable, renamed: "shortestFirst") + static let chtCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst = 0 + @available(*, unavailable, renamed: "leftToRight") + static let chtCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight = 1 + @available(*, unavailable, renamed: "rightToLeft") + static let chtCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft = 2 +} + +extension CHTCollectionViewWaterfallLayout { + public enum ItemRenderDirection: Int { + case shortestFirst + case leftToRight + case rightToLeft + } + + public enum SectionInsetReference { + case fromContentInset + case fromLayoutMargins + @available(iOS 11, *) + case fromSafeArea + } +} + +@available(*, unavailable, renamed: "UICollectionView.elementKindSectionHeader") +public let CHTCollectionElementKindSectionHeader = "CHTCollectionElementKindSectionHeader" +@available(*, unavailable, renamed: "UICollectionView.elementKindSectionFooter") +public let CHTCollectionElementKindSectionFooter = "CHTCollectionElementKindSectionFooter" +public class CHTCollectionViewWaterfallLayout: UICollectionViewLayout { + public var columnCount: Int = 2 { + didSet { + invalidateLayout() + } + } + + public var minimumColumnSpacing: CGFloat = 10 { + didSet { + invalidateLayout() + } + } + + public var minimumInteritemSpacing: CGFloat = 10 { + didSet { + invalidateLayout() + } + } + + public var headerHeight: CGFloat = 0 { + didSet { + invalidateLayout() + } + } + + public var footerHeight: CGFloat = 0 { + didSet { + invalidateLayout() + } + } + + public var sectionInset: UIEdgeInsets = .zero { + didSet { + invalidateLayout() + } + } + + public var itemRenderDirection: ItemRenderDirection = .shortestFirst { + didSet { + invalidateLayout() + } + } + + public var sectionInsetReference: SectionInsetReference = .fromContentInset { + didSet { + invalidateLayout() + } + } + + public var delegate: CHTCollectionViewDelegateWaterfallLayout? { + get { + return collectionView!.delegate as? CHTCollectionViewDelegateWaterfallLayout + } + } + + private var columnHeights: [[CGFloat]] = [] + private var sectionItemAttributes: [[UICollectionViewLayoutAttributes]] = [] + private var allItemAttributes: [UICollectionViewLayoutAttributes] = [] + private var headersAttributes: [Int: UICollectionViewLayoutAttributes] = [:] + private var footersAttributes: [Int: UICollectionViewLayoutAttributes] = [:] + private var unionRects: [CGRect] = [] + private let unionSize = 20 + + private func columnCount(forSection section: Int) -> Int { + return delegate?.collectionView?(collectionView!, layout: self, columnCountFor: section) ?? columnCount + } + + private var collectionViewContentWidth: CGFloat { + let insets: UIEdgeInsets + switch sectionInsetReference { + case .fromContentInset: + insets = collectionView!.contentInset + case .fromSafeArea: + if #available(iOS 11.0, *) { + insets = collectionView!.safeAreaInsets + } else { + insets = .zero + } + case .fromLayoutMargins: + insets = collectionView!.layoutMargins + } + return collectionView!.bounds.size.width - insets.left - insets.right + } + + private func collectionViewContentWidth(ofSection section: Int) -> CGFloat { + let insets = delegate?.collectionView?(collectionView!, layout: self, insetsFor: section) ?? sectionInset + return collectionViewContentWidth - insets.left - insets.right + } + + @available(*, unavailable, renamed: "itemWidth(inSection:)") + public func itemWidthInSectionAtIndex(_ section: Int) -> CGFloat { + return itemWidth(inSection: section) + } + + public func itemWidth(inSection section: Int) -> CGFloat { + let columnCount = self.columnCount(forSection: section) + let spaceColumCount = CGFloat(columnCount - 1) + let width = collectionViewContentWidth(ofSection: section) + return floor((width - (spaceColumCount * minimumColumnSpacing)) / CGFloat(columnCount)) + } + + override public func prepare() { + super.prepare() + + let numberOfSections = collectionView!.numberOfSections + if numberOfSections == 0 { + return + } + + headersAttributes = [:] + footersAttributes = [:] + unionRects = [] + allItemAttributes = [] + sectionItemAttributes = [] + columnHeights = (0 ..< numberOfSections).map { section in + let columnCount = self.columnCount(forSection: section) + let sectionColumnHeights = (0 ..< columnCount).map { CGFloat($0) } + return sectionColumnHeights + } + + var top: CGFloat = 0.0 + var attributes = UICollectionViewLayoutAttributes() + + for section in 0 ..< numberOfSections { + // MARK: 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset) + let minimumInteritemSpacing = delegate?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingFor: section) + ?? self.minimumInteritemSpacing + let sectionInsets = delegate?.collectionView?(collectionView!, layout: self, insetsFor: section) ?? self.sectionInset + let columnCount = columnHeights[section].count + let itemWidth = self.itemWidth(inSection: section) + + // MARK: 2. Section header + let heightHeader = delegate?.collectionView?(collectionView!, layout: self, heightForHeaderIn: section) + ?? self.headerHeight + if heightHeader > 0 { + attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: IndexPath(row: 0, section: section)) + attributes.frame = CGRect(x: 0, y: top, width: collectionView!.bounds.size.width, height: heightHeader) + headersAttributes[section] = attributes + allItemAttributes.append(attributes) + + top = attributes.frame.maxY + } + top += sectionInsets.top + columnHeights[section] = [CGFloat](repeating: top, count: columnCount) + + // MARK: 3. Section items + let itemCount = collectionView!.numberOfItems(inSection: section) + var itemAttributes: [UICollectionViewLayoutAttributes] = [] + + // Item will be put into shortest column. + for idx in 0 ..< itemCount { + let indexPath = IndexPath(item: idx, section: section) + + let columnIndex = nextColumnIndexForItem(idx, inSection: section) + let xOffset = sectionInsets.left + (itemWidth + minimumColumnSpacing) * CGFloat(columnIndex) + + let yOffset = columnHeights[section][columnIndex] + var itemHeight: CGFloat = 0.0 + if let itemSize = delegate?.collectionView(collectionView!, layout: self, sizeForItemAt: indexPath), + itemSize.height > 0 { + itemHeight = itemSize.height + if itemSize.width > 0 { + itemHeight = floor(itemHeight * itemWidth / itemSize.width) + } // else use default item width based on other parameters + } + + attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) + attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemWidth, height: itemHeight) + itemAttributes.append(attributes) + allItemAttributes.append(attributes) + columnHeights[section][columnIndex] = attributes.frame.maxY + minimumInteritemSpacing + } + sectionItemAttributes.append(itemAttributes) + + // MARK: 4. Section footer + let columnIndex = longestColumnIndex(inSection: section) + top = columnHeights[section][columnIndex] - minimumInteritemSpacing + sectionInsets.bottom + let footerHeight = delegate?.collectionView?(collectionView!, layout: self, heightForFooterIn: section) ?? self.footerHeight + + if footerHeight > 0 { + attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, with: IndexPath(item: 0, section: section)) + attributes.frame = CGRect(x: 0, y: top, width: collectionView!.bounds.size.width, height: footerHeight) + footersAttributes[section] = attributes + allItemAttributes.append(attributes) + top = attributes.frame.maxY + } + + columnHeights[section] = [CGFloat](repeating: top, count: columnCount) + } + + var idx = 0 + let itemCounts = allItemAttributes.count + while idx < itemCounts { + let rect1 = allItemAttributes[idx].frame + idx = min(idx + unionSize, itemCounts) - 1 + let rect2 = allItemAttributes[idx].frame + unionRects.append(rect1.union(rect2)) + idx += 1 + } + } + + override public var collectionViewContentSize: CGSize { + if collectionView!.numberOfSections == 0 { + return .zero + } + + var contentSize = collectionView!.bounds.size + contentSize.width = collectionViewContentWidth + + if let height = columnHeights.last?.first { + contentSize.height = height + return contentSize + } + return .zero + } + + override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + if indexPath.section >= sectionItemAttributes.count { + return nil + } + let list = sectionItemAttributes[indexPath.section] + if indexPath.item >= list.count { + return nil + } + return list[indexPath.item] + } + + override public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes { + var attribute: UICollectionViewLayoutAttributes? + if elementKind == UICollectionView.elementKindSectionHeader { + attribute = headersAttributes[indexPath.section] + } else if elementKind == UICollectionView.elementKindSectionFooter { + attribute = footersAttributes[indexPath.section] + } + return attribute ?? UICollectionViewLayoutAttributes() + } + + override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + var begin = 0, end = unionRects.count + + if let i = unionRects.firstIndex(where: { rect.intersects($0) }) { + begin = i * unionSize + } + if let i = unionRects.lastIndex(where: { rect.intersects($0) }) { + end = min((i + 1) * unionSize, allItemAttributes.count) + } + return allItemAttributes[begin.. Bool { + return newBounds.width != collectionView?.bounds.width + } + + /// Find the shortest column. + /// + /// - Returns: index for the shortest column + private func shortestColumnIndex(inSection section: Int) -> Int { + return columnHeights[section].enumerated() + .min(by: { $0.element < $1.element })? + .offset ?? 0 + } + + /// Find the longest column. + /// + /// - Returns: index for the longest column + private func longestColumnIndex(inSection section: Int) -> Int { + return columnHeights[section].enumerated() + .max(by: { $0.element < $1.element })? + .offset ?? 0 + } + + /// Find the index for the next column. + /// + /// - Returns: index for the next column + private func nextColumnIndexForItem(_ item: Int, inSection section: Int) -> Int { + var index = 0 + let columnCount = self.columnCount(forSection: section) + switch itemRenderDirection { + case .shortestFirst : + index = shortestColumnIndex(inSection: section) + case .leftToRight : + index = item % columnCount + case .rightToLeft: + index = (columnCount - 1) - (item % columnCount) + } + return index + } +}