├── .gitignore ├── .travis.yml ├── Assets └── logo.svg ├── CHANGELOG.md ├── CODEOWNERS ├── CampcotCollectionView.podspec ├── CampcotCollectionView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── CampcotCollectionView.xcscheme ├── CampcotCollectionView.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── Example ├── Assets │ └── campcot.gif ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Example.xcscheme └── Source │ ├── AppDelegate.swift │ ├── CustomCollectionViewCell.swift │ ├── CustomHeaderView.swift │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-24@2x.png │ │ ├── Icon-27.5@2x.png │ │ ├── Icon-29@2x.png │ │ ├── Icon-29@3x.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-44@2x.png │ │ ├── Icon-86@2x.png │ │ ├── Icon-98@2x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x-1.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x-1.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x-1.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ ├── Icon-App-83.5x83.5@2x.png │ │ └── ItunesArtwork@2x.png │ ├── Contents.json │ ├── logo.imageset │ │ ├── Contents.json │ │ └── logo.pdf │ └── logo_splash.imageset │ │ ├── Contents.json │ │ └── logo_splash.png │ ├── Launch Screen.storyboard │ ├── Main.storyboard │ ├── StoryboardViewController.swift │ ├── Supporting Files │ └── Info.plist │ └── ViewController.swift ├── LICENSE ├── README.md ├── Source ├── CampcotCollectionView.swift ├── CollapsedLayout.swift ├── ContentSizeAdjustmentBehavior.swift ├── ExpandedLayout.swift └── Supporting Files │ ├── CampcotCollectionView.h │ └── Info.plist ├── Tests ├── CampcotCollectionViewTests.swift └── Supporting Files │ └── Info.plist └── scripts └── deploy.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | Pods/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | osx_image: xcode13.1 3 | language: objective-c 4 | 5 | before_deploy: 6 | - gem install cocoapods 7 | - pod repo add-cdn trunk 'https://cdn.cocoapods.org/' 8 | 9 | deploy: 10 | provider: script 11 | script: ./scripts/deploy.sh 12 | on: 13 | tags: true 14 | 15 | script: 16 | - set -o pipefail && xcodebuild -scheme CampcotCollectionView -workspace CampcotCollectionView.xcworkspace -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.0' build test | xcpretty --color 17 | - pod lib lint 18 | 19 | after_success: 20 | - bash <(curl -s https://codecov.io/bash) 21 | -------------------------------------------------------------------------------- /Assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Page 1 Copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for CampcotCollectionView 0.0.8 2 | ### Added 3 | * Optional `isExpanded` parameter to `CampcotCollectionView` initializer. 4 | 5 | # Changelog for CampcotCollectionView 0.0.7 6 | ### Fixed 7 | * Fixed crash with storyboard implementation. 8 | 9 | ### Added 10 | * Storyboard support. 11 | * Properties in `Attributes Inspector` for `CampcotCollectionView`. 12 | * Example for storyboard implementation. 13 | 14 | # Changelog for CampcotCollectionView 0.0.6 15 | ### Added 16 | * `contentSizeAdjustmentBehavior` property allows to manage content size calculation. 17 | 18 | # Changelog for CampcotCollectionView 0.0.5 19 | ### Added 20 | * Layout supports more than 2 items in row. 21 | 22 | ### Fixed 23 | * Fixed bug with pinned header position when `contentInset.top` is bigger than section height. 24 | 25 | # Changelog for CampcotCollectionView 0.0.4 26 | ### Changed 27 | * Updates for Swift 5.0 28 | 29 | # Changelog for CampcotCollectionView 0.0.2 30 | ### Added 31 | * Example UI update. 32 | 33 | # Changelog for CampcotCollectionView 0.0.1 34 | ### Added 35 | * Initial setup. 36 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @VadzimMarozau -------------------------------------------------------------------------------- /CampcotCollectionView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CampcotCollectionView' 3 | s.version = '0.0.8' 4 | s.summary = 'CapmcotCollectionView is custom UICollectionView that allows to expand and collapse sections.' 5 | s.description = 'This library provides a custom UICollectionView that allows to expand and collapse sections.' \ 6 | 'It provides a simple API to manage collection view appearance.' 7 | s.homepage = 'https://github.com/touchlane/CampcotCollectionView' 8 | s.license = { :type => 'MIT', :file => 'LICENSE' } 9 | s.author = { 'Touchlane LLC' => 'tech@touchlane.com' } 10 | s.source = { :git => 'https://github.com/touchlane/CampcotCollectionView.git', :tag => s.version.to_s } 11 | s.ios.deployment_target = '9.0' 12 | s.source_files = 'Source/*.swift' 13 | s.swift_versions = ['5.0'] 14 | end 15 | -------------------------------------------------------------------------------- /CampcotCollectionView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DD35D937201B57C80049ED03 /* CampcotCollectionView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD35D92D201B57C80049ED03 /* CampcotCollectionView.framework */; }; 11 | DD35D93C201B57C80049ED03 /* CampcotCollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35D93B201B57C80049ED03 /* CampcotCollectionViewTests.swift */; }; 12 | DD35D93E201B57C80049ED03 /* CampcotCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = DD35D930201B57C80049ED03 /* CampcotCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | DD35D94A201B5E280049ED03 /* ExpandedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35D949201B5E280049ED03 /* ExpandedLayout.swift */; }; 14 | DD35D98A201B66B40049ED03 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DD35D987201B66B40049ED03 /* LICENSE */; }; 15 | DD35D98B201B66B40049ED03 /* CampcotCollectionView.podspec in Resources */ = {isa = PBXBuildFile; fileRef = DD35D988201B66B40049ED03 /* CampcotCollectionView.podspec */; }; 16 | DD35D98C201B66B40049ED03 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = DD35D989201B66B40049ED03 /* README.md */; }; 17 | DDC6199F2369C68100B6981E /* ContentSizeAdjustmentBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC6199E2369C68100B6981E /* ContentSizeAdjustmentBehavior.swift */; }; 18 | DDFB5C80201F451E00F8E164 /* CollapsedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFB5C7F201F451E00F8E164 /* CollapsedLayout.swift */; }; 19 | DDFB5C82201F522B00F8E164 /* CampcotCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFB5C81201F522B00F8E164 /* CampcotCollectionView.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | DD35D938201B57C80049ED03 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = DD35D924201B57C80049ED03 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = DD35D92C201B57C80049ED03; 28 | remoteInfo = ExpandableLayout; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | DD35D92D201B57C80049ED03 /* CampcotCollectionView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CampcotCollectionView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | DD35D930201B57C80049ED03 /* CampcotCollectionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CampcotCollectionView.h; sourceTree = ""; }; 35 | DD35D931201B57C80049ED03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | DD35D936201B57C80049ED03 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | DD35D93B201B57C80049ED03 /* CampcotCollectionViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CampcotCollectionViewTests.swift; sourceTree = ""; }; 38 | DD35D93D201B57C80049ED03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | DD35D949201B5E280049ED03 /* ExpandedLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandedLayout.swift; sourceTree = ""; }; 40 | DD35D987201B66B40049ED03 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 41 | DD35D988201B66B40049ED03 /* CampcotCollectionView.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CampcotCollectionView.podspec; sourceTree = ""; }; 42 | DD35D989201B66B40049ED03 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; tabWidth = 3; }; 43 | DDC6199E2369C68100B6981E /* ContentSizeAdjustmentBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizeAdjustmentBehavior.swift; sourceTree = ""; }; 44 | DDFB5C7F201F451E00F8E164 /* CollapsedLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsedLayout.swift; sourceTree = ""; }; 45 | DDFB5C81201F522B00F8E164 /* CampcotCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CampcotCollectionView.swift; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | DD35D929201B57C80049ED03 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | DD35D933201B57C80049ED03 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | DD35D937201B57C80049ED03 /* CampcotCollectionView.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | DD35D923201B57C80049ED03 = { 68 | isa = PBXGroup; 69 | children = ( 70 | DD35D986201B66A00049ED03 /* Metadata */, 71 | DD35D947201B58910049ED03 /* Source */, 72 | DD35D93A201B57C80049ED03 /* Tests */, 73 | DD35D92E201B57C80049ED03 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | DD35D92E201B57C80049ED03 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | DD35D92D201B57C80049ED03 /* CampcotCollectionView.framework */, 81 | DD35D936201B57C80049ED03 /* Tests.xctest */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | DD35D92F201B57C80049ED03 /* Supporting Files */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | DD35D930201B57C80049ED03 /* CampcotCollectionView.h */, 90 | DD35D931201B57C80049ED03 /* Info.plist */, 91 | ); 92 | path = "Supporting Files"; 93 | sourceTree = ""; 94 | }; 95 | DD35D93A201B57C80049ED03 /* Tests */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | DD35D93B201B57C80049ED03 /* CampcotCollectionViewTests.swift */, 99 | DD35D948201B59420049ED03 /* Supporting Files */, 100 | ); 101 | path = Tests; 102 | sourceTree = ""; 103 | }; 104 | DD35D947201B58910049ED03 /* Source */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | DD35D92F201B57C80049ED03 /* Supporting Files */, 108 | DDFB5C81201F522B00F8E164 /* CampcotCollectionView.swift */, 109 | DD35D949201B5E280049ED03 /* ExpandedLayout.swift */, 110 | DDFB5C7F201F451E00F8E164 /* CollapsedLayout.swift */, 111 | DDC6199E2369C68100B6981E /* ContentSizeAdjustmentBehavior.swift */, 112 | ); 113 | path = Source; 114 | sourceTree = ""; 115 | }; 116 | DD35D948201B59420049ED03 /* Supporting Files */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | DD35D93D201B57C80049ED03 /* Info.plist */, 120 | ); 121 | path = "Supporting Files"; 122 | sourceTree = ""; 123 | }; 124 | DD35D986201B66A00049ED03 /* Metadata */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | DD35D987201B66B40049ED03 /* LICENSE */, 128 | DD35D989201B66B40049ED03 /* README.md */, 129 | DD35D988201B66B40049ED03 /* CampcotCollectionView.podspec */, 130 | ); 131 | name = Metadata; 132 | sourceTree = ""; 133 | }; 134 | /* End PBXGroup section */ 135 | 136 | /* Begin PBXHeadersBuildPhase section */ 137 | DD35D92A201B57C80049ED03 /* Headers */ = { 138 | isa = PBXHeadersBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | DD35D93E201B57C80049ED03 /* CampcotCollectionView.h in Headers */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXHeadersBuildPhase section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | DD35D92C201B57C80049ED03 /* CampcotCollectionView */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = DD35D941201B57C80049ED03 /* Build configuration list for PBXNativeTarget "CampcotCollectionView" */; 151 | buildPhases = ( 152 | DD35D928201B57C80049ED03 /* Sources */, 153 | DD35D929201B57C80049ED03 /* Frameworks */, 154 | DD35D92A201B57C80049ED03 /* Headers */, 155 | DD35D92B201B57C80049ED03 /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = CampcotCollectionView; 162 | productName = ExpandableLayout; 163 | productReference = DD35D92D201B57C80049ED03 /* CampcotCollectionView.framework */; 164 | productType = "com.apple.product-type.framework"; 165 | }; 166 | DD35D935201B57C80049ED03 /* Tests */ = { 167 | isa = PBXNativeTarget; 168 | buildConfigurationList = DD35D944201B57C80049ED03 /* Build configuration list for PBXNativeTarget "Tests" */; 169 | buildPhases = ( 170 | DD35D932201B57C80049ED03 /* Sources */, 171 | DD35D933201B57C80049ED03 /* Frameworks */, 172 | DD35D934201B57C80049ED03 /* Resources */, 173 | ); 174 | buildRules = ( 175 | ); 176 | dependencies = ( 177 | DD35D939201B57C80049ED03 /* PBXTargetDependency */, 178 | ); 179 | name = Tests; 180 | productName = ExpandableLayoutTests; 181 | productReference = DD35D936201B57C80049ED03 /* Tests.xctest */; 182 | productType = "com.apple.product-type.bundle.unit-test"; 183 | }; 184 | /* End PBXNativeTarget section */ 185 | 186 | /* Begin PBXProject section */ 187 | DD35D924201B57C80049ED03 /* Project object */ = { 188 | isa = PBXProject; 189 | attributes = { 190 | LastSwiftUpdateCheck = 0920; 191 | LastUpgradeCheck = 1010; 192 | ORGANIZATIONNAME = "Touchlane LLC"; 193 | TargetAttributes = { 194 | DD35D92C201B57C80049ED03 = { 195 | CreatedOnToolsVersion = 9.2; 196 | LastSwiftMigration = 1020; 197 | ProvisioningStyle = Automatic; 198 | }; 199 | DD35D935201B57C80049ED03 = { 200 | CreatedOnToolsVersion = 9.2; 201 | LastSwiftMigration = 1020; 202 | ProvisioningStyle = Automatic; 203 | }; 204 | }; 205 | }; 206 | buildConfigurationList = DD35D927201B57C80049ED03 /* Build configuration list for PBXProject "CampcotCollectionView" */; 207 | compatibilityVersion = "Xcode 8.0"; 208 | developmentRegion = en; 209 | hasScannedForEncodings = 0; 210 | knownRegions = ( 211 | en, 212 | ); 213 | mainGroup = DD35D923201B57C80049ED03; 214 | productRefGroup = DD35D92E201B57C80049ED03 /* Products */; 215 | projectDirPath = ""; 216 | projectRoot = ""; 217 | targets = ( 218 | DD35D92C201B57C80049ED03 /* CampcotCollectionView */, 219 | DD35D935201B57C80049ED03 /* Tests */, 220 | ); 221 | }; 222 | /* End PBXProject section */ 223 | 224 | /* Begin PBXResourcesBuildPhase section */ 225 | DD35D92B201B57C80049ED03 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | DD35D98C201B66B40049ED03 /* README.md in Resources */, 230 | DD35D98B201B66B40049ED03 /* CampcotCollectionView.podspec in Resources */, 231 | DD35D98A201B66B40049ED03 /* LICENSE in Resources */, 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | DD35D934201B57C80049ED03 /* Resources */ = { 236 | isa = PBXResourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | DD35D928201B57C80049ED03 /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | DDFB5C82201F522B00F8E164 /* CampcotCollectionView.swift in Sources */, 250 | DD35D94A201B5E280049ED03 /* ExpandedLayout.swift in Sources */, 251 | DDFB5C80201F451E00F8E164 /* CollapsedLayout.swift in Sources */, 252 | DDC6199F2369C68100B6981E /* ContentSizeAdjustmentBehavior.swift in Sources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | DD35D932201B57C80049ED03 /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | DD35D93C201B57C80049ED03 /* CampcotCollectionViewTests.swift in Sources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXSourcesBuildPhase section */ 265 | 266 | /* Begin PBXTargetDependency section */ 267 | DD35D939201B57C80049ED03 /* PBXTargetDependency */ = { 268 | isa = PBXTargetDependency; 269 | target = DD35D92C201B57C80049ED03 /* CampcotCollectionView */; 270 | targetProxy = DD35D938201B57C80049ED03 /* PBXContainerItemProxy */; 271 | }; 272 | /* End PBXTargetDependency section */ 273 | 274 | /* Begin XCBuildConfiguration section */ 275 | DD35D93F201B57C80049ED03 /* Debug */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | CLANG_ANALYZER_NONNULL = YES; 280 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_COMMA = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 292 | CLANG_WARN_EMPTY_BODY = YES; 293 | CLANG_WARN_ENUM_CONVERSION = YES; 294 | CLANG_WARN_INFINITE_RECURSION = YES; 295 | CLANG_WARN_INT_CONVERSION = YES; 296 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 297 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 298 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 299 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 304 | CLANG_WARN_UNREACHABLE_CODE = YES; 305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 306 | CODE_SIGN_IDENTITY = "iPhone Developer"; 307 | COPY_PHASE_STRIP = NO; 308 | CURRENT_PROJECT_VERSION = 1; 309 | DEBUG_INFORMATION_FORMAT = dwarf; 310 | ENABLE_STRICT_OBJC_MSGSEND = YES; 311 | ENABLE_TESTABILITY = YES; 312 | GCC_C_LANGUAGE_STANDARD = gnu11; 313 | GCC_DYNAMIC_NO_PIC = NO; 314 | GCC_NO_COMMON_BLOCKS = YES; 315 | GCC_OPTIMIZATION_LEVEL = 0; 316 | GCC_PREPROCESSOR_DEFINITIONS = ( 317 | "DEBUG=1", 318 | "$(inherited)", 319 | ); 320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 322 | GCC_WARN_UNDECLARED_SELECTOR = YES; 323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 324 | GCC_WARN_UNUSED_FUNCTION = YES; 325 | GCC_WARN_UNUSED_VARIABLE = YES; 326 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 327 | MTL_ENABLE_DEBUG_INFO = YES; 328 | ONLY_ACTIVE_ARCH = YES; 329 | SDKROOT = iphoneos; 330 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 331 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 332 | VERSIONING_SYSTEM = "apple-generic"; 333 | VERSION_INFO_PREFIX = ""; 334 | }; 335 | name = Debug; 336 | }; 337 | DD35D940201B57C80049ED03 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_ANALYZER_NONNULL = YES; 342 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 343 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 344 | CLANG_CXX_LIBRARY = "libc++"; 345 | CLANG_ENABLE_MODULES = YES; 346 | CLANG_ENABLE_OBJC_ARC = YES; 347 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_COMMA = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INFINITE_RECURSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 360 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 363 | CLANG_WARN_STRICT_PROTOTYPES = YES; 364 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | CODE_SIGN_IDENTITY = "iPhone Developer"; 369 | COPY_PHASE_STRIP = NO; 370 | CURRENT_PROJECT_VERSION = 1; 371 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 372 | ENABLE_NS_ASSERTIONS = NO; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu11; 375 | GCC_NO_COMMON_BLOCKS = YES; 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 383 | MTL_ENABLE_DEBUG_INFO = NO; 384 | SDKROOT = iphoneos; 385 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 386 | VALIDATE_PRODUCT = YES; 387 | VERSIONING_SYSTEM = "apple-generic"; 388 | VERSION_INFO_PREFIX = ""; 389 | }; 390 | name = Release; 391 | }; 392 | DD35D942201B57C80049ED03 /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | CLANG_ENABLE_MODULES = YES; 396 | CODE_SIGN_IDENTITY = ""; 397 | CODE_SIGN_STYLE = Automatic; 398 | DEFINES_MODULE = YES; 399 | DYLIB_COMPATIBILITY_VERSION = 1; 400 | DYLIB_CURRENT_VERSION = 1; 401 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 402 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 403 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 404 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 405 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 406 | PRODUCT_BUNDLE_IDENTIFIER = io.touchlane.CampcotCollectionView; 407 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 408 | SKIP_INSTALL = YES; 409 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 410 | SWIFT_VERSION = 5.0; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | }; 413 | name = Debug; 414 | }; 415 | DD35D943201B57C80049ED03 /* Release */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | CLANG_ENABLE_MODULES = YES; 419 | CODE_SIGN_IDENTITY = ""; 420 | CODE_SIGN_STYLE = Automatic; 421 | DEFINES_MODULE = YES; 422 | DYLIB_COMPATIBILITY_VERSION = 1; 423 | DYLIB_CURRENT_VERSION = 1; 424 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 425 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 426 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 427 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 429 | PRODUCT_BUNDLE_IDENTIFIER = io.touchlane.CampcotCollectionView; 430 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 431 | SKIP_INSTALL = YES; 432 | SWIFT_VERSION = 5.0; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Release; 436 | }; 437 | DD35D945201B57C80049ED03 /* Debug */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 441 | CODE_SIGN_STYLE = Automatic; 442 | INFOPLIST_FILE = "Tests/Supporting Files/Info.plist"; 443 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 444 | PRODUCT_BUNDLE_IDENTIFIER = io.touchlane.CampcotCollectionViewTests; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | SWIFT_VERSION = 5.0; 447 | TARGETED_DEVICE_FAMILY = "1,2"; 448 | }; 449 | name = Debug; 450 | }; 451 | DD35D946201B57C80049ED03 /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 455 | CODE_SIGN_STYLE = Automatic; 456 | INFOPLIST_FILE = "Tests/Supporting Files/Info.plist"; 457 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 458 | PRODUCT_BUNDLE_IDENTIFIER = io.touchlane.CampcotCollectionViewTests; 459 | PRODUCT_NAME = "$(TARGET_NAME)"; 460 | SWIFT_VERSION = 5.0; 461 | TARGETED_DEVICE_FAMILY = "1,2"; 462 | }; 463 | name = Release; 464 | }; 465 | /* End XCBuildConfiguration section */ 466 | 467 | /* Begin XCConfigurationList section */ 468 | DD35D927201B57C80049ED03 /* Build configuration list for PBXProject "CampcotCollectionView" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | DD35D93F201B57C80049ED03 /* Debug */, 472 | DD35D940201B57C80049ED03 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | DD35D941201B57C80049ED03 /* Build configuration list for PBXNativeTarget "CampcotCollectionView" */ = { 478 | isa = XCConfigurationList; 479 | buildConfigurations = ( 480 | DD35D942201B57C80049ED03 /* Debug */, 481 | DD35D943201B57C80049ED03 /* Release */, 482 | ); 483 | defaultConfigurationIsVisible = 0; 484 | defaultConfigurationName = Release; 485 | }; 486 | DD35D944201B57C80049ED03 /* Build configuration list for PBXNativeTarget "Tests" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | DD35D945201B57C80049ED03 /* Debug */, 490 | DD35D946201B57C80049ED03 /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | /* End XCConfigurationList section */ 496 | }; 497 | rootObject = DD35D924201B57C80049ED03 /* Project object */; 498 | } 499 | -------------------------------------------------------------------------------- /CampcotCollectionView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CampcotCollectionView.xcodeproj/xcshareddata/xcschemes/CampcotCollectionView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 104 | 105 | 106 | 107 | 109 | 110 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /CampcotCollectionView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CampcotCollectionView.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CampcotCollectionView.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example/Assets/campcot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Assets/campcot.gif -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 86EB284F203DE4920052BFA2 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 86EB284D203DE4920052BFA2 /* Launch Screen.storyboard */; }; 11 | A3B228A923ED503D00757A28 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A3B228A823ED503D00757A28 /* Main.storyboard */; }; 12 | A3B228AC23ED525100757A28 /* StoryboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B228AB23ED525100757A28 /* StoryboardViewController.swift */; }; 13 | DD35D958201B5F390049ED03 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35D957201B5F390049ED03 /* AppDelegate.swift */; }; 14 | DD35D95A201B5F390049ED03 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35D959201B5F390049ED03 /* ViewController.swift */; }; 15 | DD35D95F201B5F390049ED03 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DD35D95E201B5F390049ED03 /* Images.xcassets */; }; 16 | DD35D980201B63F70049ED03 /* CustomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35D97E201B63F70049ED03 /* CustomHeaderView.swift */; }; 17 | DD35D981201B63F70049ED03 /* CustomCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35D97F201B63F70049ED03 /* CustomCollectionViewCell.swift */; }; 18 | DDFB5C7A201F36BC00F8E164 /* CampcotCollectionView.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = DDFB5C75201F367A00F8E164 /* CampcotCollectionView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | DDFB5C74201F367A00F8E164 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = DD35D96E201B62BA0049ED03 /* CampcotCollectionView.xcodeproj */; 25 | proxyType = 2; 26 | remoteGlobalIDString = DD35D92D201B57C80049ED03; 27 | remoteInfo = CampcotCollectionView; 28 | }; 29 | DDFB5C76201F367A00F8E164 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = DD35D96E201B62BA0049ED03 /* CampcotCollectionView.xcodeproj */; 32 | proxyType = 2; 33 | remoteGlobalIDString = DD35D936201B57C80049ED03; 34 | remoteInfo = Tests; 35 | }; 36 | DDFB5C78201F36B400F8E164 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = DD35D96E201B62BA0049ED03 /* CampcotCollectionView.xcodeproj */; 39 | proxyType = 1; 40 | remoteGlobalIDString = DD35D92C201B57C80049ED03; 41 | remoteInfo = CampcotCollectionView; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXCopyFilesBuildPhase section */ 46 | DD35D979201B62CE0049ED03 /* Copy Frameworks */ = { 47 | isa = PBXCopyFilesBuildPhase; 48 | buildActionMask = 2147483647; 49 | dstPath = ""; 50 | dstSubfolderSpec = 10; 51 | files = ( 52 | DDFB5C7A201F36BC00F8E164 /* CampcotCollectionView.framework in Copy Frameworks */, 53 | ); 54 | name = "Copy Frameworks"; 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXCopyFilesBuildPhase section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | 86EB284D203DE4920052BFA2 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; 61 | A3B228A823ED503D00757A28 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 62 | A3B228AB23ED525100757A28 /* StoryboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryboardViewController.swift; sourceTree = ""; }; 63 | DD35D954201B5F390049ED03 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | DD35D957201B5F390049ED03 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 65 | DD35D959201B5F390049ED03 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 66 | DD35D95E201B5F390049ED03 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 67 | DD35D963201B5F390049ED03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | DD35D96E201B62BA0049ED03 /* CampcotCollectionView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CampcotCollectionView.xcodeproj; path = ../CampcotCollectionView.xcodeproj; sourceTree = ""; }; 69 | DD35D97E201B63F70049ED03 /* CustomHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomHeaderView.swift; sourceTree = ""; }; 70 | DD35D97F201B63F70049ED03 /* CustomCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomCollectionViewCell.swift; sourceTree = ""; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | DD35D951201B5F390049ED03 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | DD35D94B201B5F390049ED03 = { 85 | isa = PBXGroup; 86 | children = ( 87 | DD35D956201B5F390049ED03 /* Source */, 88 | DD35D955201B5F390049ED03 /* Products */, 89 | DD35D96E201B62BA0049ED03 /* CampcotCollectionView.xcodeproj */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | DD35D955201B5F390049ED03 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | DD35D954201B5F390049ED03 /* Example.app */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | DD35D956201B5F390049ED03 /* Source */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | DD35D959201B5F390049ED03 /* ViewController.swift */, 105 | A3B228AB23ED525100757A28 /* StoryboardViewController.swift */, 106 | DD35D97E201B63F70049ED03 /* CustomHeaderView.swift */, 107 | DD35D97F201B63F70049ED03 /* CustomCollectionViewCell.swift */, 108 | DD35D957201B5F390049ED03 /* AppDelegate.swift */, 109 | DD35D95E201B5F390049ED03 /* Images.xcassets */, 110 | 86EB284D203DE4920052BFA2 /* Launch Screen.storyboard */, 111 | A3B228A823ED503D00757A28 /* Main.storyboard */, 112 | DD35D969201B5FB10049ED03 /* Supporting Files */, 113 | ); 114 | path = Source; 115 | sourceTree = ""; 116 | }; 117 | DD35D969201B5FB10049ED03 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | DD35D963201B5F390049ED03 /* Info.plist */, 121 | ); 122 | path = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | DDFB5C70201F367800F8E164 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | DDFB5C75201F367A00F8E164 /* CampcotCollectionView.framework */, 129 | DDFB5C77201F367A00F8E164 /* Tests.xctest */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | /* End PBXGroup section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | DD35D953201B5F390049ED03 /* Example */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = DD35D966201B5F390049ED03 /* Build configuration list for PBXNativeTarget "Example" */; 140 | buildPhases = ( 141 | DD35D950201B5F390049ED03 /* Sources */, 142 | DD35D951201B5F390049ED03 /* Frameworks */, 143 | DD35D952201B5F390049ED03 /* Resources */, 144 | DD35D979201B62CE0049ED03 /* Copy Frameworks */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | DDFB5C79201F36B400F8E164 /* PBXTargetDependency */, 150 | ); 151 | name = Example; 152 | productName = Example; 153 | productReference = DD35D954201B5F390049ED03 /* Example.app */; 154 | productType = "com.apple.product-type.application"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | DD35D94C201B5F390049ED03 /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastSwiftUpdateCheck = 0920; 163 | LastUpgradeCheck = 1010; 164 | ORGANIZATIONNAME = "Touchlane LLC"; 165 | TargetAttributes = { 166 | DD35D953201B5F390049ED03 = { 167 | CreatedOnToolsVersion = 9.2; 168 | LastSwiftMigration = 1020; 169 | ProvisioningStyle = Automatic; 170 | }; 171 | }; 172 | }; 173 | buildConfigurationList = DD35D94F201B5F390049ED03 /* Build configuration list for PBXProject "Example" */; 174 | compatibilityVersion = "Xcode 8.0"; 175 | developmentRegion = en; 176 | hasScannedForEncodings = 0; 177 | knownRegions = ( 178 | en, 179 | Base, 180 | ); 181 | mainGroup = DD35D94B201B5F390049ED03; 182 | productRefGroup = DD35D955201B5F390049ED03 /* Products */; 183 | projectDirPath = ""; 184 | projectReferences = ( 185 | { 186 | ProductGroup = DDFB5C70201F367800F8E164 /* Products */; 187 | ProjectRef = DD35D96E201B62BA0049ED03 /* CampcotCollectionView.xcodeproj */; 188 | }, 189 | ); 190 | projectRoot = ""; 191 | targets = ( 192 | DD35D953201B5F390049ED03 /* Example */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXReferenceProxy section */ 198 | DDFB5C75201F367A00F8E164 /* CampcotCollectionView.framework */ = { 199 | isa = PBXReferenceProxy; 200 | fileType = wrapper.framework; 201 | path = CampcotCollectionView.framework; 202 | remoteRef = DDFB5C74201F367A00F8E164 /* PBXContainerItemProxy */; 203 | sourceTree = BUILT_PRODUCTS_DIR; 204 | }; 205 | DDFB5C77201F367A00F8E164 /* Tests.xctest */ = { 206 | isa = PBXReferenceProxy; 207 | fileType = wrapper.cfbundle; 208 | path = Tests.xctest; 209 | remoteRef = DDFB5C76201F367A00F8E164 /* PBXContainerItemProxy */; 210 | sourceTree = BUILT_PRODUCTS_DIR; 211 | }; 212 | /* End PBXReferenceProxy section */ 213 | 214 | /* Begin PBXResourcesBuildPhase section */ 215 | DD35D952201B5F390049ED03 /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 86EB284F203DE4920052BFA2 /* Launch Screen.storyboard in Resources */, 220 | DD35D95F201B5F390049ED03 /* Images.xcassets in Resources */, 221 | A3B228A923ED503D00757A28 /* Main.storyboard in Resources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXResourcesBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | DD35D950201B5F390049ED03 /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | A3B228AC23ED525100757A28 /* StoryboardViewController.swift in Sources */, 233 | DD35D95A201B5F390049ED03 /* ViewController.swift in Sources */, 234 | DD35D981201B63F70049ED03 /* CustomCollectionViewCell.swift in Sources */, 235 | DD35D980201B63F70049ED03 /* CustomHeaderView.swift in Sources */, 236 | DD35D958201B5F390049ED03 /* AppDelegate.swift in Sources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXSourcesBuildPhase section */ 241 | 242 | /* Begin PBXTargetDependency section */ 243 | DDFB5C79201F36B400F8E164 /* PBXTargetDependency */ = { 244 | isa = PBXTargetDependency; 245 | name = CampcotCollectionView; 246 | targetProxy = DDFB5C78201F36B400F8E164 /* PBXContainerItemProxy */; 247 | }; 248 | /* End PBXTargetDependency section */ 249 | 250 | /* Begin XCBuildConfiguration section */ 251 | DD35D964201B5F390049ED03 /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_COMMA = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 266 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 267 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 268 | CLANG_WARN_EMPTY_BODY = YES; 269 | CLANG_WARN_ENUM_CONVERSION = YES; 270 | CLANG_WARN_INFINITE_RECURSION = YES; 271 | CLANG_WARN_INT_CONVERSION = YES; 272 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 274 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 276 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 277 | CLANG_WARN_STRICT_PROTOTYPES = YES; 278 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 279 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 280 | CLANG_WARN_UNREACHABLE_CODE = YES; 281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 282 | CODE_SIGN_IDENTITY = "iPhone Developer"; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = dwarf; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | ENABLE_TESTABILITY = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu11; 288 | GCC_DYNAMIC_NO_PIC = NO; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_OPTIMIZATION_LEVEL = 0; 291 | GCC_PREPROCESSOR_DEFINITIONS = ( 292 | "DEBUG=1", 293 | "$(inherited)", 294 | ); 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 302 | MTL_ENABLE_DEBUG_INFO = YES; 303 | ONLY_ACTIVE_ARCH = YES; 304 | SDKROOT = iphoneos; 305 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 306 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 307 | }; 308 | name = Debug; 309 | }; 310 | DD35D965201B5F390049ED03 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 317 | CLANG_CXX_LIBRARY = "libc++"; 318 | CLANG_ENABLE_MODULES = YES; 319 | CLANG_ENABLE_OBJC_ARC = YES; 320 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 321 | CLANG_WARN_BOOL_CONVERSION = YES; 322 | CLANG_WARN_COMMA = YES; 323 | CLANG_WARN_CONSTANT_CONVERSION = YES; 324 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 326 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 327 | CLANG_WARN_EMPTY_BODY = YES; 328 | CLANG_WARN_ENUM_CONVERSION = YES; 329 | CLANG_WARN_INFINITE_RECURSION = YES; 330 | CLANG_WARN_INT_CONVERSION = YES; 331 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 336 | CLANG_WARN_STRICT_PROTOTYPES = YES; 337 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 338 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | CODE_SIGN_IDENTITY = "iPhone Developer"; 342 | COPY_PHASE_STRIP = NO; 343 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 344 | ENABLE_NS_ASSERTIONS = NO; 345 | ENABLE_STRICT_OBJC_MSGSEND = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu11; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 355 | MTL_ENABLE_DEBUG_INFO = NO; 356 | SDKROOT = iphoneos; 357 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 358 | VALIDATE_PRODUCT = YES; 359 | }; 360 | name = Release; 361 | }; 362 | DD35D967201B5F390049ED03 /* Debug */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 366 | CODE_SIGN_STYLE = Automatic; 367 | DEVELOPMENT_TEAM = NPGMYNXF85; 368 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = io.touchlane.Example; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SWIFT_VERSION = 5.0; 373 | TARGETED_DEVICE_FAMILY = "1,2"; 374 | }; 375 | name = Debug; 376 | }; 377 | DD35D968201B5F390049ED03 /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 381 | CODE_SIGN_STYLE = Automatic; 382 | DEVELOPMENT_TEAM = NPGMYNXF85; 383 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 384 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 385 | PRODUCT_BUNDLE_IDENTIFIER = io.touchlane.Example; 386 | PRODUCT_NAME = "$(TARGET_NAME)"; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Release; 391 | }; 392 | /* End XCBuildConfiguration section */ 393 | 394 | /* Begin XCConfigurationList section */ 395 | DD35D94F201B5F390049ED03 /* Build configuration list for PBXProject "Example" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | DD35D964201B5F390049ED03 /* Debug */, 399 | DD35D965201B5F390049ED03 /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | defaultConfigurationName = Release; 403 | }; 404 | DD35D966201B5F390049ED03 /* Build configuration list for PBXNativeTarget "Example" */ = { 405 | isa = XCConfigurationList; 406 | buildConfigurations = ( 407 | DD35D967201B5F390049ED03 /* Debug */, 408 | DD35D968201B5F390049ED03 /* Release */, 409 | ); 410 | defaultConfigurationIsVisible = 0; 411 | defaultConfigurationName = Release; 412 | }; 413 | /* End XCConfigurationList section */ 414 | }; 415 | rootObject = DD35D94C201B5F390049ED03 /* Project object */; 416 | } 417 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Example/Source/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Vadim Morozov on 1/26/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | let rootVC = ViewController() 15 | 16 | func application(_ application: UIApplication, 17 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | self.window = UIWindow(frame: UIScreen.main.bounds) 19 | self.window?.rootViewController = rootVC 20 | self.window?.makeKeyAndVisible() 21 | return true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/Source/CustomCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomCollectionViewCell.swift 3 | // Example 4 | // 5 | // Created by Panda Systems on 1/8/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomCollectionViewCell: UICollectionViewCell { 12 | static let reuseIdentifier = "CustomCell" 13 | 14 | private let internalBackgroundColor = UIColor(red: 61 / 255, green: 86 / 255, blue: 166 / 255, alpha: 1) 15 | private let textLabel = UILabel() 16 | 17 | var text: String? { 18 | didSet { 19 | self.textLabel.text = text 20 | } 21 | } 22 | 23 | override init(frame: CGRect) { 24 | super.init(frame: .zero) 25 | self.commonInit() 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | super.init(coder: aDecoder) 30 | self.commonInit() 31 | } 32 | 33 | private func commonInit() { 34 | self.clipsToBounds = true 35 | self.backgroundColor = self.internalBackgroundColor 36 | self.layer.cornerRadius = 10 37 | self.textLabel.font = UIFont.boldSystemFont(ofSize: 24) 38 | self.textLabel.translatesAutoresizingMaskIntoConstraints = false 39 | self.textLabel.textColor = .white 40 | self.addSubview(self.textLabel) 41 | self.activateTextLabelConstraints(view: self.textLabel, anchorView: self) 42 | } 43 | 44 | override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 45 | super.apply(layoutAttributes) 46 | self.layoutIfNeeded() 47 | } 48 | 49 | private func activateTextLabelConstraints(view: UIView, anchorView: UIView) { 50 | NSLayoutConstraint.activate([ 51 | view.centerXAnchor.constraint(equalTo: anchorView.centerXAnchor), 52 | view.centerYAnchor.constraint(equalTo: anchorView.centerYAnchor) 53 | ]) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/Source/CustomHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomHeaderView.swift 3 | // Example 4 | // 5 | // Created by Panda Systems on 1/8/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CustomHeaderViewDelegate: class { 12 | func selectSection(section: Int) 13 | } 14 | 15 | class CustomHeaderView: UICollectionReusableView { 16 | static let reuseIdentifier = "CustomHeaderView" 17 | private let internalBackgroundColor = UIColor.purple 18 | private let textLeadingOffset: CGFloat = 20 19 | private let textLabel = UILabel() 20 | 21 | weak var delegate: CustomHeaderViewDelegate? 22 | var section: Int? 23 | var text: String? { 24 | didSet { 25 | self.textLabel.text = text 26 | } 27 | } 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: .zero) 31 | self.commonInit() 32 | } 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | super.init(coder: aDecoder) 36 | self.commonInit() 37 | } 38 | 39 | private func commonInit() { 40 | self.backgroundColor = self.internalBackgroundColor 41 | self.textLabel.translatesAutoresizingMaskIntoConstraints = false 42 | self.textLabel.textColor = .white 43 | self.textLabel.textAlignment = .center 44 | self.textLabel.font = UIFont.boldSystemFont(ofSize: 24) 45 | self.addSubview(self.textLabel) 46 | self.activateTextLabelConstraints(view: self.textLabel, anchorView: self) 47 | let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapOnView(sender:))) 48 | self.addGestureRecognizer(tapRecognizer) 49 | } 50 | 51 | override func prepareForReuse() { 52 | super.prepareForReuse() 53 | self.text = nil 54 | self.section = nil 55 | } 56 | 57 | override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 58 | super.apply(layoutAttributes) 59 | self.layoutIfNeeded() 60 | } 61 | } 62 | 63 | private typealias CustomHeaderViewPrivate = CustomHeaderView 64 | private extension CustomHeaderViewPrivate { 65 | func activateTextLabelConstraints(view: UIView, anchorView: UIView) { 66 | NSLayoutConstraint.activate([ 67 | view.centerXAnchor.constraint(equalTo: anchorView.centerXAnchor), 68 | view.centerYAnchor.constraint(equalTo: anchorView.centerYAnchor) 69 | ]) 70 | } 71 | 72 | @objc func tapOnView(sender: UIGestureRecognizer) { 73 | guard let section = self.section else { 74 | return 75 | } 76 | self.delegate?.selectSection(section: section) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "Icon-App-20x20@1x.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-29x29@1x.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@2x-1.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-40x40@1x.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@2x-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-76x76@1x.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-83.5x83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "ItunesArtwork@2x.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "24x24", 113 | "idiom" : "watch", 114 | "filename" : "Icon-24@2x.png", 115 | "scale" : "2x", 116 | "role" : "notificationCenter", 117 | "subtype" : "38mm" 118 | }, 119 | { 120 | "size" : "27.5x27.5", 121 | "idiom" : "watch", 122 | "filename" : "Icon-27.5@2x.png", 123 | "scale" : "2x", 124 | "role" : "notificationCenter", 125 | "subtype" : "42mm" 126 | }, 127 | { 128 | "size" : "29x29", 129 | "idiom" : "watch", 130 | "filename" : "Icon-29@2x.png", 131 | "role" : "companionSettings", 132 | "scale" : "2x" 133 | }, 134 | { 135 | "size" : "29x29", 136 | "idiom" : "watch", 137 | "filename" : "Icon-29@3x.png", 138 | "role" : "companionSettings", 139 | "scale" : "3x" 140 | }, 141 | { 142 | "size" : "40x40", 143 | "idiom" : "watch", 144 | "filename" : "Icon-40@2x.png", 145 | "scale" : "2x", 146 | "role" : "appLauncher", 147 | "subtype" : "38mm" 148 | }, 149 | { 150 | "size" : "44x44", 151 | "idiom" : "watch", 152 | "scale" : "2x", 153 | "role" : "appLauncher", 154 | "subtype" : "40mm" 155 | }, 156 | { 157 | "size" : "50x50", 158 | "idiom" : "watch", 159 | "scale" : "2x", 160 | "role" : "appLauncher", 161 | "subtype" : "44mm" 162 | }, 163 | { 164 | "size" : "86x86", 165 | "idiom" : "watch", 166 | "filename" : "Icon-86@2x.png", 167 | "scale" : "2x", 168 | "role" : "quickLook", 169 | "subtype" : "38mm" 170 | }, 171 | { 172 | "size" : "98x98", 173 | "idiom" : "watch", 174 | "filename" : "Icon-98@2x.png", 175 | "scale" : "2x", 176 | "role" : "quickLook", 177 | "subtype" : "42mm" 178 | }, 179 | { 180 | "size" : "108x108", 181 | "idiom" : "watch", 182 | "scale" : "2x", 183 | "role" : "quickLook", 184 | "subtype" : "44mm" 185 | }, 186 | { 187 | "idiom" : "watch-marketing", 188 | "size" : "1024x1024", 189 | "scale" : "1x" 190 | }, 191 | { 192 | "size" : "44x44", 193 | "idiom" : "watch", 194 | "filename" : "Icon-44@2x.png", 195 | "scale" : "2x", 196 | "role" : "longLook", 197 | "subtype" : "42mm" 198 | } 199 | ], 200 | "info" : { 201 | "version" : 1, 202 | "author" : "xcode" 203 | }, 204 | "properties" : { 205 | "pre-rendered" : true 206 | } 207 | } -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-24@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-27.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-27.5@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-44@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-44@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-86@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-98@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo.pdf", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/logo.imageset/logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/logo.imageset/logo.pdf -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/logo_splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_splash.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Source/Images.xcassets/logo_splash.imageset/logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlane/CampcotCollectionView/0540049dad4ce4e07c214671465c55c5eae82da3/Example/Source/Images.xcassets/logo_splash.imageset/logo_splash.png -------------------------------------------------------------------------------- /Example/Source/Launch Screen.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/Source/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 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Example/Source/StoryboardViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardViewController.swift 3 | // Example 4 | // 5 | // Created by Alex Yanski on 2/7/20. 6 | // Copyright © 2020 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CampcotCollectionView 11 | 12 | class StoryboardViewController: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: CampcotCollectionView! 15 | 16 | let itemsInRow = 2 17 | var itemsInSection: [Int: Int] = [:] 18 | 19 | override var preferredStatusBarStyle: UIStatusBarStyle { 20 | return .default 21 | } 22 | } 23 | 24 | extension StoryboardViewController: UICollectionViewDataSource { 25 | func numberOfSections(in collectionView: UICollectionView) -> Int { 26 | return 20 27 | } 28 | 29 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 30 | if let numberOfItems = itemsInSection[section] { 31 | return numberOfItems 32 | } 33 | let numberOfItems = Int.random(in: 1...6) 34 | itemsInSection[section] = numberOfItems 35 | return numberOfItems 36 | } 37 | 38 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 39 | let cell = collectionView.dequeueReusableCell( 40 | withReuseIdentifier: CustomCollectionViewCell.reuseIdentifier, 41 | for: indexPath) as! CustomCollectionViewCell 42 | cell.text = "\(indexPath.section):\(indexPath.row)" 43 | return cell 44 | } 45 | 46 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 47 | let view = collectionView.dequeueReusableSupplementaryView( 48 | ofKind: kind, 49 | withReuseIdentifier: CustomHeaderView.reuseIdentifier, 50 | for: indexPath) as! CustomHeaderView 51 | view.section = indexPath.section 52 | view.text = "section: \(indexPath.section)" 53 | view.delegate = self 54 | return view 55 | } 56 | } 57 | 58 | extension StoryboardViewController: UICollectionViewDelegateFlowLayout { 59 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 60 | let interitemSpacing = self.collectionView.minimumInteritemSpacing * CGFloat(itemsInRow - 1) 61 | let totalSpacing = collectionView.bounds.width - self.collectionView.sectionInset.left - self.collectionView.sectionInset.right - interitemSpacing 62 | let width = totalSpacing / CGFloat(itemsInRow) 63 | return CGSize(width: width, height: width) 64 | } 65 | 66 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 67 | return CGSize(width: UIScreen.main.bounds.size.width, height: 60) 68 | } 69 | } 70 | 71 | extension StoryboardViewController: CustomHeaderViewDelegate { 72 | func selectSection(section: Int) { 73 | self.collectionView.toggle(to: section, animated: true) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Example/Source/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | CampcotCollectionView 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | Launch Screen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleDefault 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/Source/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by Panda Systems on 12/19/17. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CampcotCollectionView 11 | 12 | class ViewController: UIViewController { 13 | let collectionView = CampcotCollectionView() 14 | 15 | let interitemSpacing: CGFloat = 10 16 | let lineSpacing: CGFloat = 10 17 | let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 18 | 19 | let backgroundColor = UIColor(red: 189 / 255, green: 195 / 255, blue: 199 / 255, alpha: 1) 20 | 21 | let itemsInRow = 2 22 | var itemsInSection: [Int: Int] = [:] 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | self.view.backgroundColor = backgroundColor 27 | self.collectionView.backgroundColor = backgroundColor 28 | self.collectionView.clipsToBounds = true 29 | self.collectionView.sectionInset = sectionInsets 30 | self.collectionView.minimumSectionSpacing = 1 31 | self.collectionView.minimumInteritemSpacing = interitemSpacing 32 | self.collectionView.minimumLineSpacing = lineSpacing 33 | self.collectionView.sectionHeadersPinToVisibleBounds = true 34 | self.collectionView.register( 35 | CustomCollectionViewCell.self, 36 | forCellWithReuseIdentifier: CustomCollectionViewCell.reuseIdentifier 37 | ) 38 | self.collectionView.register( 39 | CustomHeaderView.self, 40 | forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, 41 | withReuseIdentifier: CustomHeaderView.reuseIdentifier 42 | ) 43 | self.collectionView.delegate = self 44 | self.collectionView.dataSource = self 45 | self.collectionView.translatesAutoresizingMaskIntoConstraints = false 46 | self.view.addSubview(self.collectionView) 47 | self.activateCollectionViewConstraints(view: self.collectionView, anchorView: self.view) 48 | } 49 | 50 | override var preferredStatusBarStyle: UIStatusBarStyle { 51 | return .default 52 | } 53 | 54 | private func activateCollectionViewConstraints(view: UIView, anchorView: UIView) { 55 | NSLayoutConstraint.activate([ 56 | view.leadingAnchor.constraint(equalTo: anchorView.leadingAnchor), 57 | view.trailingAnchor.constraint(equalTo: anchorView.trailingAnchor), 58 | view.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor), 59 | view.bottomAnchor.constraint(equalTo: self.bottomLayoutGuide.topAnchor) 60 | ]) 61 | } 62 | } 63 | 64 | extension ViewController: UICollectionViewDataSource { 65 | func numberOfSections(in collectionView: UICollectionView) -> Int { 66 | return 20 67 | } 68 | 69 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 70 | if let numberOfItems = itemsInSection[section] { 71 | return numberOfItems 72 | } 73 | let numberOfItems = Int.random(in: 1...6) 74 | itemsInSection[section] = numberOfItems 75 | return numberOfItems 76 | } 77 | 78 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 79 | let cell = collectionView.dequeueReusableCell( 80 | withReuseIdentifier: CustomCollectionViewCell.reuseIdentifier, 81 | for: indexPath) as! CustomCollectionViewCell 82 | cell.text = "\(indexPath.section):\(indexPath.row)" 83 | return cell 84 | } 85 | 86 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 87 | let view = collectionView.dequeueReusableSupplementaryView( 88 | ofKind: kind, 89 | withReuseIdentifier: CustomHeaderView.reuseIdentifier, 90 | for: indexPath) as! CustomHeaderView 91 | view.section = indexPath.section 92 | view.text = "section: \(indexPath.section)" 93 | view.delegate = self 94 | return view 95 | } 96 | } 97 | 98 | extension ViewController: UICollectionViewDelegateFlowLayout { 99 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 100 | let width = (collectionView.bounds.width - sectionInsets.left - sectionInsets.right - interitemSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) 101 | return CGSize(width: width, height: width) 102 | } 103 | 104 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 105 | return CGSize(width: UIScreen.main.bounds.size.width, height: 60) 106 | } 107 | } 108 | 109 | extension ViewController: CustomHeaderViewDelegate { 110 | func selectSection(section: Int) { 111 | self.collectionView.toggle(to: section, animated: true) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Touchlane LLC tech@touchlane.com 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 | ![LOGO](https://github.com/touchlane/CampcotCollectionView/blob/master/Assets/logo.svg) 2 | 3 | ![Language](https://img.shields.io/badge/swift-5.0-orange.svg) 4 | [![Build Status](https://travis-ci.org/touchlane/CampcotCollectionView.svg?branch=master)](https://travis-ci.org/touchlane/CampcotCollectionView) 5 | [![codecov](https://codecov.io/gh/touchlane/CampcotCollectionView/branch/master/graph/badge.svg)](https://codecov.io/gh/touchlane/CampcotCollectionView) 6 | [![Version](https://img.shields.io/cocoapods/v/CampcotCollectionView.svg?style=flat)](http://cocoapods.org/pods/CampcotCollectionView) 7 | [![License](https://img.shields.io/cocoapods/l/CampcotCollectionView.svg?style=flat)](http://cocoapods.org/pods/CampcotCollectionView) 8 | [![Platform](https://img.shields.io/cocoapods/p/CampcotCollectionView.svg?style=flat)](http://cocoapods.org/pods/CampcotCollectionView) 9 | 10 | This library provides a custom `UICollectionView` that allows to expand and collapse sections. Provides a simple API to manage collection view appearance. 11 | 12 | ![CampcotCollectionView](Example/Assets/campcot.gif) 13 | 14 | # Requirements 15 | 16 | * iOS 9.0+ 17 | * Xcode 10.2+ 18 | * Swift 5.0+ 19 | 20 | # Installation 21 | 22 | ## CocoaPods 23 | 24 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 25 | 26 | ```$ gem install cocoapods``` 27 | 28 | To integrate CampcotCollectionView into your Xcode project using CocoaPods, specify it in your ```Podfile```: 29 | 30 | ```ruby 31 | source 'https://github.com/CocoaPods/Specs.git' 32 | platform :ios, '9.0' 33 | use_frameworks! 34 | 35 | target '' do 36 | pod 'CampcotCollectionView' 37 | end 38 | ``` 39 | 40 | Then, run the following command: 41 | 42 | ```$ pod install``` 43 | 44 | # Usage 45 | 46 | ### Manual Setup 47 | 48 | ```swift 49 | import CampcotCollectionView 50 | ``` 51 | 52 | 1. Create CollectionView 53 | ```swift 54 | let campcotCollectionView = CampcotCollectionView() 55 | ``` 56 | 2. Add `campcotCollectionView` to view hierarchy. 57 | 3. Call `toggle` method on `campcotCollectionView`. 58 | ```swift 59 | public func toggle(to section: Int, 60 | offsetCorrection: CGFloat = default, 61 | animated: Bool, 62 | completion: ((Bool) -> Void)? = default) 63 | ``` 64 | ### Storyboard Setup 65 | 66 | 1. Add UICollectionView to your Storyboard. 67 | 2. In `Identity Inspector` set the `Class` property to `CampcotCollectionView`. 68 | 3. Open `Attributes Inspector` and set the `Layout` property to `Custom`. Set `Class` property to either `ExpandedLayout` or `CollapsedLayout`. 69 | 4. Create outlet for your collectionView. 70 | 5. Set datasource and delegate for collectionView. 71 | 5. Set the settings for collectionView in `Attributes Inspector` or manualy. 72 | 73 | # Documentation 74 | 75 | ### CampcotCollectionView 76 | 77 | A Boolean value that determines whether the sections are expanded. 78 | ```swift 79 | public var isExpanded: Bool { get } 80 | ``` 81 | 82 | Expands all the sections. Pins a section at index `section` to the top of view bounds. 83 | `offsetCorrection` - the offset for pinned section from the top. Default value of `offsetCorrection` is `0`. 84 | `animated` - if `true` expands sections with animation. 85 | `completion` - callback for animation. 86 | ```swift 87 | public func expand(from section: Int, 88 | offsetCorrection: CGFloat = default, 89 | animated: Bool, 90 | completion: ((Bool) -> Void)? = default) 91 | ``` 92 | 93 | Collapses all the sections. Pins a section at index `section` to the top of view bounds. 94 | `offsetCorrection` - the offset for pinned section from the top. Default value of `offsetCorrection` is `0`. 95 | `animated` - if `true` collapses sections with animation. 96 | `completion` - callback for animation. 97 | ```swift 98 | public func collapse(to section: Int, 99 | offsetCorrection: CGFloat = default, 100 | animated: Bool, 101 | completion: ((Bool) -> Void)? = default) 102 | ``` 103 | 104 | Toggles current state from collapsed to expaned and vise versa. Pins a section at index `section` to the top of view bounds. 105 | `offsetCorrection` - the offset for pinned section from the top. Default value of `offsetCorrection` is `0`. 106 | `animated` - if `true` toggles sections with animation. 107 | `completion` - callback for animation. 108 | ```swift 109 | public func toggle(to section: Int, 110 | offsetCorrection: CGFloat = default, 111 | animated: Bool, 112 | completion: ((Bool) -> Void)? = default) 113 | ``` 114 | ___ 115 | -------------------------------------------------------------------------------- /Source/CampcotCollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CampcotCollectionView.swift 3 | // CampcotCollectionView 4 | // 5 | // Created by Vadim Morozov on 1/29/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | public class CampcotCollectionView: UICollectionView { 10 | private var expandedLayout: ExpandedLayout! 11 | private var collapsedLayout: CollapsedLayout! 12 | 13 | /// A Boolean value that determines whether the sections are expanded. 14 | public var isExpanded: Bool { 15 | return self.collectionViewLayout === self.expandedLayout 16 | } 17 | 18 | /// Space between section headers in collapsed state. 19 | @IBInspectable 20 | public var minimumSectionSpacing: CGFloat = 0 { 21 | didSet { 22 | self.expandedLayout.minimumSectionSpacing = minimumSectionSpacing 23 | self.collapsedLayout.minimumSectionSpacing = minimumSectionSpacing 24 | } 25 | } 26 | 27 | /// Layout minimum interitem spaceign. 28 | @IBInspectable 29 | public var minimumInteritemSpacing: CGFloat = 0 { 30 | didSet { 31 | self.expandedLayout.minimumInteritemSpacing = minimumInteritemSpacing 32 | self.collapsedLayout.minimumInteritemSpacing = minimumInteritemSpacing 33 | } 34 | } 35 | 36 | /// Layout minimum line spacing. 37 | @IBInspectable 38 | public var minimumLineSpacing: CGFloat = 0 { 39 | didSet { 40 | self.expandedLayout.minimumLineSpacing = minimumLineSpacing 41 | self.collapsedLayout.minimumLineSpacing = minimumLineSpacing 42 | } 43 | } 44 | 45 | /// Layout section inset. 46 | public var sectionInset = UIEdgeInsets.zero { 47 | didSet { 48 | self.expandedLayout.sectionInset = sectionInset 49 | self.collapsedLayout.sectionInset = sectionInset 50 | } 51 | } 52 | 53 | @IBInspectable 54 | private var topInset: CGFloat = 0 { 55 | didSet { 56 | sectionInset.top = topInset 57 | } 58 | } 59 | 60 | @IBInspectable 61 | private var bottomInset: CGFloat = 0 { 62 | didSet { 63 | sectionInset.bottom = bottomInset 64 | } 65 | } 66 | 67 | @IBInspectable 68 | private var leftInset: CGFloat = 0 { 69 | didSet { 70 | sectionInset.left = leftInset 71 | } 72 | } 73 | 74 | @IBInspectable 75 | private var rightInset: CGFloat = 0 { 76 | didSet { 77 | sectionInset.right = rightInset 78 | } 79 | } 80 | 81 | /// Layout section headers pin to visible bounds. 82 | @IBInspectable 83 | public var sectionHeadersPinToVisibleBounds: Bool = false { 84 | didSet { 85 | self.expandedLayout.sectionHeadersPinToVisibleBounds = sectionHeadersPinToVisibleBounds 86 | self.collapsedLayout.sectionHeadersPinToVisibleBounds = sectionHeadersPinToVisibleBounds 87 | } 88 | } 89 | 90 | /// Content size calculation rules. 91 | public var contentSizeAdjustmentBehavior: ContentSizeAdjustmentBehavior = .normal { 92 | didSet { 93 | self.expandedLayout.contentSizeAdjustmentBehavior = contentSizeAdjustmentBehavior 94 | self.collapsedLayout.contentSizeAdjustmentBehavior = contentSizeAdjustmentBehavior 95 | } 96 | } 97 | 98 | public init(isExpanded: Bool = true) { 99 | expandedLayout = ExpandedLayout() 100 | collapsedLayout = CollapsedLayout() 101 | super.init(frame: .zero, collectionViewLayout: isExpanded ? self.expandedLayout : self.collapsedLayout) 102 | } 103 | 104 | required public init?(coder aDecoder: NSCoder) { 105 | super.init(coder: aDecoder) 106 | if let expandedLayout = self.collectionViewLayout as? ExpandedLayout { 107 | /// When collectionViewLayout is expanded 108 | self.expandedLayout = expandedLayout 109 | self.collapsedLayout = CollapsedLayout() 110 | } else if let collapsedLayout = self.collectionViewLayout as? CollapsedLayout { 111 | /// When collectionViewLayout is collapsed 112 | self.expandedLayout = ExpandedLayout() 113 | self.collapsedLayout = collapsedLayout 114 | } else { 115 | /// By default, if collectionViewLayout was not set neither expanded nor collapsed 116 | self.expandedLayout = ExpandedLayout() 117 | self.collapsedLayout = CollapsedLayout() 118 | 119 | self.collectionViewLayout = self.expandedLayout /// Default layout 120 | } 121 | } 122 | 123 | /// Expand all sections and pin section from params to top. 124 | public func expand(from section: Int, 125 | offsetCorrection: CGFloat = 0, 126 | animated: Bool, 127 | completion: ((Bool) -> Void)? = nil) { 128 | guard !self.isExpanded else { 129 | return 130 | } 131 | self.expandedLayout.targetSection = section 132 | self.expandedLayout.offsetCorrection = offsetCorrection 133 | self.collapsedLayout.targetSection = section 134 | self.collapsedLayout.offsetCorrection = offsetCorrection 135 | self.setCollectionViewLayout(self.expandedLayout, animated: animated, completion: { completed in 136 | DispatchQueue.main.async(execute: { 137 | completion?(completed) 138 | }) 139 | }) 140 | } 141 | 142 | /// Collapse all sections and pin section from params to top. 143 | public func collapse(to section: Int, 144 | offsetCorrection: CGFloat = 0, 145 | animated: Bool, 146 | completion: ((Bool) -> Void)? = nil) { 147 | guard self.isExpanded else { 148 | return 149 | } 150 | self.expandedLayout.targetSection = section 151 | self.expandedLayout.offsetCorrection = offsetCorrection 152 | self.collapsedLayout.targetSection = section 153 | self.collapsedLayout.offsetCorrection = offsetCorrection 154 | self.setCollectionViewLayout(self.collapsedLayout, animated: animated, completion: { completed in 155 | DispatchQueue.main.async(execute: { 156 | completion?(completed) 157 | }) 158 | }) 159 | } 160 | 161 | /// Change sections mode to opposite. 162 | public func toggle(to section: Int, 163 | offsetCorrection: CGFloat = 0, 164 | animated: Bool, 165 | completion: ((Bool) -> Void)? = nil) { 166 | if self.isExpanded { 167 | self.collapse(to: section, 168 | offsetCorrection: offsetCorrection, 169 | animated: animated, 170 | completion: completion) 171 | } 172 | else { 173 | self.expand(from: section, 174 | offsetCorrection: offsetCorrection, 175 | animated: animated, 176 | completion: completion) 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Source/CollapsedLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollapsedLayout.swift 3 | // CampcotCollectionView 4 | // 5 | // Created by Vadim Morozov on 1/29/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | public class CollapsedLayout: UICollectionViewFlowLayout { 10 | var targetSection: Int = 0 11 | var offsetCorrection: CGFloat = 0 12 | var minimumSectionSpacing: CGFloat = 0 { 13 | didSet { 14 | self.invalidateLayout() 15 | } 16 | } 17 | 18 | private var contentHeight: CGFloat = 0 19 | private var contentWidth: CGFloat { 20 | guard let collectionView = self.collectionView else { 21 | return 0 22 | } 23 | let insets = collectionView.contentInset 24 | return collectionView.bounds.width - (insets.left + insets.right) 25 | } 26 | 27 | public var contentSizeAdjustmentBehavior: ContentSizeAdjustmentBehavior = .normal { 28 | didSet { 29 | self.invalidateLayout() 30 | } 31 | } 32 | 33 | override public var collectionViewContentSize: CGSize { 34 | switch self.contentSizeAdjustmentBehavior { 35 | case .normal: 36 | return CGSize(width: self.contentWidth, height: self.contentHeight) 37 | case .fitHeight(let adjustInsets): 38 | guard let collectionView = self.collectionView else { 39 | return CGSize(width: self.contentWidth, height: self.contentHeight) 40 | } 41 | var adjustedContentHeight = collectionView.bounds.height 42 | if adjustInsets.contains(.top) { 43 | adjustedContentHeight -= collectionView.contentInset.top 44 | } 45 | if adjustInsets.contains(.bottom) { 46 | adjustedContentHeight -= collectionView.contentInset.bottom 47 | } 48 | let contentHeight = max(self.contentHeight, adjustedContentHeight) 49 | return CGSize(width: self.contentWidth, height: contentHeight) 50 | } 51 | } 52 | 53 | override public var sectionInset: UIEdgeInsets { 54 | get { 55 | return super.sectionInset 56 | } 57 | set { 58 | super.sectionInset = UIEdgeInsets(top: 0, left: newValue.left, bottom: 0, right: newValue.right) 59 | } 60 | } 61 | 62 | private var headersAttributes: [UICollectionViewLayoutAttributes] = [] 63 | private var itemsAttributes: [[UICollectionViewLayoutAttributes]] = [] 64 | 65 | override public func prepare() { 66 | super.prepare() 67 | 68 | guard let collectionView = self.collectionView else { 69 | return 70 | } 71 | 72 | guard let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout else { 73 | return 74 | } 75 | 76 | guard let dataSource = collectionView.dataSource else { 77 | return 78 | } 79 | self.headersAttributes = [] 80 | self.itemsAttributes = [] 81 | self.contentHeight = 0 82 | 83 | let numberOfSections = dataSource.numberOfSections!(in: collectionView) 84 | for section in 0.. [UICollectionViewLayoutAttributes]? { 122 | guard self.collectionView?.dataSource != nil else { 123 | return nil 124 | } 125 | 126 | var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] 127 | 128 | for attributes in headersAttributes { 129 | if attributes.frame.intersects(rect) { 130 | visibleLayoutAttributes.append(attributes) 131 | } 132 | } 133 | return visibleLayoutAttributes 134 | } 135 | 136 | override public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 137 | guard self.headersAttributes.indices.contains(indexPath.section) else { 138 | return super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) 139 | } 140 | return self.headersAttributes[indexPath.section] 141 | } 142 | 143 | 144 | override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 145 | guard self.itemsAttributes.indices.contains(indexPath.section) else { 146 | return super.layoutAttributesForItem(at: indexPath) 147 | } 148 | guard self.itemsAttributes[indexPath.section].indices.contains(indexPath.row) else { 149 | return super.layoutAttributesForItem(at: indexPath) 150 | } 151 | return self.itemsAttributes[indexPath.section][indexPath.row] 152 | } 153 | 154 | override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { 155 | guard let collectionView = self.collectionView else { 156 | return proposedContentOffset 157 | } 158 | var targetOffset = proposedContentOffset 159 | targetOffset.y = offsetCorrection 160 | for section in 0.. 0 { 167 | targetOffset.y = targetOffset.y - emptySpace 168 | } 169 | if self.contentHeight < self.collectionViewContentSize.height { 170 | let freeSpace = self.collectionViewContentSize.height - (targetOffset.y - self.offsetCorrection) 171 | if freeSpace > 0 { 172 | targetOffset.y = self.offsetCorrection 173 | } 174 | } 175 | return targetOffset 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Source/ContentSizeAdjustmentBehavior.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentSizeAdjustmentBehavior.swift 3 | // CampcotCollectionView 4 | // 5 | // Created by Vadim Morozov on 10/30/19. 6 | // Copyright © 2019 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | public enum ContentSizeAdjustmentBehavior { 10 | public struct Inset: OptionSet { 11 | public let rawValue: Int 12 | 13 | public init(rawValue: Int) { 14 | self.rawValue = rawValue 15 | } 16 | 17 | public static let top = Inset(rawValue: 1 << 0) 18 | public static let bottom = Inset(rawValue: 1 << 1) 19 | 20 | public static let none: Inset = [] 21 | public static let all: Inset = [.top, .bottom] 22 | } 23 | 24 | /// Content size depends only on content. 25 | case normal 26 | 27 | /// Content size can't be less than collection view frame without adjust insets. 28 | case fitHeight(adjustInsets: Inset) 29 | } 30 | -------------------------------------------------------------------------------- /Source/ExpandedLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandedLayout.swift 3 | // CampcotCollectionView 4 | // 5 | // Created by Vadim Morozov on 1/26/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | public class ExpandedLayout: UICollectionViewFlowLayout { 10 | var targetSection: Int = 0 11 | var offsetCorrection: CGFloat = 0 12 | var minimumSectionSpacing: CGFloat = 0 13 | 14 | private var isTransitingToCollapsed = false { 15 | didSet { 16 | guard oldValue != isTransitingToCollapsed else { 17 | return 18 | } 19 | self.invalidateLayout() 20 | } 21 | } 22 | private var isTransitingToExpanded = false { 23 | didSet { 24 | guard oldValue != isTransitingToExpanded else { 25 | return 26 | } 27 | self.didFinishExpandTransition = !isTransitingToExpanded 28 | self.invalidateLayout() 29 | } 30 | } 31 | private var didFinishExpandTransition = false 32 | private var contentHeight: CGFloat = 0 33 | private var contentWidth: CGFloat { 34 | guard let collectionView = collectionView else { 35 | return 0 36 | } 37 | let insets = collectionView.contentInset 38 | return collectionView.bounds.width - (insets.left + insets.right) 39 | } 40 | 41 | public var contentSizeAdjustmentBehavior: ContentSizeAdjustmentBehavior = .normal { 42 | didSet { 43 | self.invalidateLayout() 44 | } 45 | } 46 | 47 | override public var collectionViewContentSize: CGSize { 48 | switch self.contentSizeAdjustmentBehavior { 49 | case .normal: 50 | return CGSize(width: self.contentWidth, height: self.contentHeight) 51 | case .fitHeight(let adjustInsets): 52 | guard let collectionView = self.collectionView else { 53 | return CGSize(width: self.contentWidth, height: self.contentHeight) 54 | } 55 | var adjustedContentHeight = collectionView.bounds.height 56 | if adjustInsets.contains(.top) { 57 | adjustedContentHeight -= collectionView.contentInset.top 58 | } 59 | if adjustInsets.contains(.bottom) { 60 | adjustedContentHeight -= collectionView.contentInset.bottom 61 | } 62 | let contentHeight = max(self.contentHeight, adjustedContentHeight) 63 | return CGSize(width: self.contentWidth, height: contentHeight) 64 | } 65 | } 66 | 67 | private var headersAttributes: [UICollectionViewLayoutAttributes] = [] 68 | private var itemsAttributes: [[UICollectionViewLayoutAttributes]] = [] 69 | 70 | override public func prepare() { 71 | super.prepare() 72 | 73 | guard !self.isTransitingToCollapsed else { 74 | self.collapseInvisibleSections() 75 | return 76 | } 77 | 78 | guard !self.isTransitingToExpanded else { 79 | self.expandVisibleSections() 80 | return 81 | } 82 | 83 | guard let collectionView = self.collectionView else { 84 | return 85 | } 86 | 87 | guard let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout else { 88 | return 89 | } 90 | 91 | guard let dataSource = collectionView.dataSource else { 92 | return 93 | } 94 | 95 | self.headersAttributes = [] 96 | self.itemsAttributes = [] 97 | self.contentHeight = 0 98 | 99 | let numberOfSections = dataSource.numberOfSections!(in: collectionView) 100 | for section in 0.. [UICollectionViewLayoutAttributes]? { 167 | guard self.collectionView?.dataSource != nil else { 168 | return nil 169 | } 170 | 171 | guard isTransitingToCollapsed || isTransitingToExpanded else { 172 | let superAttributes = super.layoutAttributesForElements(in: rect) 173 | guard let attributes = superAttributes else { 174 | return superAttributes 175 | } 176 | for elementAttributes in attributes { 177 | guard elementAttributes.representedElementCategory == .supplementaryView else { 178 | continue 179 | } 180 | guard self.headersAttributes.indices.contains(elementAttributes.indexPath.section) else { 181 | continue 182 | } 183 | elementAttributes.frame.origin.y -= self.sectionHeadersPinToBoundsCorrection( 184 | proposedTopOffset: elementAttributes.frame.origin.y, 185 | estimatedTopOffset: self.headersAttributes[elementAttributes.indexPath.section].frame.origin.y) 186 | } 187 | return attributes 188 | } 189 | var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] 190 | for attributes in headersAttributes { 191 | if attributes.frame.intersects(rect) { 192 | visibleLayoutAttributes.append(attributes) 193 | } 194 | } 195 | for sectionAttributes in itemsAttributes { 196 | for attributes in sectionAttributes { 197 | if attributes.frame.intersects(rect) { 198 | visibleLayoutAttributes.append(attributes) 199 | } 200 | } 201 | } 202 | return visibleLayoutAttributes 203 | } 204 | 205 | override public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 206 | guard self.headersAttributes.indices.contains(indexPath.section) else { 207 | return super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) 208 | } 209 | guard isTransitingToCollapsed || isTransitingToExpanded else { 210 | let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) 211 | 212 | guard let proposedTopOffset = attributes?.frame.origin.y else { 213 | return attributes 214 | } 215 | let estimatedTopOffset = self.headersAttributes[indexPath.section].frame.origin.y 216 | attributes?.frame.origin.y -= self.sectionHeadersPinToBoundsCorrection( 217 | proposedTopOffset: proposedTopOffset, 218 | estimatedTopOffset: estimatedTopOffset) 219 | return attributes 220 | } 221 | return self.headersAttributes[indexPath.section] 222 | } 223 | 224 | override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 225 | guard self.itemsAttributes.indices.contains(indexPath.section) else { 226 | return super.layoutAttributesForItem(at: indexPath) 227 | } 228 | guard self.itemsAttributes[indexPath.section].indices.contains(indexPath.row) else { 229 | return super.layoutAttributesForItem(at: indexPath) 230 | } 231 | return self.itemsAttributes[indexPath.section][indexPath.row] 232 | } 233 | 234 | override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { 235 | guard let collectionView = self.collectionView else { 236 | return proposedContentOffset 237 | } 238 | var targetOffset = proposedContentOffset 239 | targetOffset.y = offsetCorrection 240 | for section in 0.. 0 { 256 | targetOffset.y += self.minimumLineSpacing 257 | } 258 | } 259 | } 260 | targetOffset.y += self.sectionInset.bottom 261 | if sectionContentHeight == 0 { 262 | targetOffset.y -= self.sectionInset.top 263 | targetOffset.y -= self.sectionInset.bottom 264 | targetOffset.y += self.minimumSectionSpacing 265 | } 266 | } 267 | let emptySpace = collectionView.bounds.size.height - (max(self.contentHeight, collectionView.bounds.size.height) - targetOffset.y) 268 | if emptySpace > 0 { 269 | targetOffset.y = targetOffset.y - emptySpace 270 | } 271 | return targetOffset 272 | } 273 | 274 | public func collapseInvisibleSections() { 275 | guard let collectionView = self.collectionView else { 276 | return 277 | } 278 | 279 | guard let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout else { 280 | return 281 | } 282 | 283 | guard let dataSource = collectionView.dataSource else { 284 | return 285 | } 286 | 287 | var contentOffset = collectionView.contentOffset 288 | let previousItemsAttributes = self.itemsAttributes 289 | let visibleItemIndexPaths = collectionView.indexPathsForVisibleItems 290 | let visibleSections = Set(visibleItemIndexPaths.map({ $0.section })) 291 | 292 | self.headersAttributes = [] 293 | self.itemsAttributes = [] 294 | self.contentHeight = 0 295 | 296 | let numberOfSections = dataSource.numberOfSections!(in: collectionView) 297 | for section in 0.. row }).count > 0 348 | if !visibleItemIndexPaths.contains(indexPath) && section == targetSection && hasVisibleItemsAfterCurrent { 349 | contentOffset.y -= previousItemsAttributes[section][row].frame.size.height 350 | } 351 | if row < numberOfItems - 1 && visibleItemIndexPaths.contains(indexPath) { 352 | let nextIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section) 353 | if visibleItemIndexPaths.contains(nextIndexPath) { 354 | contentHeight += self.minimumLineSpacing 355 | } 356 | } 357 | if row < numberOfItems - 1 && !visibleItemIndexPaths.contains(indexPath) && section < targetSection { 358 | contentOffset.y -= self.minimumLineSpacing 359 | } 360 | if !visibleItemIndexPaths.contains(indexPath) && section == targetSection && hasVisibleItemsAfterCurrent { 361 | contentOffset.y -= self.minimumLineSpacing 362 | } 363 | } 364 | } 365 | 366 | if visibleSections.contains(section) { 367 | self.contentHeight += self.sectionInset.bottom 368 | } 369 | else { 370 | self.contentHeight += self.minimumSectionSpacing / 2 371 | if section < targetSection { 372 | contentOffset.y -= self.sectionInset.bottom 373 | } 374 | } 375 | 376 | if sectionHeadersPinToVisibleBounds { 377 | let headerAttributes = self.headersAttributes[section] 378 | if section == self.targetSection && 379 | self.contentHeight - contentOffset.y < collectionView.contentInset.top { 380 | 381 | headerAttributes.frame.origin.y = min(self.contentHeight - headerAttributes.frame.size.height, contentOffset.y) 382 | let originY = headerAttributes.frame.origin.y 383 | for i in (0.. headerAttributes.frame.origin.y { 389 | 390 | let originY = min(self.contentHeight - headerAttributes.frame.size.height, contentOffset.y) 391 | for i in (0..(visibleItemIndexPaths.map({ $0.section })) 416 | 417 | self.headersAttributes = [] 418 | self.itemsAttributes = [] 419 | self.contentHeight = 0 420 | 421 | let numberOfSections = dataSource.numberOfSections!(in: collectionView) 422 | for section in 0.. visibleContentOffset { 491 | if section != self.targetSection { 492 | self.headersAttributes[section].frame.origin.y = min( 493 | self.headersAttributes[section + 1].frame.origin.y - self.headersAttributes[section].frame.size.height, 494 | visibleContentOffset) 495 | } 496 | let originY = self.headersAttributes[section].frame.origin.y 497 | for i in (0.. CGFloat { 512 | guard let collectionViewContentOffset = self.collectionView?.contentOffset.y, 513 | let collectionViewTopInset = self.collectionView?.contentInset.top, 514 | self.sectionHeadersPinToVisibleBounds else { 515 | return 0 516 | } 517 | 518 | var topOffsetCorrection: CGFloat = 0 519 | if proposedTopOffset <= estimatedTopOffset { 520 | topOffsetCorrection = 0 521 | } 522 | else if proposedTopOffset - estimatedTopOffset <= collectionViewTopInset { 523 | if proposedTopOffset - collectionViewContentOffset >= collectionViewTopInset { 524 | topOffsetCorrection = proposedTopOffset - estimatedTopOffset 525 | } else { 526 | let newProposedTopOffset = proposedTopOffset - (collectionViewContentOffset - estimatedTopOffset) 527 | topOffsetCorrection = min(newProposedTopOffset, proposedTopOffset) - estimatedTopOffset 528 | } 529 | } 530 | else if proposedTopOffset - collectionViewContentOffset < collectionViewTopInset { 531 | topOffsetCorrection = proposedTopOffset - collectionViewContentOffset 532 | } 533 | else if proposedTopOffset - estimatedTopOffset > collectionViewTopInset { 534 | topOffsetCorrection = collectionViewTopInset 535 | } 536 | topOffsetCorrection = max(topOffsetCorrection, 0) 537 | return topOffsetCorrection 538 | } 539 | 540 | private func determineVisibleIndexPaths() -> [IndexPath] { 541 | guard let collectionView = self.collectionView, 542 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout, 543 | let dataSource = collectionView.dataSource else { 544 | return [] 545 | } 546 | 547 | let visibleFrameHeight = collectionView.bounds.size.height + offsetCorrection 548 | 549 | var visibleItems: [IndexPath] = [] 550 | var visibleContentHeight: CGFloat = 0 551 | 552 | guard visibleContentHeight < visibleFrameHeight else { 553 | return visibleItems 554 | } 555 | 556 | let numberOfSections = dataSource.numberOfSections!(in: collectionView) 557 | for section in self.targetSection.. 0 { 602 | visibleContentHeight += self.minimumLineSpacing 603 | } 604 | guard visibleContentHeight < visibleFrameHeight else { 605 | return visibleItems 606 | } 607 | } 608 | } 609 | visibleContentHeight += self.sectionInset.top 610 | let headerHeight = delegate.collectionView!(collectionView, layout: self, referenceSizeForHeaderInSection: section).height 611 | visibleContentHeight += headerHeight 612 | 613 | guard visibleContentHeight < visibleFrameHeight else { 614 | return visibleItems 615 | } 616 | } 617 | 618 | return visibleItems 619 | } 620 | 621 | private func setRealOffset() { 622 | guard let collectionView = self.collectionView, 623 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout, 624 | let dataSource = collectionView.dataSource else { 625 | return 626 | } 627 | 628 | var contentOffset: CGFloat = self.offsetCorrection 629 | for section in 0.. 10 | 11 | //! Project version number for CampcotCollectionView. 12 | FOUNDATION_EXPORT double CampcotCollectionViewVersionNumber; 13 | 14 | //! Project version string for CampcotCollectionView. 15 | FOUNDATION_EXPORT const unsigned char CampcotCollectionViewVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/Supporting Files/Info.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 | 0.0.8 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/CampcotCollectionViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CampcotCollectionViewTests.swift 3 | // CampcotCollectionViewTests 4 | // 5 | // Created by Vadim Morozov on 1/26/18. 6 | // Copyright © 2018 Touchlane LLC. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CampcotCollectionView 11 | 12 | class CampcotCollectionViewTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Supporting Files/Info.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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source ~/.rvm/scripts/rvm 4 | rvm use default 5 | pod trunk push --verbose | ruby -e 'ARGF.each{ print "." }' 6 | --------------------------------------------------------------------------------