├── .gitignore
├── .slather.yml
├── .travis.yml
├── Art
├── StickLayout_Tabular.gif
├── StickyLayout_Calendar.gif
└── StickyLayout_swimming.gif
├── LICENSE
├── README.md
├── StickyLayout.podspec
├── StickyLayout.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ └── StickyLayout.xcscheme
├── StickyLayout
├── Info.plist
├── StickyLayout.h
├── StickyLayout.swift
└── StickyLayoutConfig.swift
├── StickyLayoutTests
├── Info.plist
├── StickyLayoutConfigTests.swift
└── StickyLayoutTests.swift
├── example
├── .swiftlint.yml
├── Podfile
├── Podfile.lock
├── StickyLayout.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── StickyLayoutExamples.xcscheme
└── StickyLayoutExamples
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── ExampleController.swift
│ ├── ExamplesListController.swift
│ ├── Extensions
│ └── UIColor+Extension.swift
│ ├── Info.plist
│ ├── LabelCell.swift
│ ├── SceneDelegate.swift
│ ├── Storyboards
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
│ └── ViewModels
│ ├── CalendarViewModel.swift
│ ├── MobileExpenseViewModel.swift
│ ├── StickyCollectionViewModel.swift
│ └── SwimLeaderboardViewModel.swift
└── reports
└── cobertura.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build generated
2 | build/
3 | DerivedData/
4 |
5 | Pods
6 | *.xcworkspace/
7 | .idea/
8 | xcuserdata/
9 | *.xccheckout
10 | *.xcuserstate
11 | *.xcscmblueprint
12 | *.DS_Store
13 | StickyLayout/StickyLayout\.xcodeproj/xcuserdata/
14 | StickyLayout/StickyLayout\.xcworkspace/xcuserdata
15 |
--------------------------------------------------------------------------------
/.slather.yml:
--------------------------------------------------------------------------------
1 | coverage_service: cobertura_xml
2 | xcodeproj: StickyLayout.xcodeproj
3 | scheme: StickyLayout
4 | source_directory: StickyLayout
5 | output_directory: reports
6 | ignore:
7 | - StickyLayoutTests/*
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 |
3 | osx_image: xcode10.3
4 | env:
5 | matrix:
6 | - TEST_SDK=iphonesimulator12.4 OS=12.4 NAME='iPhone XR'
7 | - TEST_SDK=iphonesimulator12.4 OS=12.4 NAME='iPhone 7'
8 |
9 | script:
10 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -project StickyLayout.xcodeproj -scheme StickyLayout -sdk $TEST_SDK -destination "platform=iOS Simulator,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES
11 |
12 | before_install:
13 | - gem install cocoapods
14 | - gem install slather
15 | after_success:
16 | - slather
17 | - bash <(curl -s https://codecov.io/bash) -f reports/cobertura.xml -X coveragepy -X gcov -X xcode
18 |
19 |
--------------------------------------------------------------------------------
/Art/StickLayout_Tabular.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffreysfllo24/StickyLayout/79d8b5a48417dbd4dabb808a2cea0b583d20500b/Art/StickLayout_Tabular.gif
--------------------------------------------------------------------------------
/Art/StickyLayout_Calendar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffreysfllo24/StickyLayout/79d8b5a48417dbd4dabb808a2cea0b583d20500b/Art/StickyLayout_Calendar.gif
--------------------------------------------------------------------------------
/Art/StickyLayout_swimming.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffreysfllo24/StickyLayout/79d8b5a48417dbd4dabb808a2cea0b583d20500b/Art/StickyLayout_swimming.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jeffrey Zhang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StickyLayout
2 | [](https://codecov.io/gh/jeffreysfllo24/StickyLayout)
3 | [](https://cocoapods.org/pods/StickyLayout)
4 | [](https://cocoadocs.org/pods/StickyLayout)
5 | 
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ##### The above examples are accessible in the `example` folder.
19 |
20 | ## What is StickyLayout?
21 | **StickyLayout** is a collection view layout that provides sticky row and column configurability.
22 |
23 | ## Features
24 |
25 | - [X] Configurable Sticky options.
26 | - [X] Pure Swift 5.
27 | - [X] Horizontal and vertical scrolling support.
28 | - [X] Row spacing and column spacing support.
29 | - [X] Works with every `UICollectionView`.
30 |
31 | ## Setup
32 | Using **StickyLayout** quick and simple. First import `StickyLayout`.
33 |
34 | ```swift
35 | import StickyLayout
36 | ```
37 |
38 | You then have the option of creating an instance of `StickyConfig`, where you can specify which rows/columns you want to be sticky.
39 |
40 | ```swift
41 | let stickyConfig = StickyLayoutConfig(stickyRowsFromTop: 1,
42 | stickyRowsFromBottom: 0,
43 | stickyColsFromLeft: 1,
44 | stickyColsFromRight: 0)
45 |
46 | let layout = StickyLayout(stickyConfig: stickyConfig)
47 | ```
48 |
49 | Create an instance of `StickyLayout` with your `StickyConfig` as a parameter, and add it to your `UICollectionView`.
50 | ```swift
51 | UICollectionView(frame: .zero, collectionViewLayout: layout)
52 | ```
53 |
54 | ## Installation
55 | StickyLayout is available through [CocoaPods](http://cocoapods.org). To install
56 | it, simply add the following line to your Podfile:
57 |
58 | ```ruby
59 | pod "StickyLayout"
60 | ```
61 |
62 | ## Feedback and Suggestions
63 | If you have any suggestions or improvements please open a pull request or create an issue.
64 |
65 | And if you found StickyLayout useful or want to show support feel free to star this repo!
66 |
--------------------------------------------------------------------------------
/StickyLayout.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'StickyLayout'
3 | s.version = '0.0.1'
4 | s.summary = 'Stick to it.'
5 |
6 | s.description = <<-DESC
7 | StickyLayout is a collection view layout that makes top/bottom rows and left/right columns fixed while the rest of the cells are scrollable.
8 | DESC
9 |
10 | s.homepage = 'https://github.com/jeffreysfllo24/StickyLayout'
11 | s.license = { :type => 'MIT', :file => 'LICENSE' }
12 | s.author = { 'Jeffrey Zhang' => 'jeffreysfllo24@gmail.com' }
13 | s.source = { :git => 'https://github.com/jeffreysfllo24/StickyLayout.git', :tag => s.version.to_s }
14 |
15 | s.ios.deployment_target = "8.0"
16 | s.source_files = 'StickyLayout/*.{h,m,swift}'
17 |
18 | s.swift_version = "5.0"
19 | end
20 |
--------------------------------------------------------------------------------
/StickyLayout.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | F8209DE6246DDECC009160B4 /* StickyLayoutConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8209DE5246DDECC009160B4 /* StickyLayoutConfig.swift */; };
11 | F826945924636E6F00A2DF1A /* StickyLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = F826945724636E6F00A2DF1A /* StickyLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
12 | F826946624636ECD00A2DF1A /* StickyLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F826946524636ECD00A2DF1A /* StickyLayoutTests.swift */; };
13 | F826946824636ECD00A2DF1A /* StickyLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F826945424636E6F00A2DF1A /* StickyLayout.framework */; };
14 | F826946F24636F6400A2DF1A /* StickyLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F826946E24636F6400A2DF1A /* StickyLayout.swift */; };
15 | F867129D246BA3ED0015D9FA /* StickyLayoutConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F867129C246BA3ED0015D9FA /* StickyLayoutConfigTests.swift */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXContainerItemProxy section */
19 | F826946924636ECD00A2DF1A /* PBXContainerItemProxy */ = {
20 | isa = PBXContainerItemProxy;
21 | containerPortal = F826944B24636E6F00A2DF1A /* Project object */;
22 | proxyType = 1;
23 | remoteGlobalIDString = F826945324636E6F00A2DF1A;
24 | remoteInfo = StickyLayout;
25 | };
26 | /* End PBXContainerItemProxy section */
27 |
28 | /* Begin PBXFileReference section */
29 | F8209DE5246DDECC009160B4 /* StickyLayoutConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyLayoutConfig.swift; sourceTree = ""; };
30 | F826945424636E6F00A2DF1A /* StickyLayout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StickyLayout.framework; sourceTree = BUILT_PRODUCTS_DIR; };
31 | F826945724636E6F00A2DF1A /* StickyLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StickyLayout.h; sourceTree = ""; };
32 | F826945824636E6F00A2DF1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
33 | F826946324636ECD00A2DF1A /* StickyLayoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StickyLayoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
34 | F826946524636ECD00A2DF1A /* StickyLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyLayoutTests.swift; sourceTree = ""; };
35 | F826946724636ECD00A2DF1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
36 | F826946E24636F6400A2DF1A /* StickyLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyLayout.swift; sourceTree = ""; };
37 | F867129C246BA3ED0015D9FA /* StickyLayoutConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyLayoutConfigTests.swift; sourceTree = ""; };
38 | /* End PBXFileReference section */
39 |
40 | /* Begin PBXFrameworksBuildPhase section */
41 | F826945124636E6F00A2DF1A /* Frameworks */ = {
42 | isa = PBXFrameworksBuildPhase;
43 | buildActionMask = 2147483647;
44 | files = (
45 | );
46 | runOnlyForDeploymentPostprocessing = 0;
47 | };
48 | F826946024636ECD00A2DF1A /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | F826946824636ECD00A2DF1A /* StickyLayout.framework in Frameworks */,
53 | );
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXFrameworksBuildPhase section */
57 |
58 | /* Begin PBXGroup section */
59 | F826944A24636E6F00A2DF1A = {
60 | isa = PBXGroup;
61 | children = (
62 | F826945624636E6F00A2DF1A /* StickyLayout */,
63 | F826946424636ECD00A2DF1A /* StickyLayoutTests */,
64 | F826945524636E6F00A2DF1A /* Products */,
65 | );
66 | sourceTree = "";
67 | };
68 | F826945524636E6F00A2DF1A /* Products */ = {
69 | isa = PBXGroup;
70 | children = (
71 | F826945424636E6F00A2DF1A /* StickyLayout.framework */,
72 | F826946324636ECD00A2DF1A /* StickyLayoutTests.xctest */,
73 | );
74 | name = Products;
75 | sourceTree = "";
76 | };
77 | F826945624636E6F00A2DF1A /* StickyLayout */ = {
78 | isa = PBXGroup;
79 | children = (
80 | F826945724636E6F00A2DF1A /* StickyLayout.h */,
81 | F826946E24636F6400A2DF1A /* StickyLayout.swift */,
82 | F8209DE5246DDECC009160B4 /* StickyLayoutConfig.swift */,
83 | F826945824636E6F00A2DF1A /* Info.plist */,
84 | );
85 | path = StickyLayout;
86 | sourceTree = "";
87 | };
88 | F826946424636ECD00A2DF1A /* StickyLayoutTests */ = {
89 | isa = PBXGroup;
90 | children = (
91 | F826946524636ECD00A2DF1A /* StickyLayoutTests.swift */,
92 | F867129C246BA3ED0015D9FA /* StickyLayoutConfigTests.swift */,
93 | F826946724636ECD00A2DF1A /* Info.plist */,
94 | );
95 | path = StickyLayoutTests;
96 | sourceTree = "";
97 | };
98 | /* End PBXGroup section */
99 |
100 | /* Begin PBXHeadersBuildPhase section */
101 | F826944F24636E6F00A2DF1A /* Headers */ = {
102 | isa = PBXHeadersBuildPhase;
103 | buildActionMask = 2147483647;
104 | files = (
105 | F826945924636E6F00A2DF1A /* StickyLayout.h in Headers */,
106 | );
107 | runOnlyForDeploymentPostprocessing = 0;
108 | };
109 | /* End PBXHeadersBuildPhase section */
110 |
111 | /* Begin PBXNativeTarget section */
112 | F826945324636E6F00A2DF1A /* StickyLayout */ = {
113 | isa = PBXNativeTarget;
114 | buildConfigurationList = F826945C24636E6F00A2DF1A /* Build configuration list for PBXNativeTarget "StickyLayout" */;
115 | buildPhases = (
116 | F826944F24636E6F00A2DF1A /* Headers */,
117 | F826945024636E6F00A2DF1A /* Sources */,
118 | F826945124636E6F00A2DF1A /* Frameworks */,
119 | F826945224636E6F00A2DF1A /* Resources */,
120 | );
121 | buildRules = (
122 | );
123 | dependencies = (
124 | );
125 | name = StickyLayout;
126 | productName = StickyLayout;
127 | productReference = F826945424636E6F00A2DF1A /* StickyLayout.framework */;
128 | productType = "com.apple.product-type.framework";
129 | };
130 | F826946224636ECD00A2DF1A /* StickyLayoutTests */ = {
131 | isa = PBXNativeTarget;
132 | buildConfigurationList = F826946B24636ECD00A2DF1A /* Build configuration list for PBXNativeTarget "StickyLayoutTests" */;
133 | buildPhases = (
134 | F826945F24636ECD00A2DF1A /* Sources */,
135 | F826946024636ECD00A2DF1A /* Frameworks */,
136 | F826946124636ECD00A2DF1A /* Resources */,
137 | );
138 | buildRules = (
139 | );
140 | dependencies = (
141 | F826946A24636ECD00A2DF1A /* PBXTargetDependency */,
142 | );
143 | name = StickyLayoutTests;
144 | productName = StickyLayoutTests;
145 | productReference = F826946324636ECD00A2DF1A /* StickyLayoutTests.xctest */;
146 | productType = "com.apple.product-type.bundle.unit-test";
147 | };
148 | /* End PBXNativeTarget section */
149 |
150 | /* Begin PBXProject section */
151 | F826944B24636E6F00A2DF1A /* Project object */ = {
152 | isa = PBXProject;
153 | attributes = {
154 | LastSwiftUpdateCheck = 1140;
155 | LastUpgradeCheck = 1140;
156 | ORGANIZATIONNAME = "Jeffrey Zhang";
157 | TargetAttributes = {
158 | F826945324636E6F00A2DF1A = {
159 | CreatedOnToolsVersion = 11.4.1;
160 | LastSwiftMigration = 1140;
161 | };
162 | F826946224636ECD00A2DF1A = {
163 | CreatedOnToolsVersion = 11.4.1;
164 | };
165 | };
166 | };
167 | buildConfigurationList = F826944E24636E6F00A2DF1A /* Build configuration list for PBXProject "StickyLayout" */;
168 | compatibilityVersion = "Xcode 9.3";
169 | developmentRegion = en;
170 | hasScannedForEncodings = 0;
171 | knownRegions = (
172 | en,
173 | Base,
174 | );
175 | mainGroup = F826944A24636E6F00A2DF1A;
176 | productRefGroup = F826945524636E6F00A2DF1A /* Products */;
177 | projectDirPath = "";
178 | projectRoot = "";
179 | targets = (
180 | F826945324636E6F00A2DF1A /* StickyLayout */,
181 | F826946224636ECD00A2DF1A /* StickyLayoutTests */,
182 | );
183 | };
184 | /* End PBXProject section */
185 |
186 | /* Begin PBXResourcesBuildPhase section */
187 | F826945224636E6F00A2DF1A /* Resources */ = {
188 | isa = PBXResourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | );
192 | runOnlyForDeploymentPostprocessing = 0;
193 | };
194 | F826946124636ECD00A2DF1A /* Resources */ = {
195 | isa = PBXResourcesBuildPhase;
196 | buildActionMask = 2147483647;
197 | files = (
198 | );
199 | runOnlyForDeploymentPostprocessing = 0;
200 | };
201 | /* End PBXResourcesBuildPhase section */
202 |
203 | /* Begin PBXSourcesBuildPhase section */
204 | F826945024636E6F00A2DF1A /* Sources */ = {
205 | isa = PBXSourcesBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | F8209DE6246DDECC009160B4 /* StickyLayoutConfig.swift in Sources */,
209 | F826946F24636F6400A2DF1A /* StickyLayout.swift in Sources */,
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | };
213 | F826945F24636ECD00A2DF1A /* Sources */ = {
214 | isa = PBXSourcesBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | F867129D246BA3ED0015D9FA /* StickyLayoutConfigTests.swift in Sources */,
218 | F826946624636ECD00A2DF1A /* StickyLayoutTests.swift in Sources */,
219 | );
220 | runOnlyForDeploymentPostprocessing = 0;
221 | };
222 | /* End PBXSourcesBuildPhase section */
223 |
224 | /* Begin PBXTargetDependency section */
225 | F826946A24636ECD00A2DF1A /* PBXTargetDependency */ = {
226 | isa = PBXTargetDependency;
227 | target = F826945324636E6F00A2DF1A /* StickyLayout */;
228 | targetProxy = F826946924636ECD00A2DF1A /* PBXContainerItemProxy */;
229 | };
230 | /* End PBXTargetDependency section */
231 |
232 | /* Begin XCBuildConfiguration section */
233 | F826945A24636E6F00A2DF1A /* Debug */ = {
234 | isa = XCBuildConfiguration;
235 | buildSettings = {
236 | ALWAYS_SEARCH_USER_PATHS = NO;
237 | CLANG_ANALYZER_NONNULL = YES;
238 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
240 | CLANG_CXX_LIBRARY = "libc++";
241 | CLANG_ENABLE_MODULES = YES;
242 | CLANG_ENABLE_OBJC_ARC = YES;
243 | CLANG_ENABLE_OBJC_WEAK = YES;
244 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
245 | CLANG_WARN_BOOL_CONVERSION = YES;
246 | CLANG_WARN_COMMA = YES;
247 | CLANG_WARN_CONSTANT_CONVERSION = YES;
248 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
251 | CLANG_WARN_EMPTY_BODY = YES;
252 | CLANG_WARN_ENUM_CONVERSION = YES;
253 | CLANG_WARN_INFINITE_RECURSION = YES;
254 | CLANG_WARN_INT_CONVERSION = YES;
255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260 | CLANG_WARN_STRICT_PROTOTYPES = YES;
261 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
263 | CLANG_WARN_UNREACHABLE_CODE = YES;
264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
265 | COPY_PHASE_STRIP = NO;
266 | CURRENT_PROJECT_VERSION = 1;
267 | DEBUG_INFORMATION_FORMAT = dwarf;
268 | ENABLE_STRICT_OBJC_MSGSEND = YES;
269 | ENABLE_TESTABILITY = YES;
270 | GCC_C_LANGUAGE_STANDARD = gnu11;
271 | GCC_DYNAMIC_NO_PIC = NO;
272 | GCC_NO_COMMON_BLOCKS = YES;
273 | GCC_OPTIMIZATION_LEVEL = 0;
274 | GCC_PREPROCESSOR_DEFINITIONS = (
275 | "DEBUG=1",
276 | "$(inherited)",
277 | );
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
285 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
286 | MTL_FAST_MATH = YES;
287 | ONLY_ACTIVE_ARCH = YES;
288 | SDKROOT = iphoneos;
289 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
290 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
291 | SWIFT_VERSION = 5.0;
292 | VERSIONING_SYSTEM = "apple-generic";
293 | VERSION_INFO_PREFIX = "";
294 | };
295 | name = Debug;
296 | };
297 | F826945B24636E6F00A2DF1A /* Release */ = {
298 | isa = XCBuildConfiguration;
299 | buildSettings = {
300 | ALWAYS_SEARCH_USER_PATHS = NO;
301 | CLANG_ANALYZER_NONNULL = YES;
302 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
304 | CLANG_CXX_LIBRARY = "libc++";
305 | CLANG_ENABLE_MODULES = YES;
306 | CLANG_ENABLE_OBJC_ARC = YES;
307 | CLANG_ENABLE_OBJC_WEAK = YES;
308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
309 | CLANG_WARN_BOOL_CONVERSION = YES;
310 | CLANG_WARN_COMMA = YES;
311 | CLANG_WARN_CONSTANT_CONVERSION = YES;
312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
314 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
315 | CLANG_WARN_EMPTY_BODY = YES;
316 | CLANG_WARN_ENUM_CONVERSION = YES;
317 | CLANG_WARN_INFINITE_RECURSION = YES;
318 | CLANG_WARN_INT_CONVERSION = YES;
319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
324 | CLANG_WARN_STRICT_PROTOTYPES = YES;
325 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
326 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
327 | CLANG_WARN_UNREACHABLE_CODE = YES;
328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
329 | COPY_PHASE_STRIP = NO;
330 | CURRENT_PROJECT_VERSION = 1;
331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
332 | ENABLE_NS_ASSERTIONS = NO;
333 | ENABLE_STRICT_OBJC_MSGSEND = YES;
334 | GCC_C_LANGUAGE_STANDARD = gnu11;
335 | GCC_NO_COMMON_BLOCKS = YES;
336 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
337 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
338 | GCC_WARN_UNDECLARED_SELECTOR = YES;
339 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
340 | GCC_WARN_UNUSED_FUNCTION = YES;
341 | GCC_WARN_UNUSED_VARIABLE = YES;
342 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
343 | MTL_ENABLE_DEBUG_INFO = NO;
344 | MTL_FAST_MATH = YES;
345 | SDKROOT = iphoneos;
346 | SWIFT_COMPILATION_MODE = wholemodule;
347 | SWIFT_OPTIMIZATION_LEVEL = "-O";
348 | SWIFT_VERSION = 5.0;
349 | VALIDATE_PRODUCT = YES;
350 | VERSIONING_SYSTEM = "apple-generic";
351 | VERSION_INFO_PREFIX = "";
352 | };
353 | name = Release;
354 | };
355 | F826945D24636E6F00A2DF1A /* Debug */ = {
356 | isa = XCBuildConfiguration;
357 | buildSettings = {
358 | CLANG_ENABLE_MODULES = YES;
359 | CODE_SIGN_STYLE = Automatic;
360 | DEFINES_MODULE = YES;
361 | DEVELOPMENT_TEAM = R6BGCQQ764;
362 | DYLIB_COMPATIBILITY_VERSION = 1;
363 | DYLIB_CURRENT_VERSION = 1;
364 | DYLIB_INSTALL_NAME_BASE = "@rpath";
365 | INFOPLIST_FILE = StickyLayout/Info.plist;
366 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
368 | LD_RUNPATH_SEARCH_PATHS = (
369 | "$(inherited)",
370 | "@executable_path/Frameworks",
371 | "@loader_path/Frameworks",
372 | );
373 | PRODUCT_BUNDLE_IDENTIFIER = kocrashstudios.StickyLayout;
374 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
375 | SKIP_INSTALL = YES;
376 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
377 | SWIFT_VERSION = 5.0;
378 | TARGETED_DEVICE_FAMILY = "1,2";
379 | };
380 | name = Debug;
381 | };
382 | F826945E24636E6F00A2DF1A /* Release */ = {
383 | isa = XCBuildConfiguration;
384 | buildSettings = {
385 | CLANG_ENABLE_MODULES = YES;
386 | CODE_SIGN_STYLE = Automatic;
387 | DEFINES_MODULE = YES;
388 | DEVELOPMENT_TEAM = R6BGCQQ764;
389 | DYLIB_COMPATIBILITY_VERSION = 1;
390 | DYLIB_CURRENT_VERSION = 1;
391 | DYLIB_INSTALL_NAME_BASE = "@rpath";
392 | INFOPLIST_FILE = StickyLayout/Info.plist;
393 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
394 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
395 | LD_RUNPATH_SEARCH_PATHS = (
396 | "$(inherited)",
397 | "@executable_path/Frameworks",
398 | "@loader_path/Frameworks",
399 | );
400 | PRODUCT_BUNDLE_IDENTIFIER = kocrashstudios.StickyLayout;
401 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
402 | SKIP_INSTALL = YES;
403 | SWIFT_VERSION = 5.0;
404 | TARGETED_DEVICE_FAMILY = "1,2";
405 | };
406 | name = Release;
407 | };
408 | F826946C24636ECD00A2DF1A /* Debug */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
412 | CODE_SIGN_STYLE = Automatic;
413 | DEVELOPMENT_TEAM = R6BGCQQ764;
414 | INFOPLIST_FILE = StickyLayoutTests/Info.plist;
415 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
416 | LD_RUNPATH_SEARCH_PATHS = (
417 | "$(inherited)",
418 | "@executable_path/Frameworks",
419 | "@loader_path/Frameworks",
420 | );
421 | PRODUCT_BUNDLE_IDENTIFIER = kocrashstudios.StickyLayoutTests;
422 | PRODUCT_NAME = "$(TARGET_NAME)";
423 | SWIFT_VERSION = 5.0;
424 | TARGETED_DEVICE_FAMILY = "1,2";
425 | };
426 | name = Debug;
427 | };
428 | F826946D24636ECD00A2DF1A /* Release */ = {
429 | isa = XCBuildConfiguration;
430 | buildSettings = {
431 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
432 | CODE_SIGN_STYLE = Automatic;
433 | DEVELOPMENT_TEAM = R6BGCQQ764;
434 | INFOPLIST_FILE = StickyLayoutTests/Info.plist;
435 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
436 | LD_RUNPATH_SEARCH_PATHS = (
437 | "$(inherited)",
438 | "@executable_path/Frameworks",
439 | "@loader_path/Frameworks",
440 | );
441 | PRODUCT_BUNDLE_IDENTIFIER = kocrashstudios.StickyLayoutTests;
442 | PRODUCT_NAME = "$(TARGET_NAME)";
443 | SWIFT_VERSION = 5.0;
444 | TARGETED_DEVICE_FAMILY = "1,2";
445 | };
446 | name = Release;
447 | };
448 | /* End XCBuildConfiguration section */
449 |
450 | /* Begin XCConfigurationList section */
451 | F826944E24636E6F00A2DF1A /* Build configuration list for PBXProject "StickyLayout" */ = {
452 | isa = XCConfigurationList;
453 | buildConfigurations = (
454 | F826945A24636E6F00A2DF1A /* Debug */,
455 | F826945B24636E6F00A2DF1A /* Release */,
456 | );
457 | defaultConfigurationIsVisible = 0;
458 | defaultConfigurationName = Release;
459 | };
460 | F826945C24636E6F00A2DF1A /* Build configuration list for PBXNativeTarget "StickyLayout" */ = {
461 | isa = XCConfigurationList;
462 | buildConfigurations = (
463 | F826945D24636E6F00A2DF1A /* Debug */,
464 | F826945E24636E6F00A2DF1A /* Release */,
465 | );
466 | defaultConfigurationIsVisible = 0;
467 | defaultConfigurationName = Release;
468 | };
469 | F826946B24636ECD00A2DF1A /* Build configuration list for PBXNativeTarget "StickyLayoutTests" */ = {
470 | isa = XCConfigurationList;
471 | buildConfigurations = (
472 | F826946C24636ECD00A2DF1A /* Debug */,
473 | F826946D24636ECD00A2DF1A /* Release */,
474 | );
475 | defaultConfigurationIsVisible = 0;
476 | defaultConfigurationName = Release;
477 | };
478 | /* End XCConfigurationList section */
479 | };
480 | rootObject = F826944B24636E6F00A2DF1A /* Project object */;
481 | }
482 |
--------------------------------------------------------------------------------
/StickyLayout.xcodeproj/xcshareddata/xcschemes/StickyLayout.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
61 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/StickyLayout/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/StickyLayout/StickyLayout.h:
--------------------------------------------------------------------------------
1 | //
2 | // StickyLayout.h
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-06.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for StickyLayout.
12 | FOUNDATION_EXPORT double StickyLayoutVersionNumber;
13 |
14 | //! Project version string for StickyLayout.
15 | FOUNDATION_EXPORT const unsigned char StickyLayoutVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/StickyLayout/StickyLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StickyLayout.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-06.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | open class StickyLayout: UICollectionViewFlowLayout {
13 | private let stickyConfig: StickyLayoutConfig
14 | private var cellAttrsDict = [IndexPath: UICollectionViewLayoutAttributes]()
15 | private var cellFramesDict = [IndexPath: CGRect]()
16 | private var collectionViewContentWidth: CGFloat = 0
17 | private var collectionViewContentHeight: CGFloat = 0
18 |
19 | override open var collectionViewContentSize: CGSize {
20 | return CGSize(width: collectionViewContentWidth, height: collectionViewContentHeight)
21 | }
22 |
23 | private var rows: Int {
24 | return collectionView?.numberOfSections ?? 0
25 | }
26 |
27 | private func colsCount(section: Int) -> Int {
28 | return collectionView?.numberOfItems(inSection: section) ?? 0
29 | }
30 |
31 | public init(stickyConfig: StickyLayoutConfig = StickyLayoutConfig()) {
32 | self.stickyConfig = stickyConfig
33 | super.init()
34 | }
35 |
36 | public required init?(coder: NSCoder) {
37 | fatalError("init(coder:) has not been implemented")
38 | }
39 |
40 | override open func prepare() {
41 | super.prepare()
42 | layoutCellPositions()
43 | updateStickyCellPositions()
44 | }
45 |
46 | private func layoutCellPositions() {
47 | guard let collectionView = collectionView, rows > 0 else {
48 | return
49 | }
50 |
51 | // Reset collection view attributes for new layout
52 | cellAttrsDict = [IndexPath: UICollectionViewLayoutAttributes]()
53 | cellFramesDict = [IndexPath: CGRect]()
54 | collectionViewContentWidth = 0
55 | collectionViewContentHeight = 0
56 |
57 | // Set Containing current xPos and current yPos for each column
58 | var xPos: CGFloat = 0
59 | var yPos: CGFloat = 0
60 |
61 | let bottomStickyRowsSets = stickyConfig.getBottomStickyRows(rowCount: rows)
62 |
63 | // Retrieve height of right sticky columns and bottom sticky rows
64 | var stickyColHeights = stickyCellHeights()
65 | var stickyRowWidths = stickyCellsRowWidths()
66 |
67 | for section in 0.. CGFloat {
121 | var stickyCellHeights: CGFloat = 0
122 |
123 | let bottomStickyRowsSet = stickyConfig.getBottomStickyRows(rowCount: rows)
124 | for section in bottomStickyRowsSet {
125 | guard let itemsCount = collectionView?.numberOfItems(inSection: section), itemsCount > 0 else { continue }
126 | let cellSpacing = (section == rows - 1) ? 0 : getSectionSpacing(forRow: section)
127 | var maximumCellRowHeight: CGFloat = 0
128 | for col in 0.. [Int: CGFloat] {
138 | var stickyRowWidths: [Int: CGFloat] = [:]
139 |
140 | for section in 0.. 0, !stickyConfig.getRightStickyCols(colCount: itemsCount).isEmpty else {
142 | continue
143 | }
144 |
145 | let stickyCols = stickyConfig.getRightStickyCols(colCount: itemsCount)
146 | let cellSpacing = getInterItemSpacing(forRow: section)
147 |
148 | for col in stickyCols {
149 | let cellSize = getCellSize(forRow: section, forCol: col)
150 | let cellSpacing = (col == itemsCount - 1) ? 0 : cellSpacing
151 | stickyRowWidths[section] = (stickyRowWidths[section] ?? 0) + cellSize.width + cellSpacing
152 | }
153 | }
154 |
155 | return stickyRowWidths
156 | }
157 |
158 | private func updateStickyCellPositions() {
159 | guard let collectionView = collectionView else {
160 | return
161 | }
162 |
163 | let stickyRowSet = stickyConfig.getStickyRows(rowCount: rows)
164 |
165 | for row in 0.. Bool {
190 | return true
191 | }
192 |
193 | open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
194 | var collectionViewAttributes: [UICollectionViewLayoutAttributes] = []
195 | for (index, attribute) in cellAttrsDict {
196 | /// Unique case where we need to add right sticky column because collection view bounds don't update when you horizontally force scroll out of collection view content size.
197 | if stickyConfig.getRightStickyCols(colCount: colsCount(section: index.section)).contains(index.item) {
198 | collectionViewAttributes.append(attribute)
199 | } else if rect.intersects(attribute.frame) {
200 | collectionViewAttributes.append(attribute)
201 | }
202 | }
203 | return collectionViewAttributes
204 | }
205 |
206 | open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
207 | return cellAttrsDict[indexPath]
208 | }
209 |
210 | private func getCellSize(forRow row: Int, forCol col: Int) -> CGSize {
211 | guard let collectionView = collectionView,
212 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout,
213 | let size = delegate.collectionView?(collectionView, layout: self, sizeForItemAt: IndexPath(item: col, section: row)) else {
214 | return self.itemSize
215 | }
216 | return size
217 | }
218 |
219 | private func getSectionSpacing(forRow row: Int) -> CGFloat {
220 | guard let collectionView = collectionView,
221 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout,
222 | let spacing = delegate.collectionView?(collectionView, layout: self, minimumLineSpacingForSectionAt: row) else {
223 | return self.minimumLineSpacing
224 | }
225 | return spacing
226 | }
227 |
228 | private func getInterItemSpacing(forRow row: Int) -> CGFloat {
229 | guard let collectionView = collectionView,
230 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout,
231 | let spacing = delegate.collectionView?(collectionView, layout: self, minimumInteritemSpacingForSectionAt: row) else {
232 | return self.minimumInteritemSpacing
233 | }
234 | return spacing
235 | }
236 |
237 | }
238 |
239 | // MARK: - ZOrdering
240 | extension StickyLayout {
241 |
242 | private func zOrder(forRow row: Int, forCol col: Int, stickyRowSet: Set, stickyColSet: Set) -> Int {
243 | if stickyRowSet.contains(row) && stickyColSet.contains(col) {
244 | return ZOrder.staticCell
245 | } else if stickyRowSet.contains(row) || stickyColSet.contains(col) {
246 | return ZOrder.stickyCell
247 | } else {
248 | return ZOrder.basicCell
249 | }
250 | }
251 |
252 | private enum ZOrder {
253 | static let basicCell = 0
254 | static let stickyCell = 1
255 | static let staticCell = 2
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/StickyLayout/StickyLayoutConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StickyLayoutConfig.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-14.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct StickyLayoutConfig {
12 | private let stickyRowsFromTop: Int
13 | private let stickyRowsFromBottom: Int
14 | private let stickyColsFromLeft: Int
15 | private let stickyColsFromRight: Int
16 |
17 | public init(stickyRowsFromTop: Int = 1,
18 | stickyRowsFromBottom: Int = 0,
19 | stickyColsFromLeft: Int = 1,
20 | stickyColsFromRight: Int = 0) {
21 |
22 | self.stickyRowsFromTop = stickyRowsFromTop
23 | self.stickyRowsFromBottom = stickyRowsFromBottom
24 | self.stickyColsFromLeft = stickyColsFromLeft
25 | self.stickyColsFromRight = stickyColsFromRight
26 | }
27 |
28 | public func getStickyRows(rowCount: Int) -> Set {
29 | return getTopStickyRows(rowCount: rowCount).union(getBottomStickyRows(rowCount: rowCount))
30 | }
31 |
32 | public func getStickyCols(colCount: Int) -> Set {
33 | return getLeftStickyCols(colCount: colCount).union(getRightStickyCols(colCount: colCount))
34 | }
35 |
36 | public func getLeftStickyCols(colCount: Int) -> Set {
37 | return getStickySet(totalLength: colCount, startIndex: 0, stickyCount: stickyColsFromLeft)
38 | }
39 |
40 | public func getRightStickyCols(colCount: Int) -> Set {
41 | return getStickySet(totalLength: colCount, startIndex: colCount - stickyColsFromRight, stickyCount: stickyColsFromRight)
42 | }
43 |
44 | public func getBottomStickyRows(rowCount: Int) -> Set {
45 | return getStickySet(totalLength: rowCount, startIndex: rowCount - stickyRowsFromBottom, stickyCount: stickyRowsFromBottom)
46 | }
47 |
48 | public func getTopStickyRows(rowCount: Int) -> Set {
49 | return getStickySet(totalLength: rowCount, startIndex: 0, stickyCount: stickyRowsFromTop)
50 | }
51 |
52 | private func getStickySet(totalLength: Int, startIndex: Int, stickyCount: Int) -> Set {
53 | let stickySetCount = min(totalLength, stickyCount)
54 | let startIndex = max(startIndex, 0)
55 | return stickySetCount > 0 ? Set(startIndex...startIndex + stickySetCount - 1) : Set()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/StickyLayoutTests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/StickyLayoutTests/StickyLayoutConfigTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StickyLayoutConfigTests.swift
3 | // StickyLayoutTests
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-12.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | @testable import StickyLayout
10 | import XCTest
11 |
12 | class StickyLayoutConfigTests: XCTestCase {
13 | let emptySet = Set()
14 |
15 | // MARK: - Sticky Config Tests
16 | func testEmptyStickyConfigWithEmptyLayout() throws {
17 | let emptyStickyConfig = StickyLayoutConfig(stickyRowsFromTop: 0,
18 | stickyRowsFromBottom: 0,
19 | stickyColsFromLeft: 0,
20 | stickyColsFromRight: 0)
21 | XCTAssertEqual(emptyStickyConfig.getBottomStickyRows(rowCount: 0), emptySet)
22 | XCTAssertEqual(emptyStickyConfig.getTopStickyRows(rowCount: 0), emptySet)
23 | XCTAssertEqual(emptyStickyConfig.getLeftStickyCols(colCount: 0), emptySet)
24 | XCTAssertEqual(emptyStickyConfig.getRightStickyCols(colCount: 0), emptySet)
25 | XCTAssertEqual(emptyStickyConfig.getStickyRows(rowCount: 0), emptySet)
26 | XCTAssertEqual(emptyStickyConfig.getStickyCols(colCount: 0), emptySet)
27 | }
28 |
29 | func testEmptyStickyConfigWithPopulatedLayout() throws {
30 | let emptyStickyConfig = StickyLayoutConfig(stickyRowsFromTop: 0,
31 | stickyRowsFromBottom: 0,
32 | stickyColsFromLeft: 0,
33 | stickyColsFromRight: 0)
34 | XCTAssertEqual(emptyStickyConfig.getBottomStickyRows(rowCount: 100), emptySet)
35 | XCTAssertEqual(emptyStickyConfig.getTopStickyRows(rowCount: 100), emptySet)
36 | XCTAssertEqual(emptyStickyConfig.getLeftStickyCols(colCount: 100), emptySet)
37 | XCTAssertEqual(emptyStickyConfig.getRightStickyCols(colCount: 100), emptySet)
38 |
39 | XCTAssertEqual(emptyStickyConfig.getStickyRows(rowCount: 100), emptySet)
40 |
41 | XCTAssertEqual(emptyStickyConfig.getStickyCols(colCount: 100), emptySet)
42 | }
43 |
44 | func testDefaultStickyConfig() throws {
45 | let defaultStickyConfig = StickyLayoutConfig()
46 | let defaultSet = Set([0])
47 |
48 | XCTAssertEqual(defaultStickyConfig.getBottomStickyRows(rowCount: 100), emptySet)
49 | XCTAssertEqual(defaultStickyConfig.getBottomStickyRows(rowCount: 2), emptySet)
50 |
51 | XCTAssertEqual(defaultStickyConfig.getTopStickyRows(rowCount: 100), defaultSet)
52 | XCTAssertEqual(defaultStickyConfig.getTopStickyRows(rowCount: 1), defaultSet)
53 |
54 | XCTAssertEqual(defaultStickyConfig.getLeftStickyCols(colCount: 100), defaultSet)
55 | XCTAssertEqual(defaultStickyConfig.getLeftStickyCols(colCount: 1), defaultSet)
56 |
57 | XCTAssertEqual(defaultStickyConfig.getRightStickyCols(colCount: 100), emptySet)
58 | XCTAssertEqual(defaultStickyConfig.getRightStickyCols(colCount: 12), emptySet)
59 |
60 | XCTAssertEqual(defaultStickyConfig.getStickyRows(rowCount: 100), defaultSet)
61 | XCTAssertEqual(defaultStickyConfig.getStickyRows(rowCount: 1), defaultSet)
62 |
63 | XCTAssertEqual(defaultStickyConfig.getStickyCols(colCount: 100), defaultSet)
64 | XCTAssertEqual(defaultStickyConfig.getStickyCols(colCount: 1), defaultSet)
65 | }
66 |
67 | func testDefaultStickyConfigEmptyLayout() throws {
68 | let defaultStickyConfig = StickyLayoutConfig()
69 |
70 | XCTAssertEqual(defaultStickyConfig.getBottomStickyRows(rowCount: 0), emptySet)
71 | XCTAssertEqual(defaultStickyConfig.getTopStickyRows(rowCount: 0), emptySet)
72 | XCTAssertEqual(defaultStickyConfig.getLeftStickyCols(colCount: 0), emptySet)
73 | XCTAssertEqual(defaultStickyConfig.getRightStickyCols(colCount: 0), emptySet)
74 |
75 | XCTAssertEqual(defaultStickyConfig.getStickyRows(rowCount: 0), emptySet)
76 |
77 | XCTAssertEqual(defaultStickyConfig.getStickyCols(colCount: 0), emptySet)
78 | }
79 |
80 | func testNormalStickyConfig() throws {
81 | let defaultStickyConfig = StickyLayoutConfig(stickyRowsFromTop: 1,
82 | stickyRowsFromBottom: 2,
83 | stickyColsFromLeft: 1,
84 | stickyColsFromRight: 1)
85 |
86 | XCTAssertEqual(defaultStickyConfig.getBottomStickyRows(rowCount: 50), Set([48, 49]))
87 | XCTAssertEqual(defaultStickyConfig.getTopStickyRows(rowCount: 50), Set([0]))
88 | XCTAssertEqual(defaultStickyConfig.getLeftStickyCols(colCount: 50), Set([0]))
89 | XCTAssertEqual(defaultStickyConfig.getRightStickyCols(colCount: 50), Set([49]))
90 |
91 | XCTAssertEqual(defaultStickyConfig.getStickyRows(rowCount: 50), Set([0, 48, 49]))
92 | XCTAssertEqual(defaultStickyConfig.getStickyCols(colCount: 50), Set([0, 49]))
93 | }
94 |
95 | func testOverlappingStickyConfig() throws {
96 | let stickyConfig = StickyLayoutConfig(stickyRowsFromTop: 5,
97 | stickyRowsFromBottom: 3,
98 | stickyColsFromLeft: 2,
99 | stickyColsFromRight: 10)
100 | XCTAssertEqual(stickyConfig.getBottomStickyRows(rowCount: 3), Set([0, 1, 2]))
101 | XCTAssertEqual(stickyConfig.getTopStickyRows(rowCount: 5), Set([0, 1, 2, 3, 4]))
102 | XCTAssertEqual(stickyConfig.getLeftStickyCols(colCount: 7), Set([0, 1]))
103 | XCTAssertEqual(stickyConfig.getRightStickyCols(colCount: 10), Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
104 |
105 | XCTAssertEqual(stickyConfig.getStickyRows(rowCount: 4), Set([0, 1, 2, 3]))
106 | XCTAssertEqual(stickyConfig.getStickyRows(rowCount: 5), Set([0, 1, 2, 3, 4]))
107 |
108 | XCTAssertEqual(stickyConfig.getStickyCols(colCount: 10), Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
109 | }
110 |
111 | func testOverflowingStickyConfig() throws {
112 | let stickyConfig = StickyLayoutConfig(stickyRowsFromTop: 500,
113 | stickyRowsFromBottom: 300,
114 | stickyColsFromLeft: 200,
115 | stickyColsFromRight: 100)
116 | XCTAssertEqual(stickyConfig.getBottomStickyRows(rowCount: 3), Set([0, 1, 2]))
117 | XCTAssertEqual(stickyConfig.getTopStickyRows(rowCount: 5), Set([0, 1, 2, 3, 4]))
118 | XCTAssertEqual(stickyConfig.getLeftStickyCols(colCount: 2), Set([0, 1]))
119 | XCTAssertEqual(stickyConfig.getRightStickyCols(colCount: 10), Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
120 | XCTAssertEqual(stickyConfig.getStickyRows(rowCount: 4), Set([0, 1, 2, 3]))
121 |
122 | XCTAssertEqual(stickyConfig.getStickyCols(colCount: 10), Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
123 | }
124 |
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/StickyLayoutTests/StickyLayoutTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StickyLayoutTests.swift
3 | // StickyLayoutTests
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-06.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | @testable import StickyLayout
10 | import UIKit
11 | import XCTest
12 |
13 | class StickyLayoutTests: XCTestCase {
14 |
15 | var collectionView: UICollectionView!
16 | var mockDataProvider: MockDataProvider!
17 | var stickylayout: StickyLayout!
18 |
19 | var dataModel = [[0, 1, 2], [3, 4, 5], [6, 7]]
20 |
21 | class MockDataProvider: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
22 | weak var parent: StickyLayoutTests! = nil
23 |
24 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
25 | return parent.dataModel[section].count
26 | }
27 | func numberOfSections(in collectionView: UICollectionView) -> Int {
28 | return parent.dataModel.count
29 | }
30 |
31 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
32 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
33 | return cell
34 | }
35 |
36 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
37 | sizeForItemAt indexPath: IndexPath) -> CGSize {
38 | return CGSize(width: 120, height: 120)
39 | }
40 |
41 | func collectionView(_ collectionView: UICollectionView,
42 | layout collectionViewLayout: UICollectionViewLayout,
43 | minimumLineSpacingForSectionAt section: Int) -> CGFloat {
44 | return 0
45 | }
46 |
47 | func collectionView(_ collectionView: UICollectionView,
48 | layout collectionViewLayout: UICollectionViewLayout,
49 | minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
50 | return 0
51 | }
52 | }
53 |
54 | override func setUp() {
55 | super.setUp()
56 | mockDataProvider = MockDataProvider()
57 | mockDataProvider.parent = self
58 | self.dataModel = [[0, 1, 2], [3, 4, 5], [6, 7]]
59 | self.stickylayout = StickyLayout(stickyConfig: StickyLayoutConfig(stickyRowsFromTop: 1,
60 | stickyRowsFromBottom: 1,
61 | stickyColsFromLeft: 1,
62 | stickyColsFromRight: 1))
63 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: stickylayout)
64 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
65 | collectionView.delegate = mockDataProvider
66 | collectionView.dataSource = mockDataProvider
67 | }
68 |
69 | func testCollectionViewCellsCount() {
70 | XCTAssertEqual(mockDataProvider.numberOfSections(in: collectionView), 3)
71 | XCTAssertEqual(mockDataProvider.collectionView(collectionView, numberOfItemsInSection: 0), 3)
72 | XCTAssertEqual(mockDataProvider.collectionView(collectionView, numberOfItemsInSection: 1), 3)
73 | XCTAssertEqual(mockDataProvider.collectionView(collectionView, numberOfItemsInSection: 2), 2)
74 | }
75 |
76 | func testCollectionViewContentSizeAfterSectionDeletion() {
77 | stickylayout.prepare()
78 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 360, height: 360))
79 | self.dataModel.remove(at: 2)
80 | self.collectionView.deleteSections(IndexSet(arrayLiteral: 2))
81 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 360, height: 240))
82 | }
83 |
84 | func testCollectionViewContentSizeAfterSectionInsertion() {
85 | stickylayout.prepare()
86 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 360, height: 360))
87 | self.dataModel.append([8, 9, 10])
88 | self.collectionView.insertSections(IndexSet(arrayLiteral: 3))
89 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 360, height: 480))
90 | }
91 |
92 | func testCollectionViewContentSizeAfterItemDeletion() {
93 | stickylayout.prepare()
94 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 360, height: 360))
95 | self.dataModel[0] = [0, 1]
96 | self.dataModel[1] = [2, 3]
97 | self.collectionView.deleteItems(at: [IndexPath(item: 2, section: 0), IndexPath(item: 2, section: 1)])
98 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 240, height: 360))
99 | }
100 |
101 | func testCollectionViewContentSizeAfterItemInsertion() {
102 | stickylayout.prepare()
103 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 360, height: 360))
104 | self.dataModel[0].append(5)
105 | self.collectionView.insertItems(at: [IndexPath(item: 3, section: 0)])
106 | XCTAssertEqual(stickylayout.collectionViewContentSize, CGSize(width: 480, height: 360))
107 | }
108 |
109 | func testCollectionViewLayoutAttributes() {
110 | stickylayout.prepare()
111 | let visibleCells = stickylayout.layoutAttributesForElements(in: CGRect(x: 0, y: 0, width: 375, height: 800))
112 | XCTAssertEqual(visibleCells?.count, 7)
113 | }
114 |
115 | func testPerformanceExample() throws {
116 | // This is an example of a performance test case.
117 | measure {
118 | // Put the code you want to measure the time of here.
119 | }
120 | }
121 |
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/example/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | excluded:
2 | - Carthage
3 | - Pods
4 | - StickyLayoutExamples/SceneDelegate.swift
5 | - StickyLayoutExamples/AppDelegate.swift
6 | disabled_rules:
7 | - todo
8 | - trailing_whitespace
9 | line_length: 150
10 | file_length: 500
11 | function_body_length: 60
12 |
--------------------------------------------------------------------------------
/example/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'StickyLayoutExamples' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for StickyLayout
9 | pod 'SwiftLint', '~> 0.39.2'
10 | pod 'StickyLayout', :path => '../'
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - StickyLayout (0.0.1)
3 | - SwiftLint (0.39.2)
4 |
5 | DEPENDENCIES:
6 | - StickyLayout (from `../`)
7 | - SwiftLint (~> 0.39.2)
8 |
9 | SPEC REPOS:
10 | trunk:
11 | - SwiftLint
12 |
13 | EXTERNAL SOURCES:
14 | StickyLayout:
15 | :path: "../"
16 |
17 | SPEC CHECKSUMS:
18 | StickyLayout: 1306565ecfdbb5f65144df6e18d73cfaa048a4ab
19 | SwiftLint: 22ccbbe3b8008684be5955693bab135e0ed6a447
20 |
21 | PODFILE CHECKSUM: ec1c79cb5e9fb0b6a68b9559492c0f56e674ba88
22 |
23 | COCOAPODS: 1.9.1
24 |
--------------------------------------------------------------------------------
/example/StickyLayout.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3EDF56DBF7ABFF03EBF88BD9 /* Pods_StickyLayoutExamples.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A5A9AE54DF969C1635A6F9E /* Pods_StickyLayoutExamples.framework */; };
11 | F823BFA42461015A00874539 /* LabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F823BFA32461015A00874539 /* LabelCell.swift */; };
12 | F82672B1245E5B630063D767 /* ExampleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F82672B0245E5B630063D767 /* ExampleController.swift */; };
13 | F826943C246216ED00A2DF1A /* StickyCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F826943B246216ED00A2DF1A /* StickyCollectionViewModel.swift */; };
14 | F87E46862458D35700F384CC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87E46852458D35700F384CC /* AppDelegate.swift */; };
15 | F87E46882458D35700F384CC /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87E46872458D35700F384CC /* SceneDelegate.swift */; };
16 | F87E468A2458D35700F384CC /* ExamplesListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87E46892458D35700F384CC /* ExamplesListController.swift */; };
17 | F87E468F2458D35900F384CC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F87E468E2458D35900F384CC /* Assets.xcassets */; };
18 | F87E46922458D35900F384CC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F87E46902458D35900F384CC /* LaunchScreen.storyboard */; };
19 | F8DD4E0A246493500082DD7D /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DD4E09246493500082DD7D /* UIColor+Extension.swift */; };
20 | F8DD4E0D246493CF0082DD7D /* CalendarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DD4E0C246493CF0082DD7D /* CalendarViewModel.swift */; };
21 | F8DD4E0F246495AE0082DD7D /* MobileExpenseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DD4E0E246495AE0082DD7D /* MobileExpenseViewModel.swift */; };
22 | F8DD4E112465EC0A0082DD7D /* SwimLeaderboardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DD4E102465EC0A0082DD7D /* SwimLeaderboardViewModel.swift */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXFileReference section */
26 | 061F21E8A761F07A0CB1D00E /* Pods-StickyLayoutExamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StickyLayoutExamples.debug.xcconfig"; path = "Target Support Files/Pods-StickyLayoutExamples/Pods-StickyLayoutExamples.debug.xcconfig"; sourceTree = ""; };
27 | 4286BF7F23805260D5122845 /* Pods-StickyLayout.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StickyLayout.debug.xcconfig"; path = "Target Support Files/Pods-StickyLayout/Pods-StickyLayout.debug.xcconfig"; sourceTree = ""; };
28 | 713AB013868A1C5181E3125E /* Pods-StickyLayoutExamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StickyLayoutExamples.release.xcconfig"; path = "Target Support Files/Pods-StickyLayoutExamples/Pods-StickyLayoutExamples.release.xcconfig"; sourceTree = ""; };
29 | 7A5A9AE54DF969C1635A6F9E /* Pods_StickyLayoutExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StickyLayoutExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; };
30 | 88551C9E7531243CB504C60D /* Pods-StickyLayout.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StickyLayout.release.xcconfig"; path = "Target Support Files/Pods-StickyLayout/Pods-StickyLayout.release.xcconfig"; sourceTree = ""; };
31 | F823BFA32461015A00874539 /* LabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCell.swift; sourceTree = ""; };
32 | F82672B0245E5B630063D767 /* ExampleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleController.swift; sourceTree = ""; };
33 | F826943B246216ED00A2DF1A /* StickyCollectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyCollectionViewModel.swift; sourceTree = ""; };
34 | F87E46822458D35700F384CC /* StickyLayoutExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StickyLayoutExamples.app; sourceTree = BUILT_PRODUCTS_DIR; };
35 | F87E46852458D35700F384CC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
36 | F87E46872458D35700F384CC /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
37 | F87E46892458D35700F384CC /* ExamplesListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesListController.swift; sourceTree = ""; };
38 | F87E468E2458D35900F384CC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
39 | F87E46912458D35900F384CC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
40 | F87E46932458D35900F384CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | F8DD4E09246493500082DD7D /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; };
42 | F8DD4E0C246493CF0082DD7D /* CalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewModel.swift; sourceTree = ""; };
43 | F8DD4E0E246495AE0082DD7D /* MobileExpenseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileExpenseViewModel.swift; sourceTree = ""; };
44 | F8DD4E102465EC0A0082DD7D /* SwimLeaderboardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwimLeaderboardViewModel.swift; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | F87E467F2458D35700F384CC /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | 3EDF56DBF7ABFF03EBF88BD9 /* Pods_StickyLayoutExamples.framework in Frameworks */,
53 | );
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXFrameworksBuildPhase section */
57 |
58 | /* Begin PBXGroup section */
59 | 4FD91049B91169BE9DB91158 /* Pods */ = {
60 | isa = PBXGroup;
61 | children = (
62 | 4286BF7F23805260D5122845 /* Pods-StickyLayout.debug.xcconfig */,
63 | 88551C9E7531243CB504C60D /* Pods-StickyLayout.release.xcconfig */,
64 | 061F21E8A761F07A0CB1D00E /* Pods-StickyLayoutExamples.debug.xcconfig */,
65 | 713AB013868A1C5181E3125E /* Pods-StickyLayoutExamples.release.xcconfig */,
66 | );
67 | path = Pods;
68 | sourceTree = "";
69 | };
70 | A8C3E6337BCB2664EBAD2E0F /* Frameworks */ = {
71 | isa = PBXGroup;
72 | children = (
73 | 7A5A9AE54DF969C1635A6F9E /* Pods_StickyLayoutExamples.framework */,
74 | );
75 | name = Frameworks;
76 | sourceTree = "";
77 | };
78 | F843AB66245B3DB90006B011 /* Storyboards */ = {
79 | isa = PBXGroup;
80 | children = (
81 | F87E46902458D35900F384CC /* LaunchScreen.storyboard */,
82 | );
83 | path = Storyboards;
84 | sourceTree = "";
85 | };
86 | F87E46792458D35700F384CC = {
87 | isa = PBXGroup;
88 | children = (
89 | F87E46842458D35700F384CC /* StickyLayoutExamples */,
90 | F87E46832458D35700F384CC /* Products */,
91 | 4FD91049B91169BE9DB91158 /* Pods */,
92 | A8C3E6337BCB2664EBAD2E0F /* Frameworks */,
93 | );
94 | sourceTree = "";
95 | };
96 | F87E46832458D35700F384CC /* Products */ = {
97 | isa = PBXGroup;
98 | children = (
99 | F87E46822458D35700F384CC /* StickyLayoutExamples.app */,
100 | );
101 | name = Products;
102 | sourceTree = "";
103 | };
104 | F87E46842458D35700F384CC /* StickyLayoutExamples */ = {
105 | isa = PBXGroup;
106 | children = (
107 | F843AB66245B3DB90006B011 /* Storyboards */,
108 | F87E46852458D35700F384CC /* AppDelegate.swift */,
109 | F87E46872458D35700F384CC /* SceneDelegate.swift */,
110 | F87E46892458D35700F384CC /* ExamplesListController.swift */,
111 | F82672B0245E5B630063D767 /* ExampleController.swift */,
112 | F823BFA32461015A00874539 /* LabelCell.swift */,
113 | F8DD4E0B246493750082DD7D /* ViewModels */,
114 | F8DD4E08246492D60082DD7D /* Extensions */,
115 | F87E468E2458D35900F384CC /* Assets.xcassets */,
116 | F87E46932458D35900F384CC /* Info.plist */,
117 | );
118 | path = StickyLayoutExamples;
119 | sourceTree = "";
120 | };
121 | F8DD4E08246492D60082DD7D /* Extensions */ = {
122 | isa = PBXGroup;
123 | children = (
124 | F8DD4E09246493500082DD7D /* UIColor+Extension.swift */,
125 | );
126 | path = Extensions;
127 | sourceTree = "";
128 | };
129 | F8DD4E0B246493750082DD7D /* ViewModels */ = {
130 | isa = PBXGroup;
131 | children = (
132 | F826943B246216ED00A2DF1A /* StickyCollectionViewModel.swift */,
133 | F8DD4E0C246493CF0082DD7D /* CalendarViewModel.swift */,
134 | F8DD4E0E246495AE0082DD7D /* MobileExpenseViewModel.swift */,
135 | F8DD4E102465EC0A0082DD7D /* SwimLeaderboardViewModel.swift */,
136 | );
137 | path = ViewModels;
138 | sourceTree = "";
139 | };
140 | /* End PBXGroup section */
141 |
142 | /* Begin PBXNativeTarget section */
143 | F87E46812458D35700F384CC /* StickyLayoutExamples */ = {
144 | isa = PBXNativeTarget;
145 | buildConfigurationList = F87E46962458D35900F384CC /* Build configuration list for PBXNativeTarget "StickyLayoutExamples" */;
146 | buildPhases = (
147 | 4360C416795FEA5DD9FC4966 /* [CP] Check Pods Manifest.lock */,
148 | F87E467E2458D35700F384CC /* Sources */,
149 | F87E467F2458D35700F384CC /* Frameworks */,
150 | F87E46802458D35700F384CC /* Resources */,
151 | F87E46992458D8F900F384CC /* ShellScript */,
152 | 465A6B531D2475C2FE6DCD76 /* [CP] Embed Pods Frameworks */,
153 | );
154 | buildRules = (
155 | );
156 | dependencies = (
157 | );
158 | name = StickyLayoutExamples;
159 | productName = StickyLayout;
160 | productReference = F87E46822458D35700F384CC /* StickyLayoutExamples.app */;
161 | productType = "com.apple.product-type.application";
162 | };
163 | /* End PBXNativeTarget section */
164 |
165 | /* Begin PBXProject section */
166 | F87E467A2458D35700F384CC /* Project object */ = {
167 | isa = PBXProject;
168 | attributes = {
169 | LastSwiftUpdateCheck = 1140;
170 | LastUpgradeCheck = 1140;
171 | ORGANIZATIONNAME = "Jeffrey Zhang";
172 | TargetAttributes = {
173 | F87E46812458D35700F384CC = {
174 | CreatedOnToolsVersion = 11.4.1;
175 | };
176 | };
177 | };
178 | buildConfigurationList = F87E467D2458D35700F384CC /* Build configuration list for PBXProject "StickyLayout" */;
179 | compatibilityVersion = "Xcode 9.3";
180 | developmentRegion = en;
181 | hasScannedForEncodings = 0;
182 | knownRegions = (
183 | en,
184 | Base,
185 | );
186 | mainGroup = F87E46792458D35700F384CC;
187 | productRefGroup = F87E46832458D35700F384CC /* Products */;
188 | projectDirPath = "";
189 | projectRoot = "";
190 | targets = (
191 | F87E46812458D35700F384CC /* StickyLayoutExamples */,
192 | );
193 | };
194 | /* End PBXProject section */
195 |
196 | /* Begin PBXResourcesBuildPhase section */
197 | F87E46802458D35700F384CC /* Resources */ = {
198 | isa = PBXResourcesBuildPhase;
199 | buildActionMask = 2147483647;
200 | files = (
201 | F87E46922458D35900F384CC /* LaunchScreen.storyboard in Resources */,
202 | F87E468F2458D35900F384CC /* Assets.xcassets in Resources */,
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | };
206 | /* End PBXResourcesBuildPhase section */
207 |
208 | /* Begin PBXShellScriptBuildPhase section */
209 | 4360C416795FEA5DD9FC4966 /* [CP] Check Pods Manifest.lock */ = {
210 | isa = PBXShellScriptBuildPhase;
211 | buildActionMask = 2147483647;
212 | files = (
213 | );
214 | inputFileListPaths = (
215 | );
216 | inputPaths = (
217 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
218 | "${PODS_ROOT}/Manifest.lock",
219 | );
220 | name = "[CP] Check Pods Manifest.lock";
221 | outputFileListPaths = (
222 | );
223 | outputPaths = (
224 | "$(DERIVED_FILE_DIR)/Pods-StickyLayoutExamples-checkManifestLockResult.txt",
225 | );
226 | runOnlyForDeploymentPostprocessing = 0;
227 | shellPath = /bin/sh;
228 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
229 | showEnvVarsInLog = 0;
230 | };
231 | 465A6B531D2475C2FE6DCD76 /* [CP] Embed Pods Frameworks */ = {
232 | isa = PBXShellScriptBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | );
236 | inputFileListPaths = (
237 | "${PODS_ROOT}/Target Support Files/Pods-StickyLayoutExamples/Pods-StickyLayoutExamples-frameworks-${CONFIGURATION}-input-files.xcfilelist",
238 | );
239 | name = "[CP] Embed Pods Frameworks";
240 | outputFileListPaths = (
241 | "${PODS_ROOT}/Target Support Files/Pods-StickyLayoutExamples/Pods-StickyLayoutExamples-frameworks-${CONFIGURATION}-output-files.xcfilelist",
242 | );
243 | runOnlyForDeploymentPostprocessing = 0;
244 | shellPath = /bin/sh;
245 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-StickyLayoutExamples/Pods-StickyLayoutExamples-frameworks.sh\"\n";
246 | showEnvVarsInLog = 0;
247 | };
248 | F87E46992458D8F900F384CC /* ShellScript */ = {
249 | isa = PBXShellScriptBuildPhase;
250 | buildActionMask = 2147483647;
251 | files = (
252 | );
253 | inputFileListPaths = (
254 | );
255 | inputPaths = (
256 | );
257 | outputFileListPaths = (
258 | );
259 | outputPaths = (
260 | );
261 | runOnlyForDeploymentPostprocessing = 0;
262 | shellPath = /bin/sh;
263 | shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n";
264 | };
265 | /* End PBXShellScriptBuildPhase section */
266 |
267 | /* Begin PBXSourcesBuildPhase section */
268 | F87E467E2458D35700F384CC /* Sources */ = {
269 | isa = PBXSourcesBuildPhase;
270 | buildActionMask = 2147483647;
271 | files = (
272 | F8DD4E0D246493CF0082DD7D /* CalendarViewModel.swift in Sources */,
273 | F8DD4E0A246493500082DD7D /* UIColor+Extension.swift in Sources */,
274 | F826943C246216ED00A2DF1A /* StickyCollectionViewModel.swift in Sources */,
275 | F87E468A2458D35700F384CC /* ExamplesListController.swift in Sources */,
276 | F8DD4E0F246495AE0082DD7D /* MobileExpenseViewModel.swift in Sources */,
277 | F87E46862458D35700F384CC /* AppDelegate.swift in Sources */,
278 | F82672B1245E5B630063D767 /* ExampleController.swift in Sources */,
279 | F87E46882458D35700F384CC /* SceneDelegate.swift in Sources */,
280 | F8DD4E112465EC0A0082DD7D /* SwimLeaderboardViewModel.swift in Sources */,
281 | F823BFA42461015A00874539 /* LabelCell.swift in Sources */,
282 | );
283 | runOnlyForDeploymentPostprocessing = 0;
284 | };
285 | /* End PBXSourcesBuildPhase section */
286 |
287 | /* Begin PBXVariantGroup section */
288 | F87E46902458D35900F384CC /* LaunchScreen.storyboard */ = {
289 | isa = PBXVariantGroup;
290 | children = (
291 | F87E46912458D35900F384CC /* Base */,
292 | );
293 | name = LaunchScreen.storyboard;
294 | sourceTree = "";
295 | };
296 | /* End PBXVariantGroup section */
297 |
298 | /* Begin XCBuildConfiguration section */
299 | F87E46942458D35900F384CC /* Debug */ = {
300 | isa = XCBuildConfiguration;
301 | buildSettings = {
302 | ALWAYS_SEARCH_USER_PATHS = NO;
303 | CLANG_ANALYZER_NONNULL = YES;
304 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
305 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
306 | CLANG_CXX_LIBRARY = "libc++";
307 | CLANG_ENABLE_MODULES = YES;
308 | CLANG_ENABLE_OBJC_ARC = YES;
309 | CLANG_ENABLE_OBJC_WEAK = YES;
310 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
311 | CLANG_WARN_BOOL_CONVERSION = YES;
312 | CLANG_WARN_COMMA = YES;
313 | CLANG_WARN_CONSTANT_CONVERSION = YES;
314 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
316 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
317 | CLANG_WARN_EMPTY_BODY = YES;
318 | CLANG_WARN_ENUM_CONVERSION = YES;
319 | CLANG_WARN_INFINITE_RECURSION = YES;
320 | CLANG_WARN_INT_CONVERSION = YES;
321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
326 | CLANG_WARN_STRICT_PROTOTYPES = YES;
327 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
328 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
329 | CLANG_WARN_UNREACHABLE_CODE = YES;
330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
331 | COPY_PHASE_STRIP = NO;
332 | DEBUG_INFORMATION_FORMAT = dwarf;
333 | ENABLE_STRICT_OBJC_MSGSEND = YES;
334 | ENABLE_TESTABILITY = YES;
335 | GCC_C_LANGUAGE_STANDARD = gnu11;
336 | GCC_DYNAMIC_NO_PIC = NO;
337 | GCC_NO_COMMON_BLOCKS = YES;
338 | GCC_OPTIMIZATION_LEVEL = 0;
339 | GCC_PREPROCESSOR_DEFINITIONS = (
340 | "DEBUG=1",
341 | "$(inherited)",
342 | );
343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
345 | GCC_WARN_UNDECLARED_SELECTOR = YES;
346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
347 | GCC_WARN_UNUSED_FUNCTION = YES;
348 | GCC_WARN_UNUSED_VARIABLE = YES;
349 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
350 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
351 | MTL_FAST_MATH = YES;
352 | ONLY_ACTIVE_ARCH = YES;
353 | SDKROOT = iphoneos;
354 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
355 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
356 | };
357 | name = Debug;
358 | };
359 | F87E46952458D35900F384CC /* Release */ = {
360 | isa = XCBuildConfiguration;
361 | buildSettings = {
362 | ALWAYS_SEARCH_USER_PATHS = NO;
363 | CLANG_ANALYZER_NONNULL = YES;
364 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
366 | CLANG_CXX_LIBRARY = "libc++";
367 | CLANG_ENABLE_MODULES = YES;
368 | CLANG_ENABLE_OBJC_ARC = YES;
369 | CLANG_ENABLE_OBJC_WEAK = YES;
370 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
371 | CLANG_WARN_BOOL_CONVERSION = YES;
372 | CLANG_WARN_COMMA = YES;
373 | CLANG_WARN_CONSTANT_CONVERSION = YES;
374 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
375 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
376 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
377 | CLANG_WARN_EMPTY_BODY = YES;
378 | CLANG_WARN_ENUM_CONVERSION = YES;
379 | CLANG_WARN_INFINITE_RECURSION = YES;
380 | CLANG_WARN_INT_CONVERSION = YES;
381 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
382 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
383 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
384 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
385 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
386 | CLANG_WARN_STRICT_PROTOTYPES = YES;
387 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
388 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
389 | CLANG_WARN_UNREACHABLE_CODE = YES;
390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
391 | COPY_PHASE_STRIP = NO;
392 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
393 | ENABLE_NS_ASSERTIONS = NO;
394 | ENABLE_STRICT_OBJC_MSGSEND = YES;
395 | GCC_C_LANGUAGE_STANDARD = gnu11;
396 | GCC_NO_COMMON_BLOCKS = YES;
397 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
398 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
399 | GCC_WARN_UNDECLARED_SELECTOR = YES;
400 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
401 | GCC_WARN_UNUSED_FUNCTION = YES;
402 | GCC_WARN_UNUSED_VARIABLE = YES;
403 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
404 | MTL_ENABLE_DEBUG_INFO = NO;
405 | MTL_FAST_MATH = YES;
406 | SDKROOT = iphoneos;
407 | SWIFT_COMPILATION_MODE = wholemodule;
408 | SWIFT_OPTIMIZATION_LEVEL = "-O";
409 | VALIDATE_PRODUCT = YES;
410 | };
411 | name = Release;
412 | };
413 | F87E46972458D35900F384CC /* Debug */ = {
414 | isa = XCBuildConfiguration;
415 | baseConfigurationReference = 061F21E8A761F07A0CB1D00E /* Pods-StickyLayoutExamples.debug.xcconfig */;
416 | buildSettings = {
417 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
418 | CODE_SIGN_STYLE = Automatic;
419 | DEVELOPMENT_TEAM = R6BGCQQ764;
420 | INFOPLIST_FILE = StickyLayoutExamples/Info.plist;
421 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
422 | LD_RUNPATH_SEARCH_PATHS = (
423 | "$(inherited)",
424 | "@executable_path/Frameworks",
425 | );
426 | PRODUCT_BUNDLE_IDENTIFIER = kocrashstudios.StickyLayout;
427 | PRODUCT_NAME = "$(TARGET_NAME)";
428 | SWIFT_VERSION = 5.0;
429 | TARGETED_DEVICE_FAMILY = "1,2";
430 | };
431 | name = Debug;
432 | };
433 | F87E46982458D35900F384CC /* Release */ = {
434 | isa = XCBuildConfiguration;
435 | baseConfigurationReference = 713AB013868A1C5181E3125E /* Pods-StickyLayoutExamples.release.xcconfig */;
436 | buildSettings = {
437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
438 | CODE_SIGN_STYLE = Automatic;
439 | DEVELOPMENT_TEAM = R6BGCQQ764;
440 | INFOPLIST_FILE = StickyLayoutExamples/Info.plist;
441 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
442 | LD_RUNPATH_SEARCH_PATHS = (
443 | "$(inherited)",
444 | "@executable_path/Frameworks",
445 | );
446 | PRODUCT_BUNDLE_IDENTIFIER = kocrashstudios.StickyLayout;
447 | PRODUCT_NAME = "$(TARGET_NAME)";
448 | SWIFT_VERSION = 5.0;
449 | TARGETED_DEVICE_FAMILY = "1,2";
450 | };
451 | name = Release;
452 | };
453 | /* End XCBuildConfiguration section */
454 |
455 | /* Begin XCConfigurationList section */
456 | F87E467D2458D35700F384CC /* Build configuration list for PBXProject "StickyLayout" */ = {
457 | isa = XCConfigurationList;
458 | buildConfigurations = (
459 | F87E46942458D35900F384CC /* Debug */,
460 | F87E46952458D35900F384CC /* Release */,
461 | );
462 | defaultConfigurationIsVisible = 0;
463 | defaultConfigurationName = Release;
464 | };
465 | F87E46962458D35900F384CC /* Build configuration list for PBXNativeTarget "StickyLayoutExamples" */ = {
466 | isa = XCConfigurationList;
467 | buildConfigurations = (
468 | F87E46972458D35900F384CC /* Debug */,
469 | F87E46982458D35900F384CC /* Release */,
470 | );
471 | defaultConfigurationIsVisible = 0;
472 | defaultConfigurationName = Release;
473 | };
474 | /* End XCConfigurationList section */
475 | };
476 | rootObject = F87E467A2458D35700F384CC /* Project object */;
477 | }
478 |
--------------------------------------------------------------------------------
/example/StickyLayout.xcodeproj/xcshareddata/xcschemes/StickyLayoutExamples.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-04-28.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
15 | // Override point for customization after application launch.
16 | return true
17 | }
18 |
19 | // MARK: UISceneSession Lifecycle
20 |
21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
22 | // Called when a new scene session is being created.
23 | // Use this method to select a configuration to create the new scene with.
24 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
25 | }
26 |
27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
28 | // Called when the user discards a scene session.
29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/ExampleController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleController.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-02.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import StickyLayout
12 |
13 | class ExampleStickyController: UIViewController {
14 |
15 | let stickyLayoutType: StickyLayoutTypes
16 | let numberOfItems = 5
17 | let stickyCollectionViewModel: StickyCollectionViewModel
18 |
19 | init(stickLayoutType: StickyLayoutTypes) {
20 | stickyLayoutType = stickLayoutType
21 | switch stickyLayoutType {
22 | case .calendar:
23 | self.stickyCollectionViewModel = CalendarViewModel()
24 | case .tabular:
25 | self.stickyCollectionViewModel = MobileExpenseViewModel()
26 | case .horizontal:
27 | self.stickyCollectionViewModel = SwimLeaderboardViewModel()
28 | }
29 | super.init(nibName: nil, bundle: nil)
30 | }
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 | self.view.backgroundColor = .white
35 | self.view.clipsToBounds = true
36 |
37 | let collectionView = createCollectionView()
38 | view.addSubview(collectionView)
39 |
40 | var topSafeAreaHeight: CGFloat = 0
41 | var bottomSafeAreaHeight: CGFloat = 0
42 | if #available(iOS 11.0, *) {
43 | let window = UIApplication.shared.windows[0]
44 | let safeFrame = window.safeAreaLayoutGuide.layoutFrame
45 | topSafeAreaHeight = safeFrame.minY
46 | bottomSafeAreaHeight = window.frame.maxY - safeFrame.maxY
47 | }
48 | NSLayoutConstraint.activate([
49 | collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant:
50 | navigationController?.navigationBar.frame.maxY ?? topSafeAreaHeight),
51 | collectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0),
52 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -bottomSafeAreaHeight),
53 | collectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0)
54 | ])
55 | }
56 |
57 | private func createCollectionView() -> UICollectionView {
58 | let stickyLayout = StickyLayout(stickyConfig: stickyCollectionViewModel.stickyConfig)
59 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: stickyLayout)
60 | collectionView.translatesAutoresizingMaskIntoConstraints = false
61 | collectionView.delegate = self
62 | collectionView.dataSource = self
63 | collectionView.showsVerticalScrollIndicator = false
64 | collectionView.showsHorizontalScrollIndicator = false
65 | collectionView.backgroundColor = stickyCollectionViewModel.backgroundColor()
66 | collectionView.register(LabelCell.self, forCellWithReuseIdentifier: LabelCell.reuseIdentifier)
67 | return collectionView
68 | }
69 |
70 | required init?(coder: NSCoder) {
71 | fatalError("init(coder:) has not been implemented")
72 | }
73 | }
74 |
75 | extension ExampleStickyController: UICollectionViewDataSource {
76 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
77 | return stickyCollectionViewModel.colCount(forRow: section)
78 | }
79 |
80 | func numberOfSections(in collectionView: UICollectionView) -> Int {
81 | return stickyCollectionViewModel.rowCount
82 | }
83 |
84 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
85 | return collectionView.dequeueReusableCell(withReuseIdentifier: LabelCell.reuseIdentifier, for: indexPath)
86 | }
87 |
88 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
89 | stickyCollectionViewModel.setCellStyle(collectionView: collectionView, indexPath: indexPath, cell: cell)
90 | }
91 | }
92 |
93 | extension ExampleStickyController: UICollectionViewDelegateFlowLayout {
94 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
95 | sizeForItemAt indexPath: IndexPath) -> CGSize {
96 | return stickyCollectionViewModel.getCellSize(indexPath: indexPath)
97 | }
98 |
99 | func collectionView(_ collectionView: UICollectionView,
100 | layout collectionViewLayout: UICollectionViewLayout,
101 | minimumLineSpacingForSectionAt section: Int) -> CGFloat {
102 | return stickyCollectionViewModel.sectionSpacing()
103 | }
104 |
105 | func collectionView(_ collectionView: UICollectionView,
106 | layout collectionViewLayout: UICollectionViewLayout,
107 | minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
108 | return stickyCollectionViewModel.interItemSpacing()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/ExamplesListController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-04-28.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum StickyLayoutTypes {
12 | case calendar
13 | case tabular
14 | case horizontal
15 |
16 | func title() -> String {
17 | switch self {
18 | case .calendar:
19 | return "Calendar"
20 | case .tabular:
21 | return "Tabular"
22 | case .horizontal:
23 | return "Horizontal"
24 | }
25 | }
26 | }
27 |
28 | class ExamplesListController: UITableViewController {
29 |
30 | private let examples: [StickyLayoutTypes] = [.calendar, .tabular, .horizontal]
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 | // Do any additional setup after loading the view.
35 | title = "Sticky Layout Examples"
36 | self.view.backgroundColor = .white
37 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
38 | }
39 |
40 | override func viewWillAppear(_ animated: Bool) {
41 | super.viewWillAppear(animated)
42 | }
43 |
44 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
45 | return examples.count
46 | }
47 |
48 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
49 | return 40
50 | }
51 |
52 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
53 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
54 | cell.textLabel?.text = examples[indexPath.row].title()
55 | return cell
56 | }
57 |
58 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
59 | navigationController?.pushViewController(ExampleStickyController(stickLayoutType: examples[indexPath.row]), animated: true)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/Extensions/UIColor+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Extension.swift
3 | // StickyLayoutExamples
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-07.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIColor {
13 | public convenience init?(hex: String) {
14 | let red, green, blue, alpha: CGFloat
15 |
16 | if hex.hasPrefix("#") {
17 | let start = hex.index(hex.startIndex, offsetBy: 1)
18 | let hexColor = String(hex[start...])
19 |
20 | if hexColor.count == 8 {
21 | let scanner = Scanner(string: hexColor)
22 | var hexNumber: UInt64 = 0
23 |
24 | if scanner.scanHexInt64(&hexNumber) {
25 | red = CGFloat((hexNumber & 0xff000000) >> 24) / 255
26 | green = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
27 | blue = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
28 | alpha = CGFloat(hexNumber & 0x000000ff) / 255
29 |
30 | self.init(red: red, green: green, blue: blue, alpha: alpha)
31 | return
32 | }
33 | }
34 | }
35 | return nil
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 |
50 | UISupportedInterfaceOrientations~ipad
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationPortraitUpsideDown
54 | UIInterfaceOrientationLandscapeLeft
55 | UIInterfaceOrientationLandscapeRight
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/LabelCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarCells.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-04.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | protocol Cell: UICollectionViewCell {
13 | static var reuseIdentifier: String { get }
14 | }
15 |
16 | public class LabelCell: UICollectionViewCell, Cell {
17 |
18 | static let reuseIdentifier: String = "LabelCell"
19 | lazy var label = UILabel()
20 |
21 | override init(frame: CGRect) {
22 | super.init(frame: frame)
23 | label.textAlignment = .center
24 | self.contentView.addSubview(label)
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-04-28.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let windowScene = (scene as? UIWindowScene) else { return }
20 |
21 | let navigationController = UINavigationController(rootViewController: ExamplesListController())
22 | window = UIWindow(frame: UIScreen.main.bounds)
23 | window?.rootViewController = navigationController
24 | window?.windowScene = windowScene
25 | window?.makeKeyAndVisible()
26 | }
27 |
28 | func sceneDidDisconnect(_ scene: UIScene) {
29 | // Called as the scene is being released by the system.
30 | // This occurs shortly after the scene enters the background, or when its session is discarded.
31 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
32 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
33 | }
34 |
35 | func sceneDidBecomeActive(_ scene: UIScene) {
36 | // Called when the scene has moved from an inactive state to an active state.
37 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
38 | }
39 |
40 | func sceneWillResignActive(_ scene: UIScene) {
41 | // Called when the scene will move from an active state to an inactive state.
42 | // This may occur due to temporary interruptions (ex. an incoming phone call).
43 | }
44 |
45 | func sceneWillEnterForeground(_ scene: UIScene) {
46 | // Called as the scene transitions from the background to the foreground.
47 | // Use this method to undo the changes made on entering the background.
48 | }
49 |
50 | func sceneDidEnterBackground(_ scene: UIScene) {
51 | // Called as the scene transitions from the foreground to the background.
52 | // Use this method to save data, release shared resources, and store enough scene-specific state information
53 | // to restore the scene back to its current state.
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/Storyboards/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/ViewModels/CalendarViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarViewModel.swift
3 | // StickyLayoutExamples
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-07.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import StickyLayout
11 |
12 | public class CalendarViewModel: StickyCollectionViewModel {
13 | var stickyConfig = StickyLayoutConfig(stickyRowsFromTop: 1,
14 | stickyRowsFromBottom: 0,
15 | stickyColsFromLeft: 1,
16 | stickyColsFromRight: 0)
17 |
18 | private let cellText = [
19 | ["May 2020"],
20 | ["", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"],
21 | ["Week 1", "26", "27", "28", "29", "30", "1", "2"],
22 | ["Week 2", "3", "4", "5", "6", "7", "8", "9"],
23 | ["Week 3", "10", "11", "12", "13", "14", "15", "16"],
24 | ["Week 4", "17", "18", "19", "20", "21", "22", "23"],
25 | ["Week 5", "24", "25", "26", "27", "28", "29", "30"],
26 | ["Week 6", "31", "", "", "", "", "", ""]
27 | ]
28 |
29 | var rowCount: Int {
30 | return cellText.count
31 | }
32 |
33 | func colCount(forRow row: Int) -> Int {
34 | cellText[row].count
35 | }
36 |
37 | func backgroundColor() -> UIColor {
38 | return UIColor(hex: "#E26D56ff")!
39 | }
40 |
41 | func interItemSpacing() -> CGFloat {
42 | return 0
43 | }
44 |
45 | func sectionSpacing() -> CGFloat {
46 | return 0
47 | }
48 |
49 | func getCellSize(indexPath: IndexPath) -> CGSize {
50 | if indexPath.section == 0 {
51 | return CGSize(width: 375, height: 60)
52 | } else if indexPath.item == 0 {
53 | return CGSize(width: 80, height: 34.3)
54 | } else {
55 | return CGSize(width: 70, height: 34.3)
56 | }
57 | }
58 |
59 | func setCellStyle(collectionView: UICollectionView, indexPath: IndexPath, cell: UICollectionViewCell) {
60 | guard let cell = cell as? LabelCell else {
61 | return
62 | }
63 | if indexPath.section == 0 {
64 | cell.backgroundColor = UIColor(hex: "#555c64ff")
65 | cell.label.font = UIFont.boldSystemFont(ofSize: 20.0)
66 | } else if indexPath.item == 0 || indexPath.section == 1 {
67 | cell.backgroundColor = UIColor(hex: "#EB7059ff")
68 | cell.label.font = UIFont.boldSystemFont(ofSize: 12.0)
69 | } else {
70 | cell.backgroundColor = UIColor(hex: "#fa775eff")
71 | cell.label.font = UIFont.systemFont(ofSize: 12.0)
72 | }
73 | cell.label.textColor = .white
74 | cell.label.text = cellText[indexPath.section][indexPath.item]
75 | cell.label.alpha = (indexPath.section == 2 && indexPath.item < 5 && indexPath.item > 0) ? 0.7 : 1
76 | cell.label.frame = cell.bounds
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/ViewModels/MobileExpenseViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MobileExpenseViewModel.swift
3 | // StickyLayoutExamples
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-07.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import StickyLayout
11 |
12 | public class MobileExpenseViewModel: StickyCollectionViewModel {
13 | var stickyConfig = StickyLayoutConfig(stickyRowsFromTop: 1,
14 | stickyRowsFromBottom: 2,
15 | stickyColsFromLeft: 1,
16 | stickyColsFromRight: 0)
17 |
18 | private let messagesCosts = [20, 47, 30, 23, 34, 13, 15, 42, 53, 12, 43, 12, 34, 45, 65]
19 | private let minutesCosts = [5, 41, 40, 22, 74, 43, 15, 122, 23, 14, 0, 2, 0, 93, 11]
20 | private let dataCosts = [4, 0, 340, 23, 31, 13, 25, 32, 6, 33, 90, 22, 8, 0, 10]
21 | private let longDistanceCosts = [14, 24, 43, 2, 21, 23, 54, 43, 54, 2, 0, 0, 4, 5, 0]
22 |
23 | private var costLists: [[Int]] {
24 | return [messagesCosts, minutesCosts, dataCosts, longDistanceCosts]
25 | }
26 |
27 | private var messagesTotalCost: Int {
28 | return messagesCosts.reduce(0, +)
29 | }
30 |
31 | private var minutesTotalCost: Int {
32 | return minutesCosts.reduce(0, +)
33 | }
34 |
35 | private var dataTotalCost: Int {
36 | return dataCosts.reduce(0, +)
37 | }
38 |
39 | private var longDistanceTotalCost: Int {
40 | return longDistanceCosts.reduce(0, +)
41 | }
42 |
43 | private var totalExpense: Int {
44 | return messagesTotalCost + minutesTotalCost + dataTotalCost + longDistanceTotalCost
45 | }
46 |
47 | private let names = ["Charley", "Dan", "Jacob", "Olivia", "David", "Heather", "Beth", "John",
48 | "Jazz", "Simon", "Don", "Ronald", "Jay", "Carl", "Luke"]
49 |
50 | private var cellText: [[String]] = []
51 |
52 | init() {
53 | var cellText = [["Names", "Messages", "Minutes", "Data", "Long Distance"]]
54 | for index in 0.. Int {
83 | cellText[row].count
84 | }
85 |
86 | func backgroundColor() -> UIColor {
87 | return UIColor(hex: "#EAEAEAff")!
88 | }
89 |
90 | func interItemSpacing() -> CGFloat {
91 | return 0
92 | }
93 |
94 | func sectionSpacing() -> CGFloat {
95 | return 0
96 | }
97 |
98 | func getCellSize(indexPath: IndexPath) -> CGSize {
99 | if (rowCount - indexPath.section <= 2) && (indexPath.item == colCount(forRow: indexPath.section) - 1) {
100 | return CGSize(width: 140, height: 50)
101 | } else if rowCount - indexPath.section <= 2 {
102 | return CGSize(width: 100, height: 50)
103 | } else if indexPath.item == colCount(forRow: indexPath.section) - 1 {
104 | return CGSize(width: 140, height: 50)
105 | }
106 | return CGSize(width: 100, height: 50)
107 | }
108 |
109 | func setCellStyle(collectionView: UICollectionView, indexPath: IndexPath, cell: UICollectionViewCell) {
110 | guard let cell = cell as? LabelCell else {
111 | return
112 | }
113 |
114 | cell.label.textColor = .black
115 | if indexPath.section == 0 {
116 | cell.backgroundColor = UIColor(hex: "#F4F4F4ff")
117 | cell.label.textColor = UIColor(hex: "#7c9dfcff")
118 | } else if rowCount - indexPath.section <= 2 {
119 | cell.backgroundColor = UIColor(hex: "#E5E5E5ff")
120 | } else if indexPath.section % 2 == 0 {
121 | cell.backgroundColor = .white
122 | } else if indexPath.section % 2 == 1 {
123 | cell.backgroundColor = UIColor(hex: "#eff0f2ff")
124 | }
125 | cell.label.text = cellText[indexPath.section][indexPath.item]
126 | cell.label.alpha = (indexPath.section == 0 || indexPath.item == 0 || rowCount - indexPath.section <= 2) ? 1 : 0.5
127 | cell.label.font = (indexPath.section == 0 || indexPath.item == 0) ? UIFont.boldSystemFont(ofSize: 12) : UIFont.systemFont(ofSize: 12)
128 | cell.label.frame = cell.bounds
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/ViewModels/StickyCollectionViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StickyCollectionViewModel.swift
3 | // StickyLayout
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-05.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import StickyLayout
11 |
12 | protocol StickyCollectionViewModel {
13 | //Sticky Configuration
14 | var stickyConfig: StickyLayoutConfig { get }
15 |
16 | // Dimensions
17 | var rowCount: Int { get }
18 | func colCount(forRow row: Int) -> Int
19 |
20 | // Style
21 | func backgroundColor() -> UIColor
22 |
23 | // Spacing
24 | func interItemSpacing() -> CGFloat
25 | func sectionSpacing() -> CGFloat
26 |
27 | // Sizing
28 | func getCellSize(indexPath: IndexPath) -> CGSize
29 |
30 | // Cell Style
31 | func setCellStyle(collectionView: UICollectionView, indexPath: IndexPath, cell: UICollectionViewCell)
32 | }
33 |
--------------------------------------------------------------------------------
/example/StickyLayoutExamples/ViewModels/SwimLeaderboardViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwimLeaderboardViewModel.swift
3 | // StickyLayoutExamples
4 | //
5 | // Created by Jeffrey Zhang on 2020-05-08.
6 | // Copyright © 2020 Jeffrey Zhang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import StickyLayout
11 |
12 | public class SwimLeaderboardViewModel: StickyCollectionViewModel {
13 | var stickyConfig = StickyLayoutConfig(stickyRowsFromTop: 1,
14 | stickyRowsFromBottom: 0,
15 | stickyColsFromLeft: 1,
16 | stickyColsFromRight: 1)
17 |
18 | private let splitIntervals = [[55, 63, 44, 60, 50],
19 | [55, 63, 44, 60, 50],
20 | [65, 53, 65, 66, 57],
21 | [68, 63, 80, 67, 60],
22 | [59, 63, 90, 60, 55],
23 | [60, 69, 63, 70, 62],
24 | [62, 53, 54, 51, 63],
25 | [67, 55, 64, 59, 55]]
26 |
27 | private let swimmers = ["Phelps", "Lochte", "Henry", "John", "Larry"]
28 |
29 | private var cellText: [[String]] = []
30 |
31 | init() {
32 | var cellText = [["Swimmers", "Lap 1", "Lap 2", "Lap 3", "Lap 4", "Lap 5", "Lap 6", "Lap 7", "Lap 8", "Total"]]
33 | for index in 0.. Int {
52 | return cellText[row].count
53 | }
54 |
55 | func backgroundColor() -> UIColor {
56 | return UIColor(hex: "#C3F2FDff")!
57 | }
58 |
59 | func interItemSpacing() -> CGFloat {
60 | return 0
61 | }
62 |
63 | func sectionSpacing() -> CGFloat {
64 | return 0
65 | }
66 |
67 | func getCellSize(indexPath: IndexPath) -> CGSize {
68 | return CGSize(width: 80, height: 50)
69 | }
70 |
71 | func setCellStyle(collectionView: UICollectionView, indexPath: IndexPath, cell: UICollectionViewCell) {
72 | guard let cell = cell as? LabelCell else {
73 | return
74 | }
75 | let isFirstRow = indexPath.section == 0
76 | let isLastCol = indexPath.item == colCount(forRow: indexPath.section) - 1
77 | let isFirstCol = indexPath.item == 0
78 |
79 | cell.label.frame = cell.bounds
80 | cell.label.text = cellText[indexPath.section][indexPath.item]
81 | if isFirstRow {
82 | cell.backgroundColor = UIColor(hex: "#8EE5F9ff")
83 | } else if isLastCol || isFirstCol {
84 | cell.backgroundColor = UIColor(hex: "#61D0EAff")
85 | } else {
86 | cell.backgroundColor = UIColor(hex: "#5BC6DFff")
87 | }
88 | cell.label.textColor = .white
89 | cell.label.alpha = (isFirstRow || isFirstCol || isLastCol) ? 1 : 0.7
90 | cell.label.font = (isFirstRow || isFirstCol || isLastCol) ? UIFont.boldSystemFont(ofSize: 12) : UIFont.systemFont(ofSize: 12)
91 | }
92 |
93 | private func formatInterval(interval: Int) -> String {
94 | let formatter = DateComponentsFormatter()
95 | formatter.allowedUnits = [.hour, .minute, .second]
96 | formatter.unitsStyle = .positional
97 | let formattedString = formatter.string(from: TimeInterval(interval))!
98 | return formattedString
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/reports/cobertura.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | /Users/jeffreyzhang/git/StickyLayout
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 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------