├── Class └── ETCollectionViewWaterFallLayout.swift ├── ETCollectionViewWaterFallLayout.podspec ├── ETCollectionViewWaterFallLayout.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── volley.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── volley.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── ETCollectionViewWaterFallLayout ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── LICENSE ├── README.md └── demo.gif /Class/ETCollectionViewWaterFallLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ETCollectionViewWaterFallLayout.swift 3 | // ETCollectionViewWaterFallLayout 4 | // 5 | // Created by Volley on 2017/4/20. 6 | // Copyright © 2017年 Elegant Team. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum ETCollectionViewWaterfallLayoutItemRenderDirection { 12 | case shortestFirst 13 | case leftToRight 14 | case rightToLeft 15 | } 16 | 17 | @objc protocol ETCollectionViewDelegateWaterfallLayout: class, UICollectionViewDelegate { 18 | 19 | @objc func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 20 | 21 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, columnCountFor section: Int) -> Int 22 | 23 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, heightForHeaderIn section: Int) -> CGFloat 24 | 25 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, heightForFooterIn section: Int) -> CGFloat 26 | 27 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, insetForSectionAt index: Int) -> UIEdgeInsets 28 | 29 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, insetForHeaderIn section: Int) -> UIEdgeInsets 30 | 31 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, insetForFooterIn section: Int) -> UIEdgeInsets 32 | 33 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt index: Int) -> CGFloat 34 | 35 | @objc optional func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, minimumColumnSpacingForSectionAt index: Int) -> CGFloat 36 | } 37 | 38 | class ETCollectionViewWaterfallLayout: UICollectionViewLayout { 39 | 40 | open var columnCount: Int = 2 { 41 | didSet { 42 | if columnCount != oldValue { 43 | self.invalidateLayout() 44 | } 45 | } 46 | } 47 | 48 | open var minimumColumnSpacing: CGFloat = 10.0 { 49 | didSet { 50 | if minimumColumnSpacing != oldValue { 51 | self.invalidateLayout() 52 | } 53 | } 54 | } 55 | 56 | open var minimumInteritemSpacing: CGFloat = 10.0 { 57 | didSet { 58 | if minimumInteritemSpacing != oldValue { 59 | self.invalidateLayout() 60 | } 61 | } 62 | } 63 | 64 | open var headerHeight: CGFloat = 0.0 { 65 | didSet { 66 | if headerHeight != oldValue { 67 | self.invalidateLayout() 68 | } 69 | } 70 | } 71 | 72 | open var footerHeight: CGFloat = 0.0 { 73 | didSet { 74 | if footerHeight != oldValue { 75 | self.invalidateLayout() 76 | } 77 | } 78 | } 79 | 80 | open var headerInset: UIEdgeInsets = UIEdgeInsets.zero { 81 | didSet { 82 | if headerInset != oldValue { 83 | self.invalidateLayout() 84 | } 85 | } 86 | } 87 | 88 | open var footerInset: UIEdgeInsets = UIEdgeInsets.zero { 89 | didSet { 90 | if footerInset != oldValue { 91 | self.invalidateLayout() 92 | } 93 | } 94 | } 95 | 96 | open var sectionInset: UIEdgeInsets = UIEdgeInsets.zero { 97 | didSet { 98 | if sectionInset != oldValue { 99 | self.invalidateLayout() 100 | } 101 | } 102 | } 103 | 104 | open var itemRenderDirection: ETCollectionViewWaterfallLayoutItemRenderDirection = .shortestFirst { 105 | didSet { 106 | if itemRenderDirection != oldValue { 107 | self.invalidateLayout() 108 | } 109 | } 110 | } 111 | 112 | open var minimumContentHeight: CGFloat = 0.0 113 | 114 | fileprivate weak var delegate: ETCollectionViewDelegateWaterfallLayout! { 115 | 116 | return (self.collectionView?.delegate as! ETCollectionViewDelegateWaterfallLayout) 117 | } 118 | 119 | fileprivate var columnHeights: [[CGFloat]] = [] 120 | 121 | fileprivate var sectionItemAttributes: [[UICollectionViewLayoutAttributes]] = [] 122 | 123 | fileprivate var allItemAttributes: [UICollectionViewLayoutAttributes] = [] 124 | 125 | fileprivate var headersAttributes: [Int: UICollectionViewLayoutAttributes] = [:] 126 | 127 | fileprivate var footersAttributes: [Int: UICollectionViewLayoutAttributes] = [:] 128 | 129 | fileprivate var unionRects: [CGRect] = [] 130 | 131 | fileprivate let unionSize = 20 132 | 133 | 134 | // MARK: - function 135 | fileprivate func columnCount(forSection section: Int) -> Int { 136 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:columnCountFor:))) { 137 | return delegate.collectionView!(self.collectionView!, layout: self, columnCountFor: section) 138 | } 139 | 140 | return columnCount 141 | } 142 | 143 | fileprivate func evaluatedSectionInsetForSection(at index: Int) -> UIEdgeInsets { 144 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:insetForSectionAt:))) { 145 | return delegate.collectionView!(self.collectionView!, layout: self, insetForSectionAt: index) 146 | } 147 | 148 | return sectionInset 149 | } 150 | 151 | fileprivate func evaluatedMinimumColumnSpacing(at index: Int) -> CGFloat { 152 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:minimumColumnSpacingForSectionAt:))) { 153 | return delegate.collectionView!(self.collectionView!, layout: self, minimumColumnSpacingForSectionAt: index) 154 | } 155 | 156 | return minimumColumnSpacing 157 | } 158 | 159 | fileprivate func evaluatedMinimumInteritemSpaing(at index: Int) -> CGFloat { 160 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:minimumInteritemSpacingForSectionAt:))) { 161 | return delegate.collectionView!(self.collectionView!, layout: self, minimumInteritemSpacingForSectionAt: index) 162 | } 163 | 164 | return minimumInteritemSpacing 165 | } 166 | 167 | open func itemWidthInSection(at index: Int) -> CGFloat { 168 | let sectionInset = evaluatedSectionInsetForSection(at: index) 169 | 170 | let width = (self.collectionView?.bounds.size.width)! - sectionInset.left - sectionInset.right 171 | let columnCount = CGFloat(self.columnCount(forSection: index)) 172 | let columnSpacing = evaluatedMinimumColumnSpacing(at: index) 173 | 174 | return (width - (columnCount - 1) * columnSpacing) / columnCount 175 | } 176 | 177 | // MARK: - methods to override 178 | override func prepare() { 179 | super.prepare() 180 | 181 | headersAttributes.removeAll() 182 | footersAttributes.removeAll() 183 | unionRects.removeAll() 184 | columnHeights.removeAll() 185 | allItemAttributes.removeAll() 186 | sectionItemAttributes.removeAll() 187 | 188 | guard self.collectionView?.numberOfSections != 0 else { 189 | return 190 | } 191 | 192 | assert(delegate!.conforms(to: ETCollectionViewDelegateWaterfallLayout.self), "UICollectionView's delegate should conform to ETCollectionViewDelegateWaterfallLayout protocol") 193 | assert(columnCount > 0, "WaterfallLayout's columnCount should be greater than 0") 194 | 195 | let numberOfsections = (self.collectionView?.numberOfSections)! 196 | 197 | // Initialize variables 198 | for index in 0 ..< numberOfsections{ 199 | let columnCount = self.columnCount(forSection: index) 200 | let sectionColumnHeights = Array(repeatElement(CGFloat(0), count: columnCount)) 201 | self.columnHeights.append(sectionColumnHeights) 202 | } 203 | 204 | // Create attributes 205 | var top: CGFloat = 0 206 | 207 | for section in 0 ..< numberOfsections { 208 | 209 | /* 210 | * 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset) 211 | */ 212 | let interitemSpacing = evaluatedMinimumInteritemSpaing(at: section) 213 | let columnSpacing = evaluatedMinimumColumnSpacing(at: section) 214 | let sectionInset = evaluatedSectionInsetForSection(at: section) 215 | 216 | let width = (self.collectionView?.bounds.size.width)! - sectionInset.left - sectionInset.right 217 | let columnCount = self.columnCount(forSection: section) 218 | let itemWidth = (width - (CGFloat(columnCount - 1)) * columnSpacing) / CGFloat(columnCount) 219 | 220 | /* 221 | * 2. Section header 222 | */ 223 | var headerHeight = self.headerHeight 224 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:heightForHeaderIn:))) { 225 | headerHeight = delegate.collectionView!(self.collectionView!, layout: self, heightForHeaderIn: section) 226 | } 227 | 228 | var headerInset = self.headerInset 229 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:insetForHeaderIn:))) { 230 | headerInset = delegate.collectionView!(self.collectionView!, layout: self, insetForHeaderIn: section) 231 | } 232 | 233 | top += headerInset.top 234 | 235 | if headerHeight > 0 { 236 | let attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: IndexPath(item: 0, section: section)) 237 | attributes.frame = CGRect(x: headerInset.left, 238 | y: top, 239 | width: (self.collectionView?.bounds.size.width)!, 240 | height: headerHeight) 241 | self.headersAttributes[section] = attributes 242 | self.allItemAttributes.append(attributes) 243 | 244 | top = attributes.frame.maxY + headerInset.bottom 245 | } 246 | 247 | top += sectionInset.top 248 | for idx in 0 ..< columnCount { 249 | self.columnHeights[section][idx] = top 250 | } 251 | 252 | /* 253 | * 3. Section items 254 | */ 255 | let itemCount = (self.collectionView?.numberOfItems(inSection: section))! 256 | var itemAttributes: [UICollectionViewLayoutAttributes] = [] 257 | 258 | for idx in 0 ..< itemCount { 259 | 260 | let indexPath = IndexPath(item: idx, section: section) 261 | let columnIndex = nextColumnIndex(forItem: idx, section: section) 262 | let xOffset = sectionInset.left + (itemWidth + columnSpacing) * CGFloat(columnIndex) 263 | let yOffset = self.columnHeights[section][columnIndex] 264 | let itemSize = delegate.collectionView(self.collectionView!, layout: self, sizeForItemAt: indexPath) 265 | 266 | var itemHeight: CGFloat = 0 267 | if itemSize.width > 0 { 268 | itemHeight = itemSize.height * itemWidth / itemSize.width 269 | } 270 | 271 | let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) 272 | attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemWidth, height: itemHeight) 273 | itemAttributes.append(attributes) 274 | self.allItemAttributes.append(attributes) 275 | self.columnHeights[section][columnIndex] = attributes.frame.maxY + interitemSpacing 276 | } 277 | 278 | self.sectionItemAttributes.append(itemAttributes) 279 | 280 | /* 281 | * 4. Section footer 282 | */ 283 | let columnIndex = longestColumnIndexIn(section: section) 284 | top = self.columnHeights[section][columnIndex] - interitemSpacing + sectionInset.bottom 285 | 286 | var footerHeight = self.footerHeight 287 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:heightForFooterIn:))) { 288 | footerHeight = delegate.collectionView!(self.collectionView!, layout: self, heightForFooterIn: section) 289 | } 290 | 291 | var footerInset = self.footerInset 292 | if delegate.responds(to: #selector(ETCollectionViewDelegateWaterfallLayout.collectionView(_:layout:insetForFooterIn:))) { 293 | footerInset = delegate.collectionView!(self.collectionView!, layout: self, insetForFooterIn: section) 294 | } 295 | 296 | top += footerInset.top 297 | 298 | if footerHeight > 0 { 299 | let attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, with: IndexPath(item: 0, section: section)) 300 | attributes.frame = CGRect(x: footerInset.left, 301 | y: top, 302 | width: (self.collectionView?.bounds.size.width)! - (footerInset.left + footerInset.right), 303 | height: footerHeight) 304 | self.footersAttributes[section] = attributes 305 | self.allItemAttributes.append(attributes) 306 | 307 | top = attributes.frame.maxY + footerInset.bottom 308 | } 309 | 310 | for idx in 0 ..< columnCount { 311 | self.columnHeights[section][idx] = top 312 | } 313 | } 314 | 315 | // Build union rects 316 | var idx = 0 317 | let itemCounts = self.allItemAttributes.count 318 | while idx < itemCounts { 319 | var unionRect = self.allItemAttributes[idx].frame 320 | let rectEndIndex = min(idx + unionSize, itemCounts) 321 | 322 | for i in idx+1 ..< rectEndIndex { 323 | unionRect = unionRect.union(self.allItemAttributes[i].frame) 324 | } 325 | 326 | idx = rectEndIndex 327 | 328 | self.unionRects.append(unionRect) 329 | } 330 | } 331 | 332 | override var collectionViewContentSize: CGSize { 333 | 334 | let numberOfSections = (self.collectionView?.numberOfSections)! 335 | if numberOfSections == 0 { 336 | return CGSize.zero 337 | } 338 | 339 | var contentSize = self.collectionView?.bounds.size 340 | contentSize?.height = (self.columnHeights.last?.first)! 341 | 342 | if (contentSize?.height)! < minimumContentHeight { 343 | contentSize?.height = self.minimumContentHeight 344 | } 345 | 346 | return contentSize! 347 | } 348 | 349 | override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 350 | guard indexPath.section < self.sectionItemAttributes.count 351 | && indexPath.item < self.sectionItemAttributes[indexPath.section].count else { 352 | return nil 353 | } 354 | 355 | return self.sectionItemAttributes[indexPath.section][indexPath.item] 356 | } 357 | 358 | override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 359 | 360 | if elementKind == UICollectionView.elementKindSectionHeader { 361 | return self.headersAttributes[indexPath.section] 362 | } 363 | 364 | if elementKind == UICollectionView.elementKindSectionFooter { 365 | return self.footersAttributes[indexPath.section] 366 | } 367 | 368 | return nil 369 | } 370 | 371 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 372 | 373 | var begin = 0, end = 0 374 | 375 | var attrs: [UICollectionViewLayoutAttributes] = [] 376 | 377 | for i in 0 ..< self.unionRects.count { 378 | if rect.intersects(self.unionRects[i]) { 379 | begin = i * unionSize 380 | break 381 | } 382 | } 383 | 384 | var idx = self.unionRects.count - 1 385 | while idx >= 0 { 386 | if rect.intersects(self.unionRects[idx]) { 387 | end = min((idx+1) * unionSize, self.allItemAttributes.count) 388 | break 389 | } 390 | idx -= 1 391 | } 392 | 393 | for i in begin ..< end { 394 | let attr = self.allItemAttributes[i] 395 | if rect.intersects(attr.frame) { 396 | attrs.append(attr) 397 | } 398 | } 399 | return attrs 400 | } 401 | 402 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 403 | let oldBounds = (self.collectionView?.bounds)! 404 | if newBounds.width != oldBounds.width { 405 | return true 406 | } 407 | 408 | return false 409 | } 410 | 411 | // MARK: - Find the shortest column 412 | fileprivate func shortestColumnIndexIn(section: Int) -> Int { 413 | 414 | var index = 0 415 | var shortestHeight = CGFloat(Float.greatestFiniteMagnitude) 416 | 417 | for (idx, height) in self.columnHeights[section].enumerated() { 418 | if height < shortestHeight { 419 | shortestHeight = height 420 | index = idx 421 | } 422 | } 423 | 424 | return index 425 | } 426 | 427 | /** 428 | * Find the longest column. 429 | * 430 | * @return index for the longest column 431 | */ 432 | fileprivate func longestColumnIndexIn(section: Int) -> Int { 433 | 434 | var index = 0 435 | var longestHeight: CGFloat = 0 436 | 437 | for (idx, height) in self.columnHeights[section].enumerated() { 438 | if height > longestHeight { 439 | longestHeight = height 440 | index = idx 441 | } 442 | } 443 | 444 | return index 445 | } 446 | 447 | /** 448 | * Find the index for the next column. 449 | * 450 | * @return index for the next column 451 | */ 452 | fileprivate func nextColumnIndex(forItem item: Int, section: Int) -> Int { 453 | 454 | var index = 0 455 | let columnCount = self.columnCount(forSection: section) 456 | 457 | switch itemRenderDirection { 458 | case .shortestFirst: 459 | index = shortestColumnIndexIn(section: section) 460 | 461 | case .leftToRight: 462 | index = item % columnCount 463 | 464 | case .rightToLeft: 465 | index = (columnCount - 1) - (item % columnCount) 466 | } 467 | 468 | return index 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ETCollectionViewWaterFallLayout" 3 | s.version = "0.0.2" 4 | s.summary = "A waterFallLayout for UICollectionView" 5 | s.homepage = "https://github.com/ElegantTeam/ETCollectionViewWaterFallLayout" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "VolleyZ" => "552408690@qq.com" } 8 | s.source = { :git => "https://github.com/ElegantTeam/ETCollectionViewWaterFallLayout.git", :tag => "0.0.2"} 9 | s.source_files = "Class", "Class/*.{h,m}" 10 | s.platform = :ios, '9.0' 11 | s.swift_version = "5" 12 | 13 | end 14 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0DA3C92525E51772000A3EE0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA3C92425E51772000A3EE0 /* AppDelegate.swift */; }; 11 | 0DA3C92925E51772000A3EE0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA3C92825E51772000A3EE0 /* ViewController.swift */; }; 12 | 0DA3C92C25E51772000A3EE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0DA3C92A25E51772000A3EE0 /* Main.storyboard */; }; 13 | 0DA3C92E25E51773000A3EE0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DA3C92D25E51773000A3EE0 /* Assets.xcassets */; }; 14 | 0DA3C93125E51773000A3EE0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0DA3C92F25E51773000A3EE0 /* LaunchScreen.storyboard */; }; 15 | 0DA3C93E25E5180B000A3EE0 /* ETCollectionViewWaterFallLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA3C93D25E5180B000A3EE0 /* ETCollectionViewWaterFallLayout.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 0DA3C92125E51772000A3EE0 /* ETCollectionViewWaterFallLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ETCollectionViewWaterFallLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 0DA3C92425E51772000A3EE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 0DA3C92825E51772000A3EE0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 0DA3C92B25E51772000A3EE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 0DA3C92D25E51773000A3EE0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 0DA3C93025E51773000A3EE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 0DA3C93225E51773000A3EE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 0DA3C93D25E5180B000A3EE0 /* ETCollectionViewWaterFallLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ETCollectionViewWaterFallLayout.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 0DA3C91E25E51772000A3EE0 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 0DA3C91825E51771000A3EE0 = { 41 | isa = PBXGroup; 42 | children = ( 43 | 0DA3C93C25E5180B000A3EE0 /* Class */, 44 | 0DA3C92325E51772000A3EE0 /* ETCollectionViewWaterFallLayout */, 45 | 0DA3C92225E51772000A3EE0 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 0DA3C92225E51772000A3EE0 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 0DA3C92125E51772000A3EE0 /* ETCollectionViewWaterFallLayout.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 0DA3C92325E51772000A3EE0 /* ETCollectionViewWaterFallLayout */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 0DA3C92425E51772000A3EE0 /* AppDelegate.swift */, 61 | 0DA3C92825E51772000A3EE0 /* ViewController.swift */, 62 | 0DA3C92A25E51772000A3EE0 /* Main.storyboard */, 63 | 0DA3C92D25E51773000A3EE0 /* Assets.xcassets */, 64 | 0DA3C92F25E51773000A3EE0 /* LaunchScreen.storyboard */, 65 | 0DA3C93225E51773000A3EE0 /* Info.plist */, 66 | ); 67 | path = ETCollectionViewWaterFallLayout; 68 | sourceTree = ""; 69 | }; 70 | 0DA3C93C25E5180B000A3EE0 /* Class */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 0DA3C93D25E5180B000A3EE0 /* ETCollectionViewWaterFallLayout.swift */, 74 | ); 75 | path = Class; 76 | sourceTree = ""; 77 | }; 78 | /* End PBXGroup section */ 79 | 80 | /* Begin PBXNativeTarget section */ 81 | 0DA3C92025E51772000A3EE0 /* ETCollectionViewWaterFallLayout */ = { 82 | isa = PBXNativeTarget; 83 | buildConfigurationList = 0DA3C93525E51773000A3EE0 /* Build configuration list for PBXNativeTarget "ETCollectionViewWaterFallLayout" */; 84 | buildPhases = ( 85 | 0DA3C91D25E51772000A3EE0 /* Sources */, 86 | 0DA3C91E25E51772000A3EE0 /* Frameworks */, 87 | 0DA3C91F25E51772000A3EE0 /* Resources */, 88 | ); 89 | buildRules = ( 90 | ); 91 | dependencies = ( 92 | ); 93 | name = ETCollectionViewWaterFallLayout; 94 | productName = ETCollectionViewWaterFallLayout; 95 | productReference = 0DA3C92125E51772000A3EE0 /* ETCollectionViewWaterFallLayout.app */; 96 | productType = "com.apple.product-type.application"; 97 | }; 98 | /* End PBXNativeTarget section */ 99 | 100 | /* Begin PBXProject section */ 101 | 0DA3C91925E51771000A3EE0 /* Project object */ = { 102 | isa = PBXProject; 103 | attributes = { 104 | LastSwiftUpdateCheck = 1210; 105 | LastUpgradeCheck = 1210; 106 | TargetAttributes = { 107 | 0DA3C92025E51772000A3EE0 = { 108 | CreatedOnToolsVersion = 12.1; 109 | }; 110 | }; 111 | }; 112 | buildConfigurationList = 0DA3C91C25E51771000A3EE0 /* Build configuration list for PBXProject "ETCollectionViewWaterFallLayout" */; 113 | compatibilityVersion = "Xcode 9.3"; 114 | developmentRegion = en; 115 | hasScannedForEncodings = 0; 116 | knownRegions = ( 117 | en, 118 | Base, 119 | ); 120 | mainGroup = 0DA3C91825E51771000A3EE0; 121 | productRefGroup = 0DA3C92225E51772000A3EE0 /* Products */; 122 | projectDirPath = ""; 123 | projectRoot = ""; 124 | targets = ( 125 | 0DA3C92025E51772000A3EE0 /* ETCollectionViewWaterFallLayout */, 126 | ); 127 | }; 128 | /* End PBXProject section */ 129 | 130 | /* Begin PBXResourcesBuildPhase section */ 131 | 0DA3C91F25E51772000A3EE0 /* Resources */ = { 132 | isa = PBXResourcesBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 0DA3C93125E51773000A3EE0 /* LaunchScreen.storyboard in Resources */, 136 | 0DA3C92E25E51773000A3EE0 /* Assets.xcassets in Resources */, 137 | 0DA3C92C25E51772000A3EE0 /* Main.storyboard in Resources */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXResourcesBuildPhase section */ 142 | 143 | /* Begin PBXSourcesBuildPhase section */ 144 | 0DA3C91D25E51772000A3EE0 /* Sources */ = { 145 | isa = PBXSourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 0DA3C92925E51772000A3EE0 /* ViewController.swift in Sources */, 149 | 0DA3C93E25E5180B000A3EE0 /* ETCollectionViewWaterFallLayout.swift in Sources */, 150 | 0DA3C92525E51772000A3EE0 /* AppDelegate.swift in Sources */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXSourcesBuildPhase section */ 155 | 156 | /* Begin PBXVariantGroup section */ 157 | 0DA3C92A25E51772000A3EE0 /* Main.storyboard */ = { 158 | isa = PBXVariantGroup; 159 | children = ( 160 | 0DA3C92B25E51772000A3EE0 /* Base */, 161 | ); 162 | name = Main.storyboard; 163 | sourceTree = ""; 164 | }; 165 | 0DA3C92F25E51773000A3EE0 /* LaunchScreen.storyboard */ = { 166 | isa = PBXVariantGroup; 167 | children = ( 168 | 0DA3C93025E51773000A3EE0 /* Base */, 169 | ); 170 | name = LaunchScreen.storyboard; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXVariantGroup section */ 174 | 175 | /* Begin XCBuildConfiguration section */ 176 | 0DA3C93325E51773000A3EE0 /* Debug */ = { 177 | isa = XCBuildConfiguration; 178 | buildSettings = { 179 | ALWAYS_SEARCH_USER_PATHS = NO; 180 | CLANG_ANALYZER_NONNULL = YES; 181 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 182 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 183 | CLANG_CXX_LIBRARY = "libc++"; 184 | CLANG_ENABLE_MODULES = YES; 185 | CLANG_ENABLE_OBJC_ARC = YES; 186 | CLANG_ENABLE_OBJC_WEAK = YES; 187 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 188 | CLANG_WARN_BOOL_CONVERSION = YES; 189 | CLANG_WARN_COMMA = YES; 190 | CLANG_WARN_CONSTANT_CONVERSION = YES; 191 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 192 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 193 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 194 | CLANG_WARN_EMPTY_BODY = YES; 195 | CLANG_WARN_ENUM_CONVERSION = YES; 196 | CLANG_WARN_INFINITE_RECURSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 199 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 200 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 201 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 202 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 203 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 204 | CLANG_WARN_STRICT_PROTOTYPES = YES; 205 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 206 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 207 | CLANG_WARN_UNREACHABLE_CODE = YES; 208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 209 | COPY_PHASE_STRIP = NO; 210 | DEBUG_INFORMATION_FORMAT = dwarf; 211 | ENABLE_STRICT_OBJC_MSGSEND = YES; 212 | ENABLE_TESTABILITY = YES; 213 | GCC_C_LANGUAGE_STANDARD = gnu11; 214 | GCC_DYNAMIC_NO_PIC = NO; 215 | GCC_NO_COMMON_BLOCKS = YES; 216 | GCC_OPTIMIZATION_LEVEL = 0; 217 | GCC_PREPROCESSOR_DEFINITIONS = ( 218 | "DEBUG=1", 219 | "$(inherited)", 220 | ); 221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 223 | GCC_WARN_UNDECLARED_SELECTOR = YES; 224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 225 | GCC_WARN_UNUSED_FUNCTION = YES; 226 | GCC_WARN_UNUSED_VARIABLE = YES; 227 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 228 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 229 | MTL_FAST_MATH = YES; 230 | ONLY_ACTIVE_ARCH = YES; 231 | SDKROOT = iphoneos; 232 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 233 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 234 | }; 235 | name = Debug; 236 | }; 237 | 0DA3C93425E51773000A3EE0 /* Release */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_ENABLE_OBJC_WEAK = YES; 248 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_COMMA = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INFINITE_RECURSION = YES; 258 | CLANG_WARN_INT_CONVERSION = YES; 259 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 261 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 265 | CLANG_WARN_STRICT_PROTOTYPES = YES; 266 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 267 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu11; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | MTL_FAST_MATH = YES; 285 | SDKROOT = iphoneos; 286 | SWIFT_COMPILATION_MODE = wholemodule; 287 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 288 | VALIDATE_PRODUCT = YES; 289 | }; 290 | name = Release; 291 | }; 292 | 0DA3C93625E51773000A3EE0 /* Debug */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 296 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 297 | CODE_SIGN_STYLE = Automatic; 298 | INFOPLIST_FILE = ETCollectionViewWaterFallLayout/Info.plist; 299 | LD_RUNPATH_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "@executable_path/Frameworks", 302 | ); 303 | PRODUCT_BUNDLE_IDENTIFIER = com.test.test.ETCollectionViewWaterFallLayout; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | SWIFT_VERSION = 5.0; 306 | TARGETED_DEVICE_FAMILY = 1; 307 | }; 308 | name = Debug; 309 | }; 310 | 0DA3C93725E51773000A3EE0 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 314 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 315 | CODE_SIGN_STYLE = Automatic; 316 | INFOPLIST_FILE = ETCollectionViewWaterFallLayout/Info.plist; 317 | LD_RUNPATH_SEARCH_PATHS = ( 318 | "$(inherited)", 319 | "@executable_path/Frameworks", 320 | ); 321 | PRODUCT_BUNDLE_IDENTIFIER = com.test.test.ETCollectionViewWaterFallLayout; 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | SWIFT_VERSION = 5.0; 324 | TARGETED_DEVICE_FAMILY = 1; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | 0DA3C91C25E51771000A3EE0 /* Build configuration list for PBXProject "ETCollectionViewWaterFallLayout" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 0DA3C93325E51773000A3EE0 /* Debug */, 335 | 0DA3C93425E51773000A3EE0 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | 0DA3C93525E51773000A3EE0 /* Build configuration list for PBXNativeTarget "ETCollectionViewWaterFallLayout" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 0DA3C93625E51773000A3EE0 /* Debug */, 344 | 0DA3C93725E51773000A3EE0 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = 0DA3C91925E51771000A3EE0 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout.xcodeproj/project.xcworkspace/xcuserdata/volley.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElegantTeam/ETCollectionViewWaterFallLayout/e203dc269b7487aaf2e4cdaa69f767fac860a528/ETCollectionViewWaterFallLayout.xcodeproj/project.xcworkspace/xcuserdata/volley.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout.xcodeproj/xcuserdata/volley.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ETCollectionViewWaterFallLayout.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ETCollectionViewWaterFallLayout 4 | // 5 | // Created by Volley on 2017/4/22. 6 | // Copyright © 2017年 Elegant Team. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ETCollectionViewWaterFallLayout/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ETCollectionViewWaterFallLayout 4 | // 5 | // Created by Volley on 2017/4/22. 6 | // Copyright © 2017年 Elegant Team. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct WaterFallData { 12 | var size: CGSize! 13 | var color: UIColor! 14 | } 15 | 16 | extension UIColor { 17 | class func randomColor() -> UIColor { 18 | let red = CGFloat(arc4random_uniform(255))/CGFloat(255.0) 19 | let green = CGFloat( arc4random_uniform(255))/CGFloat(255.0) 20 | let blue = CGFloat(arc4random_uniform(255))/CGFloat(255.0) 21 | 22 | let color = UIColor.init(red:red, green:green, blue:blue , alpha: 1) 23 | return color 24 | } 25 | } 26 | 27 | let screen_width = UIScreen.main.bounds.size.width 28 | 29 | class ViewController: UIViewController { 30 | 31 | @IBOutlet weak var collectionView: UICollectionView! 32 | 33 | 34 | lazy var waterFallData: [WaterFallData] = { 35 | var dataSource = [WaterFallData]() 36 | for _ in 0...100 { 37 | var data = WaterFallData() 38 | let random = CGFloat(arc4random_uniform((UInt32(100)))) 39 | data.size = CGSize(width: 100, height: random) 40 | data.color = UIColor.randomColor() 41 | dataSource.append(data) 42 | } 43 | return dataSource 44 | }() 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | 49 | let layout = ETCollectionViewWaterfallLayout() 50 | layout.minimumColumnSpacing = 15 51 | layout.minimumInteritemSpacing = 10 52 | layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) 53 | 54 | collectionView.collectionViewLayout = layout 55 | 56 | 57 | } 58 | 59 | override func didReceiveMemoryWarning() { 60 | super.didReceiveMemoryWarning() 61 | // Dispose of any resources that can be recreated. 62 | } 63 | 64 | 65 | } 66 | 67 | extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, ETCollectionViewDelegateWaterfallLayout { 68 | 69 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 70 | return waterFallData.count 71 | } 72 | 73 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 74 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 75 | cell.backgroundColor = waterFallData[indexPath.item].color 76 | return cell 77 | } 78 | 79 | func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 80 | return waterFallData[indexPath.item].size 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ETCollectionViewWaterFallLayout 2 | [![CocoaPods](https://img.shields.io/cocoapods/v/ETCollectionViewWaterFallLayout.svg)]() 3 | [![CocoaPods](https://img.shields.io/cocoapods/p/ETCollectionViewWaterFallLayout.svg)]() 4 | [![CocoaPods](https://img.shields.io/cocoapods/l/ETCollectionViewWaterFallLayout.svg)]() 5 | 6 | ETCollectionViewWaterFallLayout is the swift version of CHTCollectionViewWaterfallLayout! 7 | 8 | Screen Shots 9 | ----------- 10 | ![demo](https://github.com/ElegantTeam/ETCollectionViewWaterFallLayout/blob/master/demo.gif) 11 | 12 | 13 | Installation 14 | ============ 15 | 16 | The preferred way of installation is via [CocoaPods](http://cocoapods.org). Just add 17 | 18 | ```ruby 19 | pod 'ETCollectionViewWaterFallLayout' 20 | ``` 21 | 22 | and run `pod install`. It will install the most recent version of ETCollectionViewWaterFallLayout. 23 | 24 | Usage 25 | =============== 26 | Below are the public properties and their default values that you can change to customize the layout 27 | ``` swift 28 | let layout = ETCollectionViewWaterfallLayout() 29 | layout.minimumColumnSpacing = 10.0 30 | layout.minimumInteritemSpacing = 10.0 31 | layout.columnCount = 2 32 | layout.headerHeight = 0.0 33 | layout.footerHeight = 0.0 34 | layout.headerInset = .zero 35 | layout.footerInset = .zero 36 | layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) 37 | collectionView.collectionViewLayout = layout 38 | ``` 39 | 40 | #### Required Protocol 41 | Your collection view's delegate must conforms to `ETCollectionViewDelegateWaterfallLayout` protocol and implement the required method, all you need to do is return the original size of the item: 42 | 43 | ``` swift 44 | func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 45 | ``` 46 | 47 | ## Support 48 | 49 | - iOS 9.0+ 50 | - Xcode 8.0+ 51 | - Swift 5.0 52 | 53 | ## Objective-C Version 54 | * chiahsien: [CHTCollectionViewWaterfallLayout](https://github.com/chiahsien/CHTCollectionViewWaterfallLayout) 55 | 56 | ---------- 57 | LICENSE 58 | -------------------- 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 65 | SOFTWARE. 66 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElegantTeam/ETCollectionViewWaterFallLayout/e203dc269b7487aaf2e4cdaa69f767fac860a528/demo.gif --------------------------------------------------------------------------------