├── .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 |
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 | 
2 |
3 | 
4 | [](https://travis-ci.org/touchlane/CampcotCollectionView)
5 | [](https://codecov.io/gh/touchlane/CampcotCollectionView)
6 | [](http://cocoapods.org/pods/CampcotCollectionView)
7 | [](http://cocoapods.org/pods/CampcotCollectionView)
8 | [](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 | 
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 |
--------------------------------------------------------------------------------