├── .gitignore ├── .travis.yml ├── BSGridCollectionViewLayout.podspec ├── BSGridCollectionViewLayout.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── BSGridCollectionViewLayout.xcscheme │ └── Tests.xcscheme ├── Default-568h@2x.png ├── Example ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── InfoExample.plist └── ViewController.swift ├── LICENSE ├── README.md ├── Sources ├── BSGridCollectionViewLayout.h ├── GridCollectionViewLayout.swift └── InfoSource.plist └── Tests ├── FirstIndexTests.swift ├── FirstRowTests.swift ├── LastIndexTests.swift ├── LastRowTests.swift └── Tests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | # Pods/ 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | #cache: cocoapods 8 | podfile: Example/Podfile 9 | before_install: 10 | - gem install cocoapods # Since Travis is not always on latest version 11 | #- pod install --project-directory=Example 12 | install: 13 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 14 | script: 15 | - set -o pipefail && xcodebuild test -workspace Example/BSGridCollectionViewLayout.xcworkspace -scheme BSGridCollectionViewLayout-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c 16 | - pod lib lint --quick 17 | -------------------------------------------------------------------------------- /BSGridCollectionViewLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "BSGridCollectionViewLayout" 3 | s.version = "1.2.5" 4 | s.summary = "Simple grid collection view layout." 5 | s.description = <<-DESC 6 | A simple grid collection view layout. Displays multiple sections as one continuous grid without any section breaks. 7 | DESC 8 | 9 | s.homepage = "https://github.com/mikaoj/BSGridCollectionViewLayout" 10 | s.license = 'MIT' 11 | s.author = { "Joakim Gyllstrom" => "joakim@backslashed.se" } 12 | s.swift_version = "5.0" 13 | s.source = { :git => "https://github.com/mikaoj/BSGridCollectionViewLayout.git", :tag => s.version.to_s } 14 | s.platform = :ios, '8.0' 15 | s.requires_arc = true 16 | 17 | s.source_files = 'Sources/**/*.{swift,h}' 18 | end 19 | -------------------------------------------------------------------------------- /BSGridCollectionViewLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 552D02E021D4E9E10017BF81 /* BSGridCollectionViewLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 552D02DE21D4E9E10017BF81 /* BSGridCollectionViewLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 558BE65621D4EB4E00F06E8B /* GridCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE65521D4EB4E00F06E8B /* GridCollectionViewLayout.swift */; }; 12 | 558BE65E21D4EBB800F06E8B /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE65D21D4EBB800F06E8B /* Tests.swift */; }; 13 | 558BE66021D4EBB800F06E8B /* BSGridCollectionViewLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 552D02DB21D4E9E10017BF81 /* BSGridCollectionViewLayout.framework */; }; 14 | 558BE66A21D4EBED00F06E8B /* LastIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE66621D4EBED00F06E8B /* LastIndexTests.swift */; }; 15 | 558BE66B21D4EBED00F06E8B /* LastRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE66721D4EBED00F06E8B /* LastRowTests.swift */; }; 16 | 558BE66C21D4EBED00F06E8B /* FirstRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE66821D4EBED00F06E8B /* FirstRowTests.swift */; }; 17 | 558BE66D21D4EBED00F06E8B /* FirstIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE66921D4EBED00F06E8B /* FirstIndexTests.swift */; }; 18 | 558BE67521D4EC3B00F06E8B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE67421D4EC3B00F06E8B /* AppDelegate.swift */; }; 19 | 558BE67721D4EC3B00F06E8B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558BE67621D4EC3B00F06E8B /* ViewController.swift */; }; 20 | 558BE67A21D4EC3B00F06E8B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 558BE67821D4EC3B00F06E8B /* Main.storyboard */; }; 21 | 558BE67C21D4EC3B00F06E8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 558BE67B21D4EC3B00F06E8B /* Assets.xcassets */; }; 22 | 5595158A21D4ED8A007E31CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5595158921D4ED8A007E31CE /* LaunchScreen.storyboard */; }; 23 | 55CB6AF621D4ECD40014DB72 /* BSGridCollectionViewLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 552D02DB21D4E9E10017BF81 /* BSGridCollectionViewLayout.framework */; }; 24 | 55D03E652253CDFD00567C18 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 55D03E642253CDFD00567C18 /* Default-568h@2x.png */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 558BE66121D4EBB800F06E8B /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 552D02D221D4E9E10017BF81 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 552D02DA21D4E9E10017BF81; 33 | remoteInfo = BSGridCollectionViewLayout; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 552D02DB21D4E9E10017BF81 /* BSGridCollectionViewLayout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BSGridCollectionViewLayout.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 552D02DE21D4E9E10017BF81 /* BSGridCollectionViewLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGridCollectionViewLayout.h; sourceTree = ""; }; 40 | 552D02DF21D4E9E10017BF81 /* InfoSource.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InfoSource.plist; sourceTree = ""; }; 41 | 558BE65521D4EB4E00F06E8B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewLayout.swift; sourceTree = ""; }; 42 | 558BE65B21D4EBB800F06E8B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 558BE65D21D4EBB800F06E8B /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 44 | 558BE66621D4EBED00F06E8B /* LastIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LastIndexTests.swift; sourceTree = ""; }; 45 | 558BE66721D4EBED00F06E8B /* LastRowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LastRowTests.swift; sourceTree = ""; }; 46 | 558BE66821D4EBED00F06E8B /* FirstRowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstRowTests.swift; sourceTree = ""; }; 47 | 558BE66921D4EBED00F06E8B /* FirstIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstIndexTests.swift; sourceTree = ""; }; 48 | 558BE67221D4EC3B00F06E8B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 558BE67421D4EC3B00F06E8B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 558BE67621D4EC3B00F06E8B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 51 | 558BE67921D4EC3B00F06E8B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 52 | 558BE67B21D4EC3B00F06E8B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | 558BE68021D4EC3B00F06E8B /* InfoExample.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InfoExample.plist; sourceTree = ""; }; 54 | 5595158921D4ED8A007E31CE /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 5595158B21D4EE09007E31CE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 56 | 5595158C21D4EE14007E31CE /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 57 | 5595158D21D4EE1E007E31CE /* BSGridCollectionViewLayout.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = BSGridCollectionViewLayout.podspec; sourceTree = ""; }; 58 | 55D03E642253CDFD00567C18 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 552D02D821D4E9E10017BF81 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 558BE65821D4EBB800F06E8B /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 558BE66021D4EBB800F06E8B /* BSGridCollectionViewLayout.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 558BE66F21D4EC3B00F06E8B /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | 55CB6AF621D4ECD40014DB72 /* BSGridCollectionViewLayout.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 552D02D121D4E9E10017BF81 = { 89 | isa = PBXGroup; 90 | children = ( 91 | 55D03E642253CDFD00567C18 /* Default-568h@2x.png */, 92 | 5595158B21D4EE09007E31CE /* README.md */, 93 | 5595158C21D4EE14007E31CE /* LICENSE */, 94 | 5595158D21D4EE1E007E31CE /* BSGridCollectionViewLayout.podspec */, 95 | 552D02DD21D4E9E10017BF81 /* Sources */, 96 | 558BE65C21D4EBB800F06E8B /* Tests */, 97 | 558BE67321D4EC3B00F06E8B /* Example */, 98 | 552D02DC21D4E9E10017BF81 /* Products */, 99 | 55CB6AF521D4ECD40014DB72 /* Frameworks */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | 552D02DC21D4E9E10017BF81 /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 552D02DB21D4E9E10017BF81 /* BSGridCollectionViewLayout.framework */, 107 | 558BE65B21D4EBB800F06E8B /* Tests.xctest */, 108 | 558BE67221D4EC3B00F06E8B /* Example.app */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | 552D02DD21D4E9E10017BF81 /* Sources */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 558BE65521D4EB4E00F06E8B /* GridCollectionViewLayout.swift */, 117 | 552D02DE21D4E9E10017BF81 /* BSGridCollectionViewLayout.h */, 118 | 552D02DF21D4E9E10017BF81 /* InfoSource.plist */, 119 | ); 120 | path = Sources; 121 | sourceTree = ""; 122 | }; 123 | 558BE65C21D4EBB800F06E8B /* Tests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 558BE66921D4EBED00F06E8B /* FirstIndexTests.swift */, 127 | 558BE66821D4EBED00F06E8B /* FirstRowTests.swift */, 128 | 558BE66621D4EBED00F06E8B /* LastIndexTests.swift */, 129 | 558BE66721D4EBED00F06E8B /* LastRowTests.swift */, 130 | 558BE65D21D4EBB800F06E8B /* Tests.swift */, 131 | ); 132 | path = Tests; 133 | sourceTree = ""; 134 | }; 135 | 558BE67321D4EC3B00F06E8B /* Example */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 558BE67421D4EC3B00F06E8B /* AppDelegate.swift */, 139 | 558BE67621D4EC3B00F06E8B /* ViewController.swift */, 140 | 558BE67821D4EC3B00F06E8B /* Main.storyboard */, 141 | 558BE67B21D4EC3B00F06E8B /* Assets.xcassets */, 142 | 558BE68021D4EC3B00F06E8B /* InfoExample.plist */, 143 | 5595158921D4ED8A007E31CE /* LaunchScreen.storyboard */, 144 | ); 145 | path = Example; 146 | sourceTree = ""; 147 | }; 148 | 55CB6AF521D4ECD40014DB72 /* Frameworks */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | ); 152 | name = Frameworks; 153 | sourceTree = ""; 154 | }; 155 | /* End PBXGroup section */ 156 | 157 | /* Begin PBXHeadersBuildPhase section */ 158 | 552D02D621D4E9E10017BF81 /* Headers */ = { 159 | isa = PBXHeadersBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 552D02E021D4E9E10017BF81 /* BSGridCollectionViewLayout.h in Headers */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXHeadersBuildPhase section */ 167 | 168 | /* Begin PBXNativeTarget section */ 169 | 552D02DA21D4E9E10017BF81 /* BSGridCollectionViewLayout */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 552D02E321D4E9E10017BF81 /* Build configuration list for PBXNativeTarget "BSGridCollectionViewLayout" */; 172 | buildPhases = ( 173 | 552D02D621D4E9E10017BF81 /* Headers */, 174 | 552D02D721D4E9E10017BF81 /* Sources */, 175 | 552D02D821D4E9E10017BF81 /* Frameworks */, 176 | 552D02D921D4E9E10017BF81 /* Resources */, 177 | ); 178 | buildRules = ( 179 | ); 180 | dependencies = ( 181 | ); 182 | name = BSGridCollectionViewLayout; 183 | productName = BSGridCollectionViewLayout; 184 | productReference = 552D02DB21D4E9E10017BF81 /* BSGridCollectionViewLayout.framework */; 185 | productType = "com.apple.product-type.framework"; 186 | }; 187 | 558BE65A21D4EBB800F06E8B /* Tests */ = { 188 | isa = PBXNativeTarget; 189 | buildConfigurationList = 558BE66321D4EBB800F06E8B /* Build configuration list for PBXNativeTarget "Tests" */; 190 | buildPhases = ( 191 | 558BE65721D4EBB800F06E8B /* Sources */, 192 | 558BE65821D4EBB800F06E8B /* Frameworks */, 193 | 558BE65921D4EBB800F06E8B /* Resources */, 194 | ); 195 | buildRules = ( 196 | ); 197 | dependencies = ( 198 | 558BE66221D4EBB800F06E8B /* PBXTargetDependency */, 199 | ); 200 | name = Tests; 201 | productName = Tests; 202 | productReference = 558BE65B21D4EBB800F06E8B /* Tests.xctest */; 203 | productType = "com.apple.product-type.bundle.unit-test"; 204 | }; 205 | 558BE67121D4EC3B00F06E8B /* Example */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 558BE68121D4EC3B00F06E8B /* Build configuration list for PBXNativeTarget "Example" */; 208 | buildPhases = ( 209 | 558BE66E21D4EC3B00F06E8B /* Sources */, 210 | 558BE66F21D4EC3B00F06E8B /* Frameworks */, 211 | 558BE67021D4EC3B00F06E8B /* Resources */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | ); 217 | name = Example; 218 | productName = Example; 219 | productReference = 558BE67221D4EC3B00F06E8B /* Example.app */; 220 | productType = "com.apple.product-type.application"; 221 | }; 222 | /* End PBXNativeTarget section */ 223 | 224 | /* Begin PBXProject section */ 225 | 552D02D221D4E9E10017BF81 /* Project object */ = { 226 | isa = PBXProject; 227 | attributes = { 228 | LastSwiftUpdateCheck = 1010; 229 | LastUpgradeCheck = 1010; 230 | ORGANIZATIONNAME = "Joakim Gyllström"; 231 | TargetAttributes = { 232 | 552D02DA21D4E9E10017BF81 = { 233 | CreatedOnToolsVersion = 10.1; 234 | LastSwiftMigration = 1020; 235 | }; 236 | 558BE65A21D4EBB800F06E8B = { 237 | CreatedOnToolsVersion = 10.1; 238 | LastSwiftMigration = 1020; 239 | }; 240 | 558BE67121D4EC3B00F06E8B = { 241 | CreatedOnToolsVersion = 10.1; 242 | LastSwiftMigration = 1020; 243 | }; 244 | }; 245 | }; 246 | buildConfigurationList = 552D02D521D4E9E10017BF81 /* Build configuration list for PBXProject "BSGridCollectionViewLayout" */; 247 | compatibilityVersion = "Xcode 9.3"; 248 | developmentRegion = en; 249 | hasScannedForEncodings = 0; 250 | knownRegions = ( 251 | en, 252 | Base, 253 | ); 254 | mainGroup = 552D02D121D4E9E10017BF81; 255 | productRefGroup = 552D02DC21D4E9E10017BF81 /* Products */; 256 | projectDirPath = ""; 257 | projectRoot = ""; 258 | targets = ( 259 | 552D02DA21D4E9E10017BF81 /* BSGridCollectionViewLayout */, 260 | 558BE65A21D4EBB800F06E8B /* Tests */, 261 | 558BE67121D4EC3B00F06E8B /* Example */, 262 | ); 263 | }; 264 | /* End PBXProject section */ 265 | 266 | /* Begin PBXResourcesBuildPhase section */ 267 | 552D02D921D4E9E10017BF81 /* Resources */ = { 268 | isa = PBXResourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | 558BE65921D4EBB800F06E8B /* Resources */ = { 275 | isa = PBXResourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | 558BE67021D4EC3B00F06E8B /* Resources */ = { 282 | isa = PBXResourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | 5595158A21D4ED8A007E31CE /* LaunchScreen.storyboard in Resources */, 286 | 55D03E652253CDFD00567C18 /* Default-568h@2x.png in Resources */, 287 | 558BE67C21D4EC3B00F06E8B /* Assets.xcassets in Resources */, 288 | 558BE67A21D4EC3B00F06E8B /* Main.storyboard in Resources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXResourcesBuildPhase section */ 293 | 294 | /* Begin PBXSourcesBuildPhase section */ 295 | 552D02D721D4E9E10017BF81 /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | 558BE65621D4EB4E00F06E8B /* GridCollectionViewLayout.swift in Sources */, 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | }; 303 | 558BE65721D4EBB800F06E8B /* Sources */ = { 304 | isa = PBXSourcesBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | 558BE66D21D4EBED00F06E8B /* FirstIndexTests.swift in Sources */, 308 | 558BE66A21D4EBED00F06E8B /* LastIndexTests.swift in Sources */, 309 | 558BE66C21D4EBED00F06E8B /* FirstRowTests.swift in Sources */, 310 | 558BE66B21D4EBED00F06E8B /* LastRowTests.swift in Sources */, 311 | 558BE65E21D4EBB800F06E8B /* Tests.swift in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | 558BE66E21D4EC3B00F06E8B /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 558BE67721D4EC3B00F06E8B /* ViewController.swift in Sources */, 320 | 558BE67521D4EC3B00F06E8B /* AppDelegate.swift in Sources */, 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | /* End PBXSourcesBuildPhase section */ 325 | 326 | /* Begin PBXTargetDependency section */ 327 | 558BE66221D4EBB800F06E8B /* PBXTargetDependency */ = { 328 | isa = PBXTargetDependency; 329 | target = 552D02DA21D4E9E10017BF81 /* BSGridCollectionViewLayout */; 330 | targetProxy = 558BE66121D4EBB800F06E8B /* PBXContainerItemProxy */; 331 | }; 332 | /* End PBXTargetDependency section */ 333 | 334 | /* Begin PBXVariantGroup section */ 335 | 558BE67821D4EC3B00F06E8B /* Main.storyboard */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | 558BE67921D4EC3B00F06E8B /* Base */, 339 | ); 340 | name = Main.storyboard; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXVariantGroup section */ 344 | 345 | /* Begin XCBuildConfiguration section */ 346 | 552D02E121D4E9E10017BF81 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_ANALYZER_NONNULL = YES; 351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 353 | CLANG_CXX_LIBRARY = "libc++"; 354 | CLANG_ENABLE_MODULES = YES; 355 | CLANG_ENABLE_OBJC_ARC = YES; 356 | CLANG_ENABLE_OBJC_WEAK = YES; 357 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 358 | CLANG_WARN_BOOL_CONVERSION = YES; 359 | CLANG_WARN_COMMA = YES; 360 | CLANG_WARN_CONSTANT_CONVERSION = YES; 361 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 362 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 363 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 364 | CLANG_WARN_EMPTY_BODY = YES; 365 | CLANG_WARN_ENUM_CONVERSION = YES; 366 | CLANG_WARN_INFINITE_RECURSION = YES; 367 | CLANG_WARN_INT_CONVERSION = YES; 368 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 369 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 370 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 373 | CLANG_WARN_STRICT_PROTOTYPES = YES; 374 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 375 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 376 | CLANG_WARN_UNREACHABLE_CODE = YES; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | CODE_SIGN_IDENTITY = "iPhone Developer"; 379 | COPY_PHASE_STRIP = NO; 380 | CURRENT_PROJECT_VERSION = 1; 381 | DEBUG_INFORMATION_FORMAT = dwarf; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | ENABLE_TESTABILITY = YES; 384 | GCC_C_LANGUAGE_STANDARD = gnu11; 385 | GCC_DYNAMIC_NO_PIC = NO; 386 | GCC_NO_COMMON_BLOCKS = YES; 387 | GCC_OPTIMIZATION_LEVEL = 0; 388 | GCC_PREPROCESSOR_DEFINITIONS = ( 389 | "DEBUG=1", 390 | "$(inherited)", 391 | ); 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 399 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 400 | MTL_FAST_MATH = YES; 401 | ONLY_ACTIVE_ARCH = YES; 402 | SDKROOT = iphoneos; 403 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 404 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 405 | VERSIONING_SYSTEM = "apple-generic"; 406 | VERSION_INFO_PREFIX = ""; 407 | }; 408 | name = Debug; 409 | }; 410 | 552D02E221D4E9E10017BF81 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ALWAYS_SEARCH_USER_PATHS = NO; 414 | CLANG_ANALYZER_NONNULL = YES; 415 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_ENABLE_OBJC_WEAK = YES; 421 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 422 | CLANG_WARN_BOOL_CONVERSION = YES; 423 | CLANG_WARN_COMMA = YES; 424 | CLANG_WARN_CONSTANT_CONVERSION = YES; 425 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 426 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 427 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INFINITE_RECURSION = YES; 431 | CLANG_WARN_INT_CONVERSION = YES; 432 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 434 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 436 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 437 | CLANG_WARN_STRICT_PROTOTYPES = YES; 438 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 439 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 440 | CLANG_WARN_UNREACHABLE_CODE = YES; 441 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 442 | CODE_SIGN_IDENTITY = "iPhone Developer"; 443 | COPY_PHASE_STRIP = NO; 444 | CURRENT_PROJECT_VERSION = 1; 445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 446 | ENABLE_NS_ASSERTIONS = NO; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | GCC_C_LANGUAGE_STANDARD = gnu11; 449 | GCC_NO_COMMON_BLOCKS = YES; 450 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 451 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 452 | GCC_WARN_UNDECLARED_SELECTOR = YES; 453 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 454 | GCC_WARN_UNUSED_FUNCTION = YES; 455 | GCC_WARN_UNUSED_VARIABLE = YES; 456 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 457 | MTL_ENABLE_DEBUG_INFO = NO; 458 | MTL_FAST_MATH = YES; 459 | SDKROOT = iphoneos; 460 | SWIFT_COMPILATION_MODE = wholemodule; 461 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 462 | VALIDATE_PRODUCT = YES; 463 | VERSIONING_SYSTEM = "apple-generic"; 464 | VERSION_INFO_PREFIX = ""; 465 | }; 466 | name = Release; 467 | }; 468 | 552D02E421D4E9E10017BF81 /* Debug */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | CLANG_ENABLE_MODULES = YES; 472 | CODE_SIGN_IDENTITY = ""; 473 | CODE_SIGN_STYLE = Automatic; 474 | DEFINES_MODULE = YES; 475 | DEVELOPMENT_TEAM = Y2NHS7RD78; 476 | DYLIB_COMPATIBILITY_VERSION = 1; 477 | DYLIB_CURRENT_VERSION = 1; 478 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 479 | INFOPLIST_FILE = "$(SRCROOT)/Sources/InfoSource.plist"; 480 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 481 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 482 | LD_RUNPATH_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "@executable_path/Frameworks", 485 | "@loader_path/Frameworks", 486 | ); 487 | PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.BSGridCollectionViewLayout; 488 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 489 | SKIP_INSTALL = YES; 490 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 491 | SWIFT_VERSION = 5.0; 492 | TARGETED_DEVICE_FAMILY = "1,2"; 493 | }; 494 | name = Debug; 495 | }; 496 | 552D02E521D4E9E10017BF81 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | CLANG_ENABLE_MODULES = YES; 500 | CODE_SIGN_IDENTITY = ""; 501 | CODE_SIGN_STYLE = Automatic; 502 | DEFINES_MODULE = YES; 503 | DEVELOPMENT_TEAM = Y2NHS7RD78; 504 | DYLIB_COMPATIBILITY_VERSION = 1; 505 | DYLIB_CURRENT_VERSION = 1; 506 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 507 | INFOPLIST_FILE = "$(SRCROOT)/Sources/InfoSource.plist"; 508 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 509 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 510 | LD_RUNPATH_SEARCH_PATHS = ( 511 | "$(inherited)", 512 | "@executable_path/Frameworks", 513 | "@loader_path/Frameworks", 514 | ); 515 | PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.BSGridCollectionViewLayout; 516 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 517 | SKIP_INSTALL = YES; 518 | SWIFT_VERSION = 5.0; 519 | TARGETED_DEVICE_FAMILY = "1,2"; 520 | }; 521 | name = Release; 522 | }; 523 | 558BE66421D4EBB800F06E8B /* Debug */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | CODE_SIGN_STYLE = Automatic; 527 | DEVELOPMENT_TEAM = Y2NHS7RD78; 528 | LD_RUNPATH_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "@executable_path/Frameworks", 531 | "@loader_path/Frameworks", 532 | ); 533 | PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Tests; 534 | PRODUCT_NAME = "$(TARGET_NAME)"; 535 | SWIFT_VERSION = 5.0; 536 | TARGETED_DEVICE_FAMILY = "1,2"; 537 | }; 538 | name = Debug; 539 | }; 540 | 558BE66521D4EBB800F06E8B /* Release */ = { 541 | isa = XCBuildConfiguration; 542 | buildSettings = { 543 | CODE_SIGN_STYLE = Automatic; 544 | DEVELOPMENT_TEAM = Y2NHS7RD78; 545 | LD_RUNPATH_SEARCH_PATHS = ( 546 | "$(inherited)", 547 | "@executable_path/Frameworks", 548 | "@loader_path/Frameworks", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Tests; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_VERSION = 5.0; 553 | TARGETED_DEVICE_FAMILY = "1,2"; 554 | }; 555 | name = Release; 556 | }; 557 | 558BE68221D4EC3B00F06E8B /* Debug */ = { 558 | isa = XCBuildConfiguration; 559 | buildSettings = { 560 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 561 | CODE_SIGN_STYLE = Automatic; 562 | DEVELOPMENT_TEAM = Y2NHS7RD78; 563 | INFOPLIST_FILE = "$(SRCROOT)/Example/InfoExample.plist"; 564 | LD_RUNPATH_SEARCH_PATHS = ( 565 | "$(inherited)", 566 | "@executable_path/Frameworks", 567 | ); 568 | PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example; 569 | PRODUCT_NAME = "$(TARGET_NAME)"; 570 | SWIFT_VERSION = 5.0; 571 | TARGETED_DEVICE_FAMILY = "1,2"; 572 | }; 573 | name = Debug; 574 | }; 575 | 558BE68321D4EC3B00F06E8B /* Release */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 579 | CODE_SIGN_STYLE = Automatic; 580 | DEVELOPMENT_TEAM = Y2NHS7RD78; 581 | INFOPLIST_FILE = "$(SRCROOT)/Example/InfoExample.plist"; 582 | LD_RUNPATH_SEARCH_PATHS = ( 583 | "$(inherited)", 584 | "@executable_path/Frameworks", 585 | ); 586 | PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example; 587 | PRODUCT_NAME = "$(TARGET_NAME)"; 588 | SWIFT_VERSION = 5.0; 589 | TARGETED_DEVICE_FAMILY = "1,2"; 590 | }; 591 | name = Release; 592 | }; 593 | /* End XCBuildConfiguration section */ 594 | 595 | /* Begin XCConfigurationList section */ 596 | 552D02D521D4E9E10017BF81 /* Build configuration list for PBXProject "BSGridCollectionViewLayout" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | 552D02E121D4E9E10017BF81 /* Debug */, 600 | 552D02E221D4E9E10017BF81 /* Release */, 601 | ); 602 | defaultConfigurationIsVisible = 0; 603 | defaultConfigurationName = Release; 604 | }; 605 | 552D02E321D4E9E10017BF81 /* Build configuration list for PBXNativeTarget "BSGridCollectionViewLayout" */ = { 606 | isa = XCConfigurationList; 607 | buildConfigurations = ( 608 | 552D02E421D4E9E10017BF81 /* Debug */, 609 | 552D02E521D4E9E10017BF81 /* Release */, 610 | ); 611 | defaultConfigurationIsVisible = 0; 612 | defaultConfigurationName = Release; 613 | }; 614 | 558BE66321D4EBB800F06E8B /* Build configuration list for PBXNativeTarget "Tests" */ = { 615 | isa = XCConfigurationList; 616 | buildConfigurations = ( 617 | 558BE66421D4EBB800F06E8B /* Debug */, 618 | 558BE66521D4EBB800F06E8B /* Release */, 619 | ); 620 | defaultConfigurationIsVisible = 0; 621 | defaultConfigurationName = Release; 622 | }; 623 | 558BE68121D4EC3B00F06E8B /* Build configuration list for PBXNativeTarget "Example" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | 558BE68221D4EC3B00F06E8B /* Debug */, 627 | 558BE68321D4EC3B00F06E8B /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | /* End XCConfigurationList section */ 633 | }; 634 | rootObject = 552D02D221D4E9E10017BF81 /* Project object */; 635 | } 636 | -------------------------------------------------------------------------------- /BSGridCollectionViewLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BSGridCollectionViewLayout.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BSGridCollectionViewLayout.xcodeproj/xcshareddata/xcschemes/BSGridCollectionViewLayout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /BSGridCollectionViewLayout.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaoj/BSGridCollectionViewLayout/5941fb9cbae1360f8f59e383727a90ead8a2738e/Default-568h@2x.png -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015 Joakim Gyllström 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 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | 28 | var window: UIWindow? 29 | 30 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 31 | return true 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/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 | -------------------------------------------------------------------------------- /Example/InfoExample.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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.2.3 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 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015 Joakim Gyllström 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 | 23 | import UIKit 24 | import BSGridCollectionViewLayout 25 | 26 | class ViewController: UICollectionViewController { 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | collectionView?.backgroundColor = UIColor.white 31 | 32 | let layout = GridCollectionViewLayout() 33 | layout.itemsPerRow = 3 34 | layout.itemSpacing = 2 35 | layout.itemHeightRatio = 3/4 36 | 37 | collectionView?.collectionViewLayout = layout 38 | 39 | collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") 40 | } 41 | 42 | // MARK: Data source 43 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 44 | return 2 45 | } 46 | 47 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 48 | if section == 0 { 49 | return 11 50 | } else { 51 | return 18 52 | } 53 | } 54 | 55 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 56 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 57 | if (indexPath as NSIndexPath).section == 0 { 58 | cell.backgroundColor = UIColor.blue 59 | } else { 60 | cell.backgroundColor = UIColor.red 61 | } 62 | 63 | return cell 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Joakim Gyllstrom 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BSGridCollectionViewLayout 2 | 3 | [![CI Status](http://img.shields.io/travis/mikaoj/BSGridCollectionViewLayout.svg?style=flat)](https://travis-ci.org/mikaoj/BSGridCollectionViewLayout) 4 | [![Version](https://img.shields.io/cocoapods/v/BSGridCollectionViewLayout.svg?style=flat)](http://cocoapods.org/pods/BSGridCollectionViewLayout) 5 | [![License](https://img.shields.io/cocoapods/l/BSGridCollectionViewLayout.svg?style=flat)](http://cocoapods.org/pods/BSGridCollectionViewLayout) 6 | [![Platform](https://img.shields.io/cocoapods/p/BSGridCollectionViewLayout.svg?style=flat)](http://cocoapods.org/pods/BSGridCollectionViewLayout) 7 | 8 | BSGridCollectionViewLayout is a simple UICollectionViewLayout. It simply displays the items in a grid. It doesn't have a concept of sections. So even if the items are in different data source / sections. They will be displayed as being in one continuous grid without any section breaks. I highly doubt that anyone besides me will use this, but I'm using it in [BSImagePicker](https://github.com/mikaoj/BSImagePicker). 9 | 10 | ## Usage 11 | 12 | There are 3 properties for you to tweak: 13 | * itemsPerRow - Number of items per row 14 | * itemSpacing - Spacing between items (vertical and horizontal) 15 | * itemHeightRatio - The item height ratio relative to it's width 16 | 17 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 18 | 19 | ## Requirements 20 | iOS 8 21 | 22 | ## Installation 23 | 24 | BSGridCollectionViewLayout is available through [CocoaPods](http://cocoapods.org). To install 25 | it, simply add the following line to your Podfile: 26 | 27 | ```ruby 28 | pod "BSGridCollectionViewLayout", "~> 1.1.0" 29 | ``` 30 | 31 | ## Author 32 | 33 | Joakim Gyllstrom, joakim@backslashed.se 34 | 35 | ## License 36 | 37 | BSGridCollectionViewLayout is available under the MIT license. See the LICENSE file for more info. 38 | -------------------------------------------------------------------------------- /Sources/BSGridCollectionViewLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // BSGridCollectionViewLayout.h 3 | // BSGridCollectionViewLayout 4 | // 5 | // Created by Joakim Gyllström on 2018-12-27. 6 | // Copyright © 2018 Joakim Gyllström. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BSGridCollectionViewLayout. 12 | FOUNDATION_EXPORT double BSGridCollectionViewLayoutVersionNumber; 13 | 14 | //! Project version string for BSGridCollectionViewLayout. 15 | FOUNDATION_EXPORT const unsigned char BSGridCollectionViewLayoutVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/GridCollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015 Joakim Gyllström 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 | 23 | import UIKit 24 | 25 | /** 26 | Provides a grid collection view layout 27 | */ 28 | public final class GridCollectionViewLayout: UICollectionViewLayout { 29 | /** 30 | Spacing between items (horizontal and vertical) 31 | */ 32 | public var itemSpacing: CGFloat = 0 { 33 | didSet { 34 | itemSize = estimatedItemSize() 35 | } 36 | } 37 | 38 | /** 39 | Number of items per row 40 | */ 41 | public var itemsPerRow = 3 { 42 | didSet { 43 | itemSize = estimatedItemSize() 44 | } 45 | } 46 | 47 | /** 48 | Item height ratio relative to it's width 49 | */ 50 | public var itemHeightRatio: CGFloat = 1 { 51 | didSet { 52 | itemSize = estimatedItemSize() 53 | } 54 | } 55 | 56 | /** 57 | Size for each item 58 | */ 59 | public private(set) var itemSize = CGSize.zero 60 | 61 | var items = 0 62 | var rows = 0 63 | 64 | public override func prepare() { 65 | // Set total number of items and rows 66 | items = estimatedNumberOfItems() 67 | rows = items / itemsPerRow + ((items % itemsPerRow > 0) ? 1 : 0) 68 | 69 | // Set item size 70 | itemSize = estimatedItemSize() 71 | } 72 | 73 | /** 74 | See UICollectionViewLayout documentation 75 | */ 76 | public override var collectionViewContentSize: CGSize { 77 | guard let collectionView = collectionView, rows > 0 else { 78 | return CGSize.zero 79 | } 80 | 81 | let height = estimatedRowHeight() * CGFloat(rows) 82 | return CGSize(width: collectionView.bounds.width, height: height) 83 | } 84 | 85 | /** 86 | See UICollectionViewLayout documentation 87 | */ 88 | public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 89 | return indexPathsInRect(rect).map { (indexPath) -> UICollectionViewLayoutAttributes? in 90 | return self.layoutAttributesForItem(at: indexPath) 91 | }.compactMap { $0 } 92 | } 93 | 94 | /** 95 | See UICollectionViewLayout documentation 96 | */ 97 | public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 98 | // Guard against negative row/sections. 99 | guard indexPath.row >= 0, indexPath.section >= 0 else { 100 | return nil 101 | } 102 | 103 | let itemIndex = flatIndex(indexPath) // index among total number of items 104 | let rowIndex = itemIndex % itemsPerRow // index within it's row 105 | let row = itemIndex / itemsPerRow // which row for that item 106 | 107 | let x = (CGFloat(rowIndex) * itemSpacing) + (CGFloat(rowIndex) * itemSize.width) 108 | let y = (CGFloat(row) * itemSpacing) + (CGFloat(row) * itemSize.height) 109 | let width = itemSize.width 110 | let height = itemSize.height 111 | 112 | let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath) 113 | attribute.frame = CGRect(x: x, y: y, width: width, height: height) 114 | 115 | return attribute 116 | } 117 | 118 | /** 119 | See UICollectionViewLayout documentation 120 | */ 121 | public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 122 | return true 123 | } 124 | 125 | // No decoration or supplementary views 126 | /** 127 | See UICollectionViewLayout documentation 128 | */ 129 | public override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 130 | return nil 131 | } 132 | /** 133 | See UICollectionViewLayout documentation 134 | */ 135 | public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 136 | return nil 137 | } 138 | } 139 | 140 | extension GridCollectionViewLayout { 141 | /** 142 | Calculates which index paths are within a given rect 143 | - parameter rect: The rect which we want index paths for 144 | - returns: An array of indexPaths for that rect 145 | */ 146 | func indexPathsInRect(_ rect: CGRect) -> [IndexPath] { 147 | // Make sure we have items/rows 148 | guard items > 0 && rows > 0 else { return [] } 149 | 150 | let rowHeight = estimatedRowHeight() 151 | 152 | let startRow = GridCollectionViewLayout.firstRowInRect(rect, withRowHeight: rowHeight) 153 | let endRow = GridCollectionViewLayout.lastRowInRect(rect, withRowHeight: rowHeight, max: rows) 154 | guard startRow <= endRow else { return [] } 155 | 156 | let startIndex = GridCollectionViewLayout.firstIndexInRow(min(startRow, endRow), withItemsPerRow: itemsPerRow) 157 | let endIndex = GridCollectionViewLayout.lastIndexInRow(max(startRow, endRow), withItemsPerRow: itemsPerRow, numberOfItems: items) 158 | 159 | guard startIndex <= endIndex else { return [] } 160 | let indexPaths = (startIndex...endIndex).map { indexPathFromFlatIndex($0) } 161 | 162 | return indexPaths 163 | } 164 | 165 | /** 166 | Calculates which row index would be first for a given rect. 167 | - parameter rect: The rect to check 168 | - parameter rowHeight: Height for a row 169 | - returns: First row index 170 | */ 171 | static func firstRowInRect(_ rect: CGRect, withRowHeight rowHeight: CGFloat) -> Int { 172 | if rect.origin.y / rowHeight < 0 { 173 | return 0 174 | } else { 175 | return Int(rect.origin.y / rowHeight) 176 | } 177 | } 178 | 179 | /** 180 | Calculates which row index would be last for a given rect. 181 | - parameter rect: The rect to check 182 | - parameter rowHeight: Height for a row 183 | - returns: Last row index 184 | */ 185 | static func lastRowInRect(_ rect: CGRect, withRowHeight rowHeight: CGFloat, max: Int) -> Int { 186 | guard rect.size.height >= rowHeight else { return 0 } 187 | 188 | if (rect.origin.y + rect.height) / rowHeight > CGFloat(max) { 189 | return max - 1 190 | } else { 191 | return Int(ceil((rect.origin.y + rect.height) / rowHeight)) - 1 192 | } 193 | } 194 | 195 | /** 196 | Calculates which index would be the first for a given row. 197 | - parameter row: Row index 198 | - parameter itemsPerRow: How many items there can be in a row 199 | - returns: First index 200 | */ 201 | static func firstIndexInRow(_ row: Int, withItemsPerRow itemsPerRow: Int) -> Int { 202 | return row * itemsPerRow 203 | } 204 | 205 | /** 206 | Calculates which index would be the last for a given row. 207 | - parameter row: Row index 208 | - parameter itemsPerRow: How many items there can be in a row 209 | - parameter numberOfItems: The total number of items. 210 | - returns: Last index 211 | */ 212 | static func lastIndexInRow(_ row: Int, withItemsPerRow itemsPerRow: Int, numberOfItems: Int) -> Int { 213 | let maxIndex = (row + 1) * itemsPerRow - 1 214 | let bounds = numberOfItems - 1 215 | 216 | if maxIndex > bounds { 217 | return bounds 218 | } else { 219 | return maxIndex 220 | } 221 | } 222 | 223 | /** 224 | Takes an index path (which are 2 dimensional) and turns it into a 1 dimensional index 225 | - parameter indexPath: The index path we want to flatten 226 | - returns: A flat index 227 | */ 228 | func flatIndex(_ indexPath: IndexPath) -> Int { 229 | guard let collectionView = collectionView else { 230 | return 0 231 | } 232 | 233 | return (0..<(indexPath as NSIndexPath).section).reduce((indexPath as NSIndexPath).row) { $0 + collectionView.numberOfItems(inSection: $1)} 234 | } 235 | 236 | /** 237 | Converts a flat index into an index path 238 | - parameter index: The flat index 239 | - returns: An index path 240 | */ 241 | func indexPathFromFlatIndex(_ index: Int) -> IndexPath { 242 | guard let collectionView = collectionView else { 243 | return IndexPath(item: 0, section: 0) 244 | } 245 | 246 | var item = index 247 | var section = 0 248 | 249 | while(item >= collectionView.numberOfItems(inSection: section)) { 250 | item -= collectionView.numberOfItems(inSection: section) 251 | section += 1 252 | } 253 | 254 | return IndexPath(item: item, section: section) 255 | } 256 | 257 | /** 258 | Estimated the size of the items 259 | - returns: Estimated item size 260 | */ 261 | func estimatedItemSize() -> CGSize { 262 | guard let collectionView = collectionView else { 263 | return CGSize.zero 264 | } 265 | 266 | let itemWidth = (collectionView.bounds.width - CGFloat(itemsPerRow - 1) * itemSpacing) / CGFloat(itemsPerRow) 267 | return CGSize(width: itemWidth, height: itemWidth * itemHeightRatio) 268 | } 269 | 270 | /** 271 | Estimated total number of items 272 | - returns: Total number of items 273 | */ 274 | func estimatedNumberOfItems() -> Int { 275 | guard let collectionView = collectionView else { 276 | return 0 277 | } 278 | 279 | return (0.. CGFloat { 287 | return itemSize.height+itemSpacing 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /Sources/InfoSource.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.2.5 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/FirstIndexTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2016 Joakim Gyllström 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 | 23 | import XCTest 24 | @testable import BSGridCollectionViewLayout 25 | 26 | class FirstIndexTests: XCTestCase { 27 | func testFirstIndexFirstRow() { 28 | let index = GridCollectionViewLayout.firstIndexInRow(0, withItemsPerRow: 3) 29 | XCTAssertEqual(index, 0) 30 | } 31 | 32 | func testFirstIndexThirdRow() { 33 | let index = GridCollectionViewLayout.firstIndexInRow(3, withItemsPerRow: 3) 34 | XCTAssertEqual(index, 9) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/FirstRowTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2016 Joakim Gyllström 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 | 23 | import UIKit 24 | import XCTest 25 | @testable import BSGridCollectionViewLayout 26 | 27 | class FirstRowTests: XCTestCase { 28 | 29 | func testFirstRowInZeroRect() { 30 | let firstRow = GridCollectionViewLayout.firstRowInRect(CGRect.zero, withRowHeight: 100.0) 31 | XCTAssertEqual(firstRow, 0) 32 | } 33 | 34 | func testFirstRowWithoutOffset() { 35 | let firstRow = GridCollectionViewLayout.firstRowInRect(CGRect(x: 0, y: 0, width: 100, height: 300), withRowHeight: 100.0) 36 | XCTAssertEqual(firstRow, 0) 37 | } 38 | 39 | func testFirstRowWith1RowOffset() { 40 | let firstRow = GridCollectionViewLayout.firstRowInRect(CGRect(x: 0, y: 100, width: 100, height: 300), withRowHeight: 100.0) 41 | XCTAssertEqual(firstRow, 1) 42 | } 43 | 44 | func testFirstRowWithHalfRowOffset() { 45 | let firstRow = GridCollectionViewLayout.firstRowInRect(CGRect(x: 0, y: 50, width: 100, height: 300), withRowHeight: 100.0) 46 | XCTAssertEqual(firstRow, 0) 47 | } 48 | 49 | func testFirstRowWith2RowOffset() { 50 | let firstRow = GridCollectionViewLayout.firstRowInRect(CGRect(x: 0, y: 200, width: 100, height: 300), withRowHeight: 100.0) 51 | XCTAssertEqual(firstRow, 2) 52 | } 53 | 54 | func testFirstRowWithNegativeRectOffset() { 55 | let firstRow = GridCollectionViewLayout.firstRowInRect(CGRect(x: 0, y: -200, width: 100, height: 300), withRowHeight: 100.0) 56 | XCTAssertEqual(firstRow, 0) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/LastIndexTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2016 Joakim Gyllström 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 | 23 | import XCTest 24 | @testable import BSGridCollectionViewLayout 25 | 26 | class LastIndexTests: XCTestCase { 27 | func testLastIndexFirstRowLowNumberOfItems() { 28 | let index = GridCollectionViewLayout.lastIndexInRow(0, withItemsPerRow: 3, numberOfItems: 2) 29 | XCTAssertEqual(index, 1) 30 | } 31 | 32 | func testLastIndexFirstRowMediumNumberOfItems() { 33 | let index = GridCollectionViewLayout.lastIndexInRow(0, withItemsPerRow: 3, numberOfItems: 6) 34 | XCTAssertEqual(index, 2) 35 | } 36 | 37 | func testLastIndexFirstRowLargeNumberOfItems() { 38 | let index = GridCollectionViewLayout.lastIndexInRow(0, withItemsPerRow: 3, numberOfItems: 500) 39 | XCTAssertEqual(index, 2) 40 | } 41 | 42 | func testLastIndexThirdRowLowNumberOfItems() { 43 | let index = GridCollectionViewLayout.lastIndexInRow(2, withItemsPerRow: 3, numberOfItems: 7) 44 | XCTAssertEqual(index, 6) 45 | } 46 | 47 | func testLastIndexThirdRowMediumNumberOfItems() { 48 | let index = GridCollectionViewLayout.lastIndexInRow(2, withItemsPerRow: 3, numberOfItems: 8) 49 | XCTAssertEqual(index, 7) 50 | } 51 | 52 | func testLastIndexThirdRowLargeNumberOfItems() { 53 | let index = GridCollectionViewLayout.lastIndexInRow(2, withItemsPerRow: 3, numberOfItems: 500) 54 | XCTAssertEqual(index, 8) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/LastRowTests.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2016 Joakim Gyllström 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 | 23 | import XCTest 24 | @testable import BSGridCollectionViewLayout 25 | 26 | class LastRowTests: XCTestCase { 27 | 28 | func testLastRowInZeroRect() { 29 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect.zero, withRowHeight: 100, max: 3) 30 | XCTAssertEqual(lastRow, 0) 31 | } 32 | 33 | func testLastRowWithoutOffsetSmallMax() { 34 | let max = 2 35 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 0, width: 100, height: 300), withRowHeight: 100, max: max) 36 | XCTAssertEqual(lastRow, 1) 37 | } 38 | 39 | func testLastRowWithoutOffsetMediumMax() { 40 | let max = 3 41 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 0, width: 100, height: 300), withRowHeight: 100, max: max) 42 | XCTAssertEqual(lastRow, 2) 43 | } 44 | 45 | func testLastRowWithoutOffsetLargeMax() { 46 | let max = 8 47 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 0, width: 100, height: 300), withRowHeight: 100, max: max) 48 | XCTAssertEqual(lastRow, 2) 49 | } 50 | 51 | func testLastRowWith1RowOffsetSmallMax() { 52 | let max = 2 53 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 100, width: 100, height: 300), withRowHeight: 100, max: max) 54 | XCTAssertEqual(lastRow, 1) 55 | } 56 | 57 | func testLastRowWith1RowOffsetMediumMax() { 58 | let max = 3 59 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 100, width: 100, height: 300), withRowHeight: 100, max: max) 60 | XCTAssertEqual(lastRow, 2) 61 | } 62 | 63 | func testLastRowWith1RowOffsetLargeMax() { 64 | let max = 8 65 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 100, width: 100, height: 300), withRowHeight: 100, max: max) 66 | XCTAssertEqual(lastRow, 3) 67 | } 68 | 69 | func testLastRowWithHalfRowOffsetSmallMax() { 70 | let max = 2 71 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 50, width: 100, height: 300), withRowHeight: 100, max: max) 72 | XCTAssertEqual(lastRow, 1) 73 | } 74 | 75 | func testLastRowWithHalfRowOffsetMediumMax() { 76 | let max = 3 77 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 50, width: 100, height: 300), withRowHeight: 100, max: max) 78 | XCTAssertEqual(lastRow, 2) 79 | } 80 | 81 | func testLastRowWithHalfRowOffsetLargeMax() { 82 | let max = 8 83 | let lastRow = GridCollectionViewLayout.lastRowInRect(CGRect(x: 0, y: 50, width: 100, height: 300), withRowHeight: 100, max: max) 84 | XCTAssertEqual(lastRow, 3) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests.swift 3 | // Tests 4 | // 5 | // Created by Joakim Gyllström on 2018-12-27. 6 | // Copyright © 2018 Joakim Gyllström. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class Tests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | --------------------------------------------------------------------------------