├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── Example
├── InfiniteLayout.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── InfiniteLayout-Example.xcscheme
├── InfiniteLayout
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.xib
│ │ └── Main.storyboard
│ ├── BaseCollectionViewController.swift
│ ├── Cell.swift
│ ├── CustomLayout.swift
│ ├── CustomViewController.swift
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Info.plist
│ ├── PickerController.swift
│ └── RxBaseCollectionViewController.swift
├── Podfile
├── Podfile.lock
└── Tests
│ ├── Info.plist
│ └── Tests.swift
├── InfiniteLayout.podspec
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── CocoaProxy
│ ├── CocoaProxy.h
│ └── CocoaProxy.m
├── InfiniteLayout
│ ├── InfiniteCollectionView.swift
│ ├── InfiniteCollectionViewController.swift
│ ├── InfiniteCollectionViewProxy.swift
│ ├── InfiniteDataSource.swift
│ ├── InfiniteLayout.swift
│ └── SPMBridge.swift
└── Rx
│ ├── InfiniteCollectionView+Rx.swift
│ ├── RxInfiniteCollectionView.swift
│ └── RxInfiniteCollectionViewDataSource.swift
├── _Pods.xcodeproj
├── custom.gif
├── delegate.gif
├── horizontal.gif
└── vertical.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | Carthage
26 | # We recommend against adding the Pods directory to your .gitignore. However
27 | # you should judge for yourself, the pros and cons are mentioned at:
28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
29 | #
30 | # Note: if you ignore the Pods directory, make sure to uncomment
31 | # `pod install` in .travis.yml
32 | #
33 | # Pods/
34 | /Example/InfiniteLayout.xcworkspace
35 | /Example/Pods
36 | /Example/InfiniteLayout.xcodeproj/project.xcworkspace/xcshareddata
37 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * http://www.objc.io/issue-6/travis-ci.html
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | osx_image: xcode10.2
6 | language: swift
7 | before_install:
8 | - gem install cocoapods # Since Travis is not always on latest version
9 | - pod repo update
10 | - pod install --project-directory=Example
11 | script:
12 | - set -o pipefail && xcodebuild -workspace Example/InfiniteLayout.xcworkspace -scheme InfiniteLayout-Example -sdk iphonesimulator build CODE_SIGNING_REQUIRED=NO | xcpretty -c
13 | - pod lib lint
14 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5259B95F4E58F763F6C70333 /* Pods_InfiniteLayout_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6830D8B970E919B725A0D5E /* Pods_InfiniteLayout_Example.framework */; };
11 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
12 | 607FACD81AFB9204008FA782 /* CustomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* CustomViewController.swift */; };
13 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
14 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
15 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
16 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
17 | D403973F1FEBFA9D006C41D2 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D403973E1FEBFA9D006C41D2 /* Cell.swift */; };
18 | D4162A391FF502A900AC2572 /* CustomLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4162A381FF502A900AC2572 /* CustomLayout.swift */; };
19 | D43362D11FF64B330040C679 /* BaseCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43362CF1FF64B330040C679 /* BaseCollectionViewController.swift */; };
20 | D495544E1FFD3D920081225B /* RxBaseCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D495544D1FFD3D920081225B /* RxBaseCollectionViewController.swift */; };
21 | D4B9816E1FFCCC900016C676 /* PickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B9816D1FFCCC900016C676 /* PickerController.swift */; };
22 | E997A539C8278E7A661C9F91 /* Pods_InfiniteLayout_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C50F79B0DFFDE95D322480B2 /* Pods_InfiniteLayout_Tests.framework */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXContainerItemProxy section */
26 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
29 | proxyType = 1;
30 | remoteGlobalIDString = 607FACCF1AFB9204008FA782;
31 | remoteInfo = InfiniteLayout;
32 | };
33 | /* End PBXContainerItemProxy section */
34 |
35 | /* Begin PBXFileReference section */
36 | 17E9A86BF3432BF233E3D297 /* Pods-InfiniteLayout_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InfiniteLayout_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InfiniteLayout_Example/Pods-InfiniteLayout_Example.debug.xcconfig"; sourceTree = ""; };
37 | 2E68337E21F08944069DC023 /* Pods-InfiniteLayout_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InfiniteLayout_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-InfiniteLayout_Tests/Pods-InfiniteLayout_Tests.release.xcconfig"; sourceTree = ""; };
38 | 5DA98D9A451E79A872E96E69 /* InfiniteLayout.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = InfiniteLayout.podspec; path = ../InfiniteLayout.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
39 | 607FACD01AFB9204008FA782 /* InfiniteLayout_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InfiniteLayout_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
40 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
42 | 607FACD71AFB9204008FA782 /* CustomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomViewController.swift; sourceTree = ""; };
43 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
44 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
45 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
46 | 607FACE51AFB9204008FA782 /* InfiniteLayout_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InfiniteLayout_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; };
49 | A6830D8B970E919B725A0D5E /* Pods_InfiniteLayout_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_InfiniteLayout_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50 | AD3D900D0067605DEAFF148B /* Pods-InfiniteLayout_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InfiniteLayout_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-InfiniteLayout_Example/Pods-InfiniteLayout_Example.release.xcconfig"; sourceTree = ""; };
51 | C50F79B0DFFDE95D322480B2 /* Pods_InfiniteLayout_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_InfiniteLayout_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
52 | D403973E1FEBFA9D006C41D2 /* Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; };
53 | D4162A381FF502A900AC2572 /* CustomLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLayout.swift; sourceTree = ""; };
54 | D43362CF1FF64B330040C679 /* BaseCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCollectionViewController.swift; sourceTree = ""; };
55 | D495544D1FFD3D920081225B /* RxBaseCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxBaseCollectionViewController.swift; sourceTree = ""; };
56 | D4B9816D1FFCCC900016C676 /* PickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerController.swift; sourceTree = ""; };
57 | EF577FD009F0E2F96F1CECC6 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
58 | F900DDE615FA1056EB58998B /* Pods-InfiniteLayout_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InfiniteLayout_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InfiniteLayout_Tests/Pods-InfiniteLayout_Tests.debug.xcconfig"; sourceTree = ""; };
59 | FD6AC573B93C313B5BDFF3FE /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
60 | /* End PBXFileReference section */
61 |
62 | /* Begin PBXFrameworksBuildPhase section */
63 | 607FACCD1AFB9204008FA782 /* Frameworks */ = {
64 | isa = PBXFrameworksBuildPhase;
65 | buildActionMask = 2147483647;
66 | files = (
67 | 5259B95F4E58F763F6C70333 /* Pods_InfiniteLayout_Example.framework in Frameworks */,
68 | );
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | 607FACE21AFB9204008FA782 /* Frameworks */ = {
72 | isa = PBXFrameworksBuildPhase;
73 | buildActionMask = 2147483647;
74 | files = (
75 | E997A539C8278E7A661C9F91 /* Pods_InfiniteLayout_Tests.framework in Frameworks */,
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | /* End PBXFrameworksBuildPhase section */
80 |
81 | /* Begin PBXGroup section */
82 | 607FACC71AFB9204008FA782 = {
83 | isa = PBXGroup;
84 | children = (
85 | 607FACF51AFB993E008FA782 /* Podspec Metadata */,
86 | 607FACD21AFB9204008FA782 /* Example for InfiniteLayout */,
87 | 607FACE81AFB9204008FA782 /* Tests */,
88 | 607FACD11AFB9204008FA782 /* Products */,
89 | E938139B99B9D91C14707C07 /* Pods */,
90 | B8E153BE0A71F9235FE984C6 /* Frameworks */,
91 | );
92 | sourceTree = "";
93 | };
94 | 607FACD11AFB9204008FA782 /* Products */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 607FACD01AFB9204008FA782 /* InfiniteLayout_Example.app */,
98 | 607FACE51AFB9204008FA782 /* InfiniteLayout_Tests.xctest */,
99 | );
100 | name = Products;
101 | sourceTree = "";
102 | };
103 | 607FACD21AFB9204008FA782 /* Example for InfiniteLayout */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */,
107 | D4B981781FFD1FEF0016C676 /* Swift */,
108 | D4B981791FFD1FF50016C676 /* Rx */,
109 | 607FACD91AFB9204008FA782 /* Main.storyboard */,
110 | 607FACDC1AFB9204008FA782 /* Images.xcassets */,
111 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
112 | 607FACD31AFB9204008FA782 /* Supporting Files */,
113 | );
114 | name = "Example for InfiniteLayout";
115 | path = InfiniteLayout;
116 | sourceTree = "";
117 | };
118 | 607FACD31AFB9204008FA782 /* Supporting Files */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 607FACD41AFB9204008FA782 /* Info.plist */,
122 | );
123 | name = "Supporting Files";
124 | sourceTree = "";
125 | };
126 | 607FACE81AFB9204008FA782 /* Tests */ = {
127 | isa = PBXGroup;
128 | children = (
129 | 607FACEB1AFB9204008FA782 /* Tests.swift */,
130 | 607FACE91AFB9204008FA782 /* Supporting Files */,
131 | );
132 | path = Tests;
133 | sourceTree = "";
134 | };
135 | 607FACE91AFB9204008FA782 /* Supporting Files */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 607FACEA1AFB9204008FA782 /* Info.plist */,
139 | );
140 | name = "Supporting Files";
141 | sourceTree = "";
142 | };
143 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
144 | isa = PBXGroup;
145 | children = (
146 | 5DA98D9A451E79A872E96E69 /* InfiniteLayout.podspec */,
147 | FD6AC573B93C313B5BDFF3FE /* README.md */,
148 | EF577FD009F0E2F96F1CECC6 /* LICENSE */,
149 | );
150 | name = "Podspec Metadata";
151 | sourceTree = "";
152 | };
153 | B8E153BE0A71F9235FE984C6 /* Frameworks */ = {
154 | isa = PBXGroup;
155 | children = (
156 | A6830D8B970E919B725A0D5E /* Pods_InfiniteLayout_Example.framework */,
157 | C50F79B0DFFDE95D322480B2 /* Pods_InfiniteLayout_Tests.framework */,
158 | );
159 | name = Frameworks;
160 | sourceTree = "";
161 | };
162 | D43362CC1FF64AD30040C679 /* Custom */ = {
163 | isa = PBXGroup;
164 | children = (
165 | 607FACD71AFB9204008FA782 /* CustomViewController.swift */,
166 | D4162A381FF502A900AC2572 /* CustomLayout.swift */,
167 | );
168 | name = Custom;
169 | sourceTree = "";
170 | };
171 | D43362D31FF64B9E0040C679 /* Base */ = {
172 | isa = PBXGroup;
173 | children = (
174 | D403973E1FEBFA9D006C41D2 /* Cell.swift */,
175 | D43362CF1FF64B330040C679 /* BaseCollectionViewController.swift */,
176 | );
177 | name = Base;
178 | sourceTree = "";
179 | };
180 | D4B9816C1FFCCC5B0016C676 /* Picker */ = {
181 | isa = PBXGroup;
182 | children = (
183 | D4B9816D1FFCCC900016C676 /* PickerController.swift */,
184 | );
185 | name = Picker;
186 | sourceTree = "";
187 | };
188 | D4B981781FFD1FEF0016C676 /* Swift */ = {
189 | isa = PBXGroup;
190 | children = (
191 | D43362D31FF64B9E0040C679 /* Base */,
192 | D43362CC1FF64AD30040C679 /* Custom */,
193 | D4B9816C1FFCCC5B0016C676 /* Picker */,
194 | );
195 | name = Swift;
196 | sourceTree = "";
197 | };
198 | D4B981791FFD1FF50016C676 /* Rx */ = {
199 | isa = PBXGroup;
200 | children = (
201 | D495544D1FFD3D920081225B /* RxBaseCollectionViewController.swift */,
202 | );
203 | name = Rx;
204 | sourceTree = "";
205 | };
206 | E938139B99B9D91C14707C07 /* Pods */ = {
207 | isa = PBXGroup;
208 | children = (
209 | 17E9A86BF3432BF233E3D297 /* Pods-InfiniteLayout_Example.debug.xcconfig */,
210 | AD3D900D0067605DEAFF148B /* Pods-InfiniteLayout_Example.release.xcconfig */,
211 | F900DDE615FA1056EB58998B /* Pods-InfiniteLayout_Tests.debug.xcconfig */,
212 | 2E68337E21F08944069DC023 /* Pods-InfiniteLayout_Tests.release.xcconfig */,
213 | );
214 | name = Pods;
215 | sourceTree = "";
216 | };
217 | /* End PBXGroup section */
218 |
219 | /* Begin PBXNativeTarget section */
220 | 607FACCF1AFB9204008FA782 /* InfiniteLayout_Example */ = {
221 | isa = PBXNativeTarget;
222 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "InfiniteLayout_Example" */;
223 | buildPhases = (
224 | DF297DCD753BC67E73166377 /* [CP] Check Pods Manifest.lock */,
225 | 607FACCC1AFB9204008FA782 /* Sources */,
226 | 607FACCD1AFB9204008FA782 /* Frameworks */,
227 | 607FACCE1AFB9204008FA782 /* Resources */,
228 | 36EEEA4F62F7A41DC7E00F9B /* [CP] Embed Pods Frameworks */,
229 | );
230 | buildRules = (
231 | );
232 | dependencies = (
233 | );
234 | name = InfiniteLayout_Example;
235 | productName = InfiniteLayout;
236 | productReference = 607FACD01AFB9204008FA782 /* InfiniteLayout_Example.app */;
237 | productType = "com.apple.product-type.application";
238 | };
239 | 607FACE41AFB9204008FA782 /* InfiniteLayout_Tests */ = {
240 | isa = PBXNativeTarget;
241 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "InfiniteLayout_Tests" */;
242 | buildPhases = (
243 | BB5E5FB53E7C451064DBC9A2 /* [CP] Check Pods Manifest.lock */,
244 | 607FACE11AFB9204008FA782 /* Sources */,
245 | 607FACE21AFB9204008FA782 /* Frameworks */,
246 | 607FACE31AFB9204008FA782 /* Resources */,
247 | );
248 | buildRules = (
249 | );
250 | dependencies = (
251 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */,
252 | );
253 | name = InfiniteLayout_Tests;
254 | productName = Tests;
255 | productReference = 607FACE51AFB9204008FA782 /* InfiniteLayout_Tests.xctest */;
256 | productType = "com.apple.product-type.bundle.unit-test";
257 | };
258 | /* End PBXNativeTarget section */
259 |
260 | /* Begin PBXProject section */
261 | 607FACC81AFB9204008FA782 /* Project object */ = {
262 | isa = PBXProject;
263 | attributes = {
264 | LastSwiftUpdateCheck = 0830;
265 | LastUpgradeCheck = 1020;
266 | ORGANIZATIONNAME = CocoaPods;
267 | TargetAttributes = {
268 | 607FACCF1AFB9204008FA782 = {
269 | CreatedOnToolsVersion = 6.3.1;
270 | DevelopmentTeam = CPE39ZT2AY;
271 | LastSwiftMigration = 1020;
272 | };
273 | 607FACE41AFB9204008FA782 = {
274 | CreatedOnToolsVersion = 6.3.1;
275 | DevelopmentTeam = CPE39ZT2AY;
276 | LastSwiftMigration = 1020;
277 | TestTargetID = 607FACCF1AFB9204008FA782;
278 | };
279 | };
280 | };
281 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "InfiniteLayout" */;
282 | compatibilityVersion = "Xcode 3.2";
283 | developmentRegion = English;
284 | hasScannedForEncodings = 0;
285 | knownRegions = (
286 | English,
287 | en,
288 | Base,
289 | );
290 | mainGroup = 607FACC71AFB9204008FA782;
291 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
292 | projectDirPath = "";
293 | projectRoot = "";
294 | targets = (
295 | 607FACCF1AFB9204008FA782 /* InfiniteLayout_Example */,
296 | 607FACE41AFB9204008FA782 /* InfiniteLayout_Tests */,
297 | );
298 | };
299 | /* End PBXProject section */
300 |
301 | /* Begin PBXResourcesBuildPhase section */
302 | 607FACCE1AFB9204008FA782 /* Resources */ = {
303 | isa = PBXResourcesBuildPhase;
304 | buildActionMask = 2147483647;
305 | files = (
306 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
307 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
308 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
309 | );
310 | runOnlyForDeploymentPostprocessing = 0;
311 | };
312 | 607FACE31AFB9204008FA782 /* Resources */ = {
313 | isa = PBXResourcesBuildPhase;
314 | buildActionMask = 2147483647;
315 | files = (
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | };
319 | /* End PBXResourcesBuildPhase section */
320 |
321 | /* Begin PBXShellScriptBuildPhase section */
322 | 36EEEA4F62F7A41DC7E00F9B /* [CP] Embed Pods Frameworks */ = {
323 | isa = PBXShellScriptBuildPhase;
324 | buildActionMask = 2147483647;
325 | files = (
326 | );
327 | inputPaths = (
328 | "${PODS_ROOT}/Target Support Files/Pods-InfiniteLayout_Example/Pods-InfiniteLayout_Example-frameworks.sh",
329 | "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework",
330 | "${BUILT_PRODUCTS_DIR}/InfiniteLayout/InfiniteLayout.framework",
331 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
332 | "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework",
333 | "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework",
334 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
335 | );
336 | name = "[CP] Embed Pods Frameworks";
337 | outputPaths = (
338 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework",
339 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/InfiniteLayout.framework",
340 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
341 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework",
342 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework",
343 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
344 | );
345 | runOnlyForDeploymentPostprocessing = 0;
346 | shellPath = /bin/sh;
347 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-InfiniteLayout_Example/Pods-InfiniteLayout_Example-frameworks.sh\"\n";
348 | showEnvVarsInLog = 0;
349 | };
350 | BB5E5FB53E7C451064DBC9A2 /* [CP] Check Pods Manifest.lock */ = {
351 | isa = PBXShellScriptBuildPhase;
352 | buildActionMask = 2147483647;
353 | files = (
354 | );
355 | inputPaths = (
356 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
357 | "${PODS_ROOT}/Manifest.lock",
358 | );
359 | name = "[CP] Check Pods Manifest.lock";
360 | outputPaths = (
361 | "$(DERIVED_FILE_DIR)/Pods-InfiniteLayout_Tests-checkManifestLockResult.txt",
362 | );
363 | runOnlyForDeploymentPostprocessing = 0;
364 | shellPath = /bin/sh;
365 | 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";
366 | showEnvVarsInLog = 0;
367 | };
368 | DF297DCD753BC67E73166377 /* [CP] Check Pods Manifest.lock */ = {
369 | isa = PBXShellScriptBuildPhase;
370 | buildActionMask = 2147483647;
371 | files = (
372 | );
373 | inputPaths = (
374 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
375 | "${PODS_ROOT}/Manifest.lock",
376 | );
377 | name = "[CP] Check Pods Manifest.lock";
378 | outputPaths = (
379 | "$(DERIVED_FILE_DIR)/Pods-InfiniteLayout_Example-checkManifestLockResult.txt",
380 | );
381 | runOnlyForDeploymentPostprocessing = 0;
382 | shellPath = /bin/sh;
383 | 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";
384 | showEnvVarsInLog = 0;
385 | };
386 | /* End PBXShellScriptBuildPhase section */
387 |
388 | /* Begin PBXSourcesBuildPhase section */
389 | 607FACCC1AFB9204008FA782 /* Sources */ = {
390 | isa = PBXSourcesBuildPhase;
391 | buildActionMask = 2147483647;
392 | files = (
393 | 607FACD81AFB9204008FA782 /* CustomViewController.swift in Sources */,
394 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
395 | D495544E1FFD3D920081225B /* RxBaseCollectionViewController.swift in Sources */,
396 | D4162A391FF502A900AC2572 /* CustomLayout.swift in Sources */,
397 | D4B9816E1FFCCC900016C676 /* PickerController.swift in Sources */,
398 | D403973F1FEBFA9D006C41D2 /* Cell.swift in Sources */,
399 | D43362D11FF64B330040C679 /* BaseCollectionViewController.swift in Sources */,
400 | );
401 | runOnlyForDeploymentPostprocessing = 0;
402 | };
403 | 607FACE11AFB9204008FA782 /* Sources */ = {
404 | isa = PBXSourcesBuildPhase;
405 | buildActionMask = 2147483647;
406 | files = (
407 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */,
408 | );
409 | runOnlyForDeploymentPostprocessing = 0;
410 | };
411 | /* End PBXSourcesBuildPhase section */
412 |
413 | /* Begin PBXTargetDependency section */
414 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
415 | isa = PBXTargetDependency;
416 | target = 607FACCF1AFB9204008FA782 /* InfiniteLayout_Example */;
417 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
418 | };
419 | /* End PBXTargetDependency section */
420 |
421 | /* Begin PBXVariantGroup section */
422 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = {
423 | isa = PBXVariantGroup;
424 | children = (
425 | 607FACDA1AFB9204008FA782 /* Base */,
426 | );
427 | name = Main.storyboard;
428 | sourceTree = "";
429 | };
430 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
431 | isa = PBXVariantGroup;
432 | children = (
433 | 607FACDF1AFB9204008FA782 /* Base */,
434 | );
435 | name = LaunchScreen.xib;
436 | sourceTree = "";
437 | };
438 | /* End PBXVariantGroup section */
439 |
440 | /* Begin XCBuildConfiguration section */
441 | 607FACED1AFB9204008FA782 /* Debug */ = {
442 | isa = XCBuildConfiguration;
443 | buildSettings = {
444 | ALWAYS_SEARCH_USER_PATHS = NO;
445 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
447 | CLANG_CXX_LIBRARY = "libc++";
448 | CLANG_ENABLE_MODULES = YES;
449 | CLANG_ENABLE_OBJC_ARC = YES;
450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
451 | CLANG_WARN_BOOL_CONVERSION = YES;
452 | CLANG_WARN_COMMA = YES;
453 | CLANG_WARN_CONSTANT_CONVERSION = YES;
454 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
456 | CLANG_WARN_EMPTY_BODY = YES;
457 | CLANG_WARN_ENUM_CONVERSION = YES;
458 | CLANG_WARN_INFINITE_RECURSION = YES;
459 | CLANG_WARN_INT_CONVERSION = YES;
460 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
461 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
462 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
463 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
464 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
465 | CLANG_WARN_STRICT_PROTOTYPES = YES;
466 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
467 | CLANG_WARN_UNREACHABLE_CODE = YES;
468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
469 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
470 | COPY_PHASE_STRIP = NO;
471 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
472 | ENABLE_STRICT_OBJC_MSGSEND = YES;
473 | ENABLE_TESTABILITY = YES;
474 | GCC_C_LANGUAGE_STANDARD = gnu99;
475 | GCC_DYNAMIC_NO_PIC = NO;
476 | GCC_NO_COMMON_BLOCKS = YES;
477 | GCC_OPTIMIZATION_LEVEL = 0;
478 | GCC_PREPROCESSOR_DEFINITIONS = (
479 | "DEBUG=1",
480 | "$(inherited)",
481 | );
482 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
483 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
484 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
485 | GCC_WARN_UNDECLARED_SELECTOR = YES;
486 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
487 | GCC_WARN_UNUSED_FUNCTION = YES;
488 | GCC_WARN_UNUSED_VARIABLE = YES;
489 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
490 | MTL_ENABLE_DEBUG_INFO = YES;
491 | ONLY_ACTIVE_ARCH = YES;
492 | SDKROOT = iphoneos;
493 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
494 | SWIFT_VERSION = 5.0;
495 | };
496 | name = Debug;
497 | };
498 | 607FACEE1AFB9204008FA782 /* Release */ = {
499 | isa = XCBuildConfiguration;
500 | buildSettings = {
501 | ALWAYS_SEARCH_USER_PATHS = NO;
502 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
503 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
504 | CLANG_CXX_LIBRARY = "libc++";
505 | CLANG_ENABLE_MODULES = YES;
506 | CLANG_ENABLE_OBJC_ARC = YES;
507 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
508 | CLANG_WARN_BOOL_CONVERSION = YES;
509 | CLANG_WARN_COMMA = YES;
510 | CLANG_WARN_CONSTANT_CONVERSION = YES;
511 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
512 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
513 | CLANG_WARN_EMPTY_BODY = YES;
514 | CLANG_WARN_ENUM_CONVERSION = YES;
515 | CLANG_WARN_INFINITE_RECURSION = YES;
516 | CLANG_WARN_INT_CONVERSION = YES;
517 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
518 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
519 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
520 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
521 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
522 | CLANG_WARN_STRICT_PROTOTYPES = YES;
523 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
524 | CLANG_WARN_UNREACHABLE_CODE = YES;
525 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
526 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
527 | COPY_PHASE_STRIP = NO;
528 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
529 | ENABLE_NS_ASSERTIONS = NO;
530 | ENABLE_STRICT_OBJC_MSGSEND = YES;
531 | GCC_C_LANGUAGE_STANDARD = gnu99;
532 | GCC_NO_COMMON_BLOCKS = YES;
533 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
534 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
535 | GCC_WARN_UNDECLARED_SELECTOR = YES;
536 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
537 | GCC_WARN_UNUSED_FUNCTION = YES;
538 | GCC_WARN_UNUSED_VARIABLE = YES;
539 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
540 | MTL_ENABLE_DEBUG_INFO = NO;
541 | SDKROOT = iphoneos;
542 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
543 | SWIFT_VERSION = 5.0;
544 | VALIDATE_PRODUCT = YES;
545 | };
546 | name = Release;
547 | };
548 | 607FACF01AFB9204008FA782 /* Debug */ = {
549 | isa = XCBuildConfiguration;
550 | baseConfigurationReference = 17E9A86BF3432BF233E3D297 /* Pods-InfiniteLayout_Example.debug.xcconfig */;
551 | buildSettings = {
552 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
553 | DEVELOPMENT_TEAM = CPE39ZT2AY;
554 | INFOPLIST_FILE = InfiniteLayout/Info.plist;
555 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
556 | MODULE_NAME = ExampleApp;
557 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
558 | PRODUCT_NAME = "$(TARGET_NAME)";
559 | SWIFT_VERSION = 5.0;
560 | TARGETED_DEVICE_FAMILY = 1;
561 | };
562 | name = Debug;
563 | };
564 | 607FACF11AFB9204008FA782 /* Release */ = {
565 | isa = XCBuildConfiguration;
566 | baseConfigurationReference = AD3D900D0067605DEAFF148B /* Pods-InfiniteLayout_Example.release.xcconfig */;
567 | buildSettings = {
568 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
569 | DEVELOPMENT_TEAM = CPE39ZT2AY;
570 | INFOPLIST_FILE = InfiniteLayout/Info.plist;
571 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
572 | MODULE_NAME = ExampleApp;
573 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
574 | PRODUCT_NAME = "$(TARGET_NAME)";
575 | SWIFT_VERSION = 5.0;
576 | TARGETED_DEVICE_FAMILY = 1;
577 | };
578 | name = Release;
579 | };
580 | 607FACF31AFB9204008FA782 /* Debug */ = {
581 | isa = XCBuildConfiguration;
582 | baseConfigurationReference = F900DDE615FA1056EB58998B /* Pods-InfiniteLayout_Tests.debug.xcconfig */;
583 | buildSettings = {
584 | DEVELOPMENT_TEAM = CPE39ZT2AY;
585 | FRAMEWORK_SEARCH_PATHS = (
586 | "$(SDKROOT)/Developer/Library/Frameworks",
587 | "$(inherited)",
588 | );
589 | GCC_PREPROCESSOR_DEFINITIONS = (
590 | "DEBUG=1",
591 | "$(inherited)",
592 | );
593 | INFOPLIST_FILE = Tests/Info.plist;
594 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
595 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
596 | PRODUCT_NAME = "$(TARGET_NAME)";
597 | SWIFT_VERSION = 5.0;
598 | };
599 | name = Debug;
600 | };
601 | 607FACF41AFB9204008FA782 /* Release */ = {
602 | isa = XCBuildConfiguration;
603 | baseConfigurationReference = 2E68337E21F08944069DC023 /* Pods-InfiniteLayout_Tests.release.xcconfig */;
604 | buildSettings = {
605 | DEVELOPMENT_TEAM = CPE39ZT2AY;
606 | FRAMEWORK_SEARCH_PATHS = (
607 | "$(SDKROOT)/Developer/Library/Frameworks",
608 | "$(inherited)",
609 | );
610 | INFOPLIST_FILE = Tests/Info.plist;
611 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
612 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
613 | PRODUCT_NAME = "$(TARGET_NAME)";
614 | SWIFT_VERSION = 5.0;
615 | };
616 | name = Release;
617 | };
618 | /* End XCBuildConfiguration section */
619 |
620 | /* Begin XCConfigurationList section */
621 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "InfiniteLayout" */ = {
622 | isa = XCConfigurationList;
623 | buildConfigurations = (
624 | 607FACED1AFB9204008FA782 /* Debug */,
625 | 607FACEE1AFB9204008FA782 /* Release */,
626 | );
627 | defaultConfigurationIsVisible = 0;
628 | defaultConfigurationName = Release;
629 | };
630 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "InfiniteLayout_Example" */ = {
631 | isa = XCConfigurationList;
632 | buildConfigurations = (
633 | 607FACF01AFB9204008FA782 /* Debug */,
634 | 607FACF11AFB9204008FA782 /* Release */,
635 | );
636 | defaultConfigurationIsVisible = 0;
637 | defaultConfigurationName = Release;
638 | };
639 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "InfiniteLayout_Tests" */ = {
640 | isa = XCConfigurationList;
641 | buildConfigurations = (
642 | 607FACF31AFB9204008FA782 /* Debug */,
643 | 607FACF41AFB9204008FA782 /* Release */,
644 | );
645 | defaultConfigurationIsVisible = 0;
646 | defaultConfigurationName = Release;
647 | };
648 | /* End XCConfigurationList section */
649 | };
650 | rootObject = 607FACC81AFB9204008FA782 /* Project object */;
651 | }
652 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout.xcodeproj/xcshareddata/xcschemes/InfiniteLayout-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
97 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnoymous on 12/20/2017.
6 | // Copyright (c) 2017 Arnoymous. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
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 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/BaseCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseCollectionViewController.swift
3 | // InfiniteLayout_Example
4 | //
5 | // Created by Arnaud Dorgans on 29/12/2017.
6 | // Copyright © 2017 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import InfiniteLayout
11 |
12 | class BaseCollectionViewController: InfiniteCollectionViewController {
13 |
14 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
15 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else {
16 | fatalError()
17 | }
18 | cell.update(index: self.infiniteCollectionView!.indexPath(from: indexPath).row)
19 | return cell
20 | }
21 |
22 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
23 | return 20
24 | }
25 |
26 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
27 | collectionView.scrollToItem(at: indexPath, at: self.infiniteCollectionView!.infiniteLayout.scrollDirection == .vertical ? .centeredVertically : .centeredHorizontally, animated: true)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/Cell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cell.swift
3 | // InfiniteLayout_Example
4 | //
5 | // Created by Arnaud Dorgans on 21/12/2017.
6 | // Copyright © 2017 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class Cell: UICollectionViewCell {
12 |
13 | @IBOutlet weak var cellView: CellView!
14 |
15 | func update(index: Int) {
16 | cellView.update(index: index)
17 | }
18 | }
19 |
20 | enum CellStyle {
21 | case circular
22 | case `default`
23 |
24 | static let all = [circular, `default`]
25 | }
26 |
27 | @IBDesignable class CellView: UIView {
28 |
29 | let titleLabel = UILabel()
30 |
31 | let colors = [#colorLiteral(red: 0.5254901961, green: 0.6901960784, blue: 0.9137254902, alpha: 1), #colorLiteral(red: 0.5254901961, green: 0.6196078431, blue: 0.9137254902, alpha: 1), #colorLiteral(red: 0.6078431373, green: 0.5254901961, blue: 0.9137254902, alpha: 1), #colorLiteral(red: 0.9137254902, green: 0.5254901961, blue: 0.8392156863, alpha: 1), #colorLiteral(red: 0.9137254902, green: 0.5254901961, blue: 0.6, alpha: 1), #colorLiteral(red: 0.9137254902, green: 0.6784313725, blue: 0.5254901961, alpha: 1), #colorLiteral(red: 0.9137254902, green: 0.9058823529, blue: 0.5254901961, alpha: 1), #colorLiteral(red: 0.5254901961, green: 0.9137254902, blue: 0.5921568627, alpha: 1), #colorLiteral(red: 0.5254901961, green: 0.8, blue: 0.9137254902, alpha: 1)]
32 |
33 | @IBInspectable var styleIndex: Int {
34 | get { return CellStyle.all.firstIndex(of: style)! }
35 | set { style = CellStyle.all[newValue % CellStyle.all.count] }
36 | }
37 |
38 | var style: CellStyle = .default {
39 | didSet {
40 | updateStyle()
41 | }
42 | }
43 |
44 | override init(frame: CGRect) {
45 | super.init(frame: frame)
46 | sharedInit()
47 | }
48 |
49 | required init?(coder aDecoder: NSCoder) {
50 | super.init(coder: aDecoder)
51 | sharedInit()
52 |
53 | }
54 |
55 | override func awakeFromNib() {
56 | super.awakeFromNib()
57 | }
58 |
59 | func sharedInit() {
60 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
61 | titleLabel.textColor = .white
62 | titleLabel.font = UIFont.boldSystemFont(ofSize: 32)
63 | self.addSubview(titleLabel)
64 | titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
65 | titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
66 | }
67 |
68 | func update(index: Int) {
69 | self.titleLabel.text = String(index + 1)
70 | self.backgroundColor = colors[index % colors.count]
71 | }
72 |
73 | func updateStyle() {
74 | switch style {
75 | case .default:
76 | self.layer.cornerRadius = 8
77 | case .circular:
78 | self.layer.cornerRadius = self.frame.height / 2
79 | }
80 | }
81 |
82 | override func layoutSubviews() {
83 | super.layoutSubviews()
84 | updateStyle()
85 | }
86 |
87 | override func prepareForInterfaceBuilder() {
88 | self.update(index: 0)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/CustomLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomLayout.swift
3 | // InfiniteLayout_Example
4 | //
5 | // Created by Arnaud Dorgans on 28/12/2017.
6 | // Copyright © 2017 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import InfiniteLayout
11 |
12 | class CustomLayout: InfiniteLayout {
13 |
14 | let minimumScale: CGFloat = 0.75
15 | let rangeRatio: CGFloat = 1
16 |
17 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
18 | let attributes = super.layoutAttributesForElements(in: rect).flatMap {
19 | self.copyLayoutAttributes(from: $0)
20 | }
21 | guard let visibleRect = self.visibleCollectionViewRect() else {
22 | return attributes
23 | }
24 | let centeredOffset = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
25 | for attributes in attributes ?? [] {
26 | let diff = self.scrollDirection == .horizontal ? centeredOffset.x - attributes.center.x : centeredOffset.y - attributes.center.y
27 | let scale = max(min(diff / (min(visibleRect.width, visibleRect.height) * rangeRatio), 1), -1)
28 | attributes.transform = attributes.transform.translatedBy(x: abs(scale * (visibleRect.width / 2)), y: 0)
29 | attributes.transform = attributes.transform.rotated(by: scale * (CGFloat.pi / 2))
30 | }
31 | return attributes
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/CustomViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomViewController.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnoymous on 12/20/2017.
6 | // Copyright (c) 2017 Arnoymous. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import InfiniteLayout
11 |
12 | class CustomViewController: UIViewController {
13 |
14 | @IBOutlet weak var infiniteCollectionView: InfiniteCollectionView!
15 |
16 | }
17 |
18 | extension CustomViewController: UICollectionViewDataSource {
19 |
20 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
21 | return 20
22 | }
23 |
24 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
25 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
26 | cell.update(index: self.infiniteCollectionView!.indexPath(from: indexPath).row)
27 | return cell
28 | }
29 | }
30 |
31 | extension CustomViewController: UICollectionViewDelegate {
32 |
33 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
34 | collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
35 | }
36 | }
37 |
38 | extension CustomViewController: UICollectionViewDelegateFlowLayout {
39 |
40 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
41 | return CGSize(width: 100, height: 100)
42 | }
43 |
44 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
45 | return collectionView.frame.height
46 | }
47 |
48 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
49 | return 10
50 | }
51 |
52 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
53 | return UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/Example/InfiniteLayout/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/InfiniteLayout/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/PickerController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PickerController.swift
3 | // InfiniteLayout_Example
4 | //
5 | // Created by Arnaud Dorgans on 03/01/2018.
6 | // Copyright © 2018 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import InfiniteLayout
11 |
12 | class PickerController: UIViewController {
13 |
14 | @IBOutlet weak var selectedView: CellView!
15 | @IBOutlet weak var infiniteCollectionView: InfiniteCollectionView!
16 | }
17 |
18 | extension PickerController: UICollectionViewDataSource {
19 |
20 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
21 | return 20
22 | }
23 |
24 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
25 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else {
26 | fatalError()
27 | }
28 | cell.update(index: self.infiniteCollectionView.indexPath(from: indexPath).row)
29 | return cell
30 | }
31 | }
32 |
33 | extension PickerController: UICollectionViewDelegate {
34 |
35 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
36 | collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
37 | }
38 | }
39 |
40 | extension PickerController: InfiniteCollectionViewDelegate {
41 |
42 | func infiniteCollectionView(_ infiniteCollectionView: InfiniteCollectionView, didChangeCenteredIndexPath centeredIndexPath: IndexPath?) {
43 | guard let indexPath = centeredIndexPath else {
44 | return
45 | }
46 | self.selectedView.update(index: self.infiniteCollectionView.indexPath(from: indexPath).row)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Example/InfiniteLayout/RxBaseCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxBaseCollectionViewController.swift
3 | // InfiniteLayout_Example
4 | //
5 | // Created by Arnaud Dorgans on 03/01/2018.
6 | // Copyright © 2018 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 | import RxCocoa
12 | import RxDataSources
13 | import InfiniteLayout
14 |
15 | class RxBaseCollectionViewController: UIViewController {
16 |
17 | @IBOutlet weak var infiniteCollectionView: RxInfiniteCollectionView!
18 |
19 | let disposeBag = DisposeBag()
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | Observable.just(Array(0..<20))
25 | .bind(to: infiniteCollectionView.rx.items(cellIdentifier: "cell", cellType: Cell.self, infinite: true)) { _, index, cell in
26 | cell.update(index: index)
27 | }.disposed(by: disposeBag)
28 |
29 | infiniteCollectionView.rx.modelCentered(Int.self)
30 | .asDriver()
31 | .drive(onNext: { current in
32 | print("centered: \(current + 1)")
33 | }).disposed(by: disposeBag)
34 |
35 | infiniteCollectionView.rx.itemSelected
36 | .asDriver()
37 | .drive(onNext: { [unowned self] indexPath in
38 | self.infiniteCollectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
39 | }).disposed(by: disposeBag)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 | platform :ios, '10.0'
3 |
4 | target 'InfiniteLayout_Example' do
5 | pod 'InfiniteLayout', :path => '../'
6 | pod 'InfiniteLayout/Rx', :path => '../'
7 |
8 | target 'InfiniteLayout_Tests' do
9 | inherit! :search_paths
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Differentiator (5.0.0)
3 | - InfiniteLayout (0.5):
4 | - InfiniteLayout/Core (= 0.5)
5 | - InfiniteLayout/CocoaProxy (0.5)
6 | - InfiniteLayout/Core (0.5):
7 | - InfiniteLayout/CocoaProxy
8 | - InfiniteLayout/Rx (0.5):
9 | - InfiniteLayout/Core
10 | - RxCocoa (~> 6)
11 | - RxDataSources (~> 5)
12 | - RxSwift (~> 6)
13 | - RxCocoa (6.2.0):
14 | - RxRelay (= 6.2.0)
15 | - RxSwift (= 6.2.0)
16 | - RxDataSources (5.0.0):
17 | - Differentiator (~> 5.0)
18 | - RxCocoa (~> 6.0)
19 | - RxSwift (~> 6.0)
20 | - RxRelay (6.2.0):
21 | - RxSwift (= 6.2.0)
22 | - RxSwift (6.2.0)
23 |
24 | DEPENDENCIES:
25 | - InfiniteLayout (from `../`)
26 | - InfiniteLayout/Rx (from `../`)
27 |
28 | SPEC REPOS:
29 | trunk:
30 | - Differentiator
31 | - RxCocoa
32 | - RxDataSources
33 | - RxRelay
34 | - RxSwift
35 |
36 | EXTERNAL SOURCES:
37 | InfiniteLayout:
38 | :path: "../"
39 |
40 | SPEC CHECKSUMS:
41 | Differentiator: e8497ceab83c1b10ca233716d547b9af21b9344d
42 | InfiniteLayout: 2c55b0fc14b6a9d924fbe13c0e2d88b1cbf5423d
43 | RxCocoa: 4baf94bb35f2c0ab31bc0cb9f1900155f646ba42
44 | RxDataSources: aa47cc1ed6c500fa0dfecac5c979b723542d79cf
45 | RxRelay: e72dbfd157807478401ef1982e1c61c945c94b2f
46 | RxSwift: d356ab7bee873611322f134c5f9ef379fa183d8f
47 |
48 | PODFILE CHECKSUM: 0dc7a9b37f9b7c5d5266beaab9e94861b80aa80f
49 |
50 | COCOAPODS: 1.10.1
51 |
--------------------------------------------------------------------------------
/Example/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/Tests/Tests.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import XCTest
3 | import InfiniteLayout
4 |
5 | class Tests: XCTestCase {
6 |
7 | override func setUp() {
8 | super.setUp()
9 | // Put setup code here. This method is called before the invocation of each test method in the class.
10 | }
11 |
12 | override func tearDown() {
13 | // Put teardown code here. This method is called after the invocation of each test method in the class.
14 | super.tearDown()
15 | }
16 |
17 | func testExample() {
18 | // This is an example of a functional test case.
19 | XCTAssert(true, "Pass")
20 | }
21 |
22 | func testPerformanceExample() {
23 | // This is an example of a performance test case.
24 | self.measure() {
25 | // Put the code you want to measure the time of here.
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/InfiniteLayout.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint InfiniteLayout.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'InfiniteLayout'
11 | s.version = '0.5'
12 | s.summary = 'Horizontal and Vertical infinite scrolling feature for UICollectionView with Paging, NSProxy delegate, Reactive extension'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = <<-DESC
21 | Horizontal and Vertical infinite scrolling feature for UICollectionView with Paging, NSProxy delegate, Reactive extension, SectionModel & AnimatableSectionModel support
22 | DESC
23 |
24 | s.homepage = 'https://github.com/arnauddorgans/InfiniteLayout'
25 | s.screenshots = 'https://github.com/arnauddorgans/InfiniteLayout/raw/master/horizontal.gif', 'https://github.com/arnauddorgans/InfiniteLayout/raw/master/vertical.gif', 'https://github.com/arnauddorgans/InfiniteLayout/raw/master/custom.gif', 'https://github.com/arnauddorgans/InfiniteLayout/raw/master/delegate.gif'
26 | s.license = { :type => 'MIT', :file => 'LICENSE' }
27 | s.author = { 'Arnaud Dorgans' => 'ineox@me.com' }
28 | s.source = { :git => 'https://github.com/arnauddorgans/InfiniteLayout.git', :tag => s.version.to_s }
29 | s.social_media_url = 'https://twitter.com/arnauddorgans'
30 |
31 | s.ios.deployment_target = '9.0'
32 | s.tvos.deployment_target = '9.0'
33 |
34 | #s.xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '${POD_ROOT}/InfiniteLayout/BridgeHeader.h' }
35 |
36 | # s.resource_bundles = {
37 | # 'InfiniteLayout' => ['InfiniteLayout/Assets/*.png']
38 | # }
39 |
40 | # s.public_header_files = 'Pod/Classes/**/*.h'
41 | # s.frameworks = 'UIKit', 'MapKit'
42 | s.swift_versions = ['5.0', '5.1']
43 |
44 |
45 | s.default_subspec = 'Core'
46 | s.subspec 'Core' do |core|
47 | core.source_files = 'Sources/InfiniteLayout/**/*'
48 | core.dependency 'InfiniteLayout/CocoaProxy'
49 | core.exclude_files = '**/*/SPMBridge.swift'
50 | end
51 | s.subspec 'CocoaProxy' do |core|
52 | core.source_files = 'Sources/CocoaProxy/**/*'
53 | end
54 | s.subspec 'Rx' do |rx|
55 | rx.dependency 'InfiniteLayout/Core'
56 | rx.dependency 'RxSwift', '~> 6'
57 | rx.dependency 'RxCocoa', '~> 6'
58 | rx.dependency 'RxDataSources', '~> 5'
59 | rx.source_files = 'Sources/Rx/**/*'
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Arnoymous
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "InfiniteLayout",
8 | platforms: [.iOS(.v9), .tvOS(.v9)],
9 | products: [
10 | .library(
11 | name: "InfiniteLayout",
12 | targets: ["InfiniteLayout"]),
13 | .library(
14 | name: "RxInfiniteLayout",
15 | targets: ["RxInfiniteLayout"]),
16 | ],
17 | dependencies: [
18 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.0.0")),
19 | .package(url: "https://github.com/RxSwiftCommunity/RxDataSources.git", .upToNextMajor(from: "5.0.0")),
20 | ],
21 | targets: [
22 | .target(
23 | name: "CocoaProxy",
24 | dependencies: [],
25 | publicHeadersPath: "./"
26 | ),
27 | .target(
28 | name: "InfiniteLayout",
29 | dependencies: ["CocoaProxy"]
30 | ),
31 | .target(
32 | name: "RxInfiniteLayout",
33 | dependencies: ["InfiniteLayout", "RxSwift", "RxCocoa", "RxDataSources"],
34 | path: "Sources/Rx"
35 | ),
36 | ]
37 | )
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # InfiniteLayout
2 |
3 | [](https://travis-ci.org/arnauddorgans/InfiniteLayout)
4 | [](http://cocoapods.org/pods/InfiniteLayout)
5 | [](http://cocoapods.org/pods/InfiniteLayout)
6 | [](http://cocoapods.org/pods/InfiniteLayout)
7 | [](https://github.com/apple/swift-package-manager)
8 |
9 | 

10 |
11 |
12 | ## Example
13 |
14 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
15 |
16 | ## Installation
17 |
18 | ### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
19 |
20 | InfiniteLayout is available through [CocoaPods](http://cocoapods.org). To install
21 | it, simply add the following line to your Podfile:
22 |
23 | ```ruby
24 | pod 'InfiniteLayout'
25 | ```
26 |
27 |
28 | ### [Swift Package Manager](https://github.com/apple/swift-package-manager)
29 |
30 | Create a `Package.swift` file.
31 |
32 | ```swift
33 | // swift-tools-version:5.0
34 |
35 | import PackageDescription
36 |
37 | let package = Package(
38 | name: "InfiniteLayoutTestProject",
39 | dependencies: [
40 | .package(url: "https://github.com/arnauddorgans/InfiniteLayout.git", from: "0.4.2")
41 | ],
42 | targets: [
43 | .target(name: "InfiniteLayoutTestProject", dependencies: ["InfiniteLayout"])
44 | ]
45 | )
46 | ```
47 |
48 | ## Usage
49 |
50 | ```swift
51 | @IBOutlet weak var collectionView: InfiniteCollectionView!
52 | ```
53 |
54 | InfiniteCollectionView doesn't need any other delegate or dataSource,
55 | just use UICollectionViewDataSource and UICollectionViewDelegate in the same way as you'll use it in any other UICollectionView.
56 |
57 | InfiniteLayout provides 3 classes for infinite scrolling:
58 |
59 | **InfiniteLayout**: an UICollectionViewFlowLayout
60 |
61 | **InfiniteCollectionView**: an UICollectionView with InfiniteLayout
62 |
63 | **InfiniteCollectionViewController**: an UICollectionViewController with InfiniteCollectionView
64 |
65 | ### IndexPath
66 |
67 | InfiniteCollectionView may create fake indexPath,
68 |
69 | To get the real indexPath call
70 |
71 | ```swift
72 | func indexPath(from infiniteIndexPath: IndexPath) -> IndexPath
73 | ```
74 |
75 | To get the real section call
76 |
77 | ```swift
78 | func section(from infiniteSection: Int) -> Int
79 | ```
80 |
81 | ### Paging
82 |
83 | InfiniteCollectionView provide a paging functionality, you can enable it by setting the **isItemPagingEnabled** flag to **true**
84 |
85 | ```swift
86 | self.infiniteCollectionView.isItemPagingEnabled = true
87 | ```
88 |
89 | When the **isItemPagingEnabled** flag is enabled you can adjust the deceleration rate by setting the **velocityMultiplier**, the more the value is high, the more the deceleration is long
90 |
91 | ```swift
92 | self.infiniteCollectionView.velocityMultiplier = 1 // like scrollView with paging (default value)
93 | self.infiniteCollectionView.velocityMultiplier = 500 // like scrollView without paging
94 | ```
95 |
96 | When the **isItemPagingEnabled** flag is enabled you can set a **preferredCenteredIndexPath**, this value is used to calculate the preferred visible cell to center each time the collectionView will change its contentSize
97 |
98 | ```swift
99 | self.infiniteCollectionView.preferredCenteredIndexPath = [0, 0] // center the cell at [0, 0] if visible (default value)
100 | self.infiniteCollectionView.preferredCenteredIndexPath = nil // center the closest cell from center
101 | ```
102 |
103 | ### Delegate
104 |
105 |
106 |
107 | InfiniteCollectionView provide an **infiniteDelegate** protocol used to get the centered IndexPath, usefull if you want to use an InfiniteCollectionView like a Picker.
108 |
109 | ```swift
110 | func infiniteCollectionView(_ infiniteCollectionView: InfiniteCollectionView, didChangeCenteredIndexPath from: IndexPath?, to: IndexPath?)
111 | ```
112 |
113 | ### Rx
114 |
115 | InfiniteCollectionView provide a subspec **InfiniteLayout/Rx**
116 | ```ruby
117 | pod 'InfiniteLayout/Rx'
118 | ```
119 |
120 | To use InfiniteCollectionView with RxSwift without conflicts between NSProxy
121 |
122 | Use **RxInfiniteCollectionView** instead of **InfiniteCollectionView**
123 |
124 | ```swift
125 | @IBOutlet weak var collectionView: RxInfiniteCollectionView!
126 | ```
127 |
128 | RxInfiniteCollectionView provides 2 dataSources for SectionModel:
129 |
130 | **RxInfiniteCollectionViewSectionedReloadDataSource** and **RxInfiniteCollectionViewSectionedAnimatedDataSource**
131 |
132 | #### Binding:
133 |
134 | Without sections:
135 | ```swift
136 | // automatic cell dequeue
137 | Observable.just(Array(0..<2))
138 | .bind(to: infiniteCollectionView.rx.items(cellIdentifier: "cell", cellType: Cell.self, infinite: true)) { row, element, cell in
139 | cell.update(index: row) // update your cell
140 | }.disposed(by: disposeBag)
141 |
142 | // custom cell dequeue
143 | Observable.just(Array(0..<2))
144 | .bind(to: infiniteCollectionView.rx.items(infinite: true)) { collectionView, row, element in
145 | let indexPath = IndexPath(row: row, section: 0)
146 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell // dequeue your cell
147 | cell.update(index: row) // update your cell
148 | return cell
149 | }.disposed(by: disposeBag)
150 | ```
151 |
152 | With sections:
153 | ```swift
154 | let dataSource = RxInfiniteCollectionViewSectionedReloadDataSource>(configureCell: { dataSource, collectionView, indexPath, element in
155 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell // dequeue your cell
156 | cell.update(index: indexPath.row) // update your cell
157 | return cell
158 | })
159 |
160 | Observable.just([
161 | SectionModel(model: 0, items: Array(0..<2)),
162 | SectionModel(model: 1, items: Array(0..<10))
163 | ])
164 | .bind(to: infiniteCollectionView.rx.items(dataSource: dataSource))
165 | .disposed(by: disposeBag)
166 | ```
167 |
168 | for animations just use **RxInfiniteCollectionViewSectionedAnimatedDataSource** & **AnimatableSectionModel**
169 |
170 | #### Centered IndexPath:
171 |
172 | RxInfiniteCollectionView provide Reactive extension for **itemCentered** & **modelCentered**
173 | ```swift
174 | infiniteCollectionView.rx.itemCentered
175 | .asDriver()
176 | .drive(onNext: { [unowned self] indexPath in
177 | self.selectedView.update(index: indexPath.row) // update interface with indexPath
178 | }).disposed(by: disposeBag)
179 |
180 | infiniteCollectionView.rx.modelCentered(Int.self)
181 | .asDriver()
182 | .drive(onNext: { [unowned self] element in
183 | self.selectedView.update(index: element) // update interface with model
184 | }).disposed(by: disposeBag)
185 | ```
186 |
187 | ## Author
188 |
189 | Arnaud Dorgans, arnaud.dorgans@gmail.com
190 |
191 | ## License
192 |
193 | InfiniteLayout is available under the MIT license. See the LICENSE file for more info.
194 |
--------------------------------------------------------------------------------
/Sources/CocoaProxy/CocoaProxy.h:
--------------------------------------------------------------------------------
1 | //
2 | // CocoaProxy.h
3 | // CocoaProxy
4 | //
5 | // Created by Arnaud Dorgans on 27/12/2017.
6 | //
7 |
8 | #import
9 |
10 | @interface CocoaProxy : NSProxy
11 |
12 | - (instancetype _Nonnull)init;
13 | - (instancetype _Nonnull)initWithProxies:(nonnull NSArray>*)proxies;
14 |
15 | - (NSArray> *_Nonnull)proxiesForSelector:(SEL _Nonnull )aSelector;
16 |
17 | @property (nonatomic, strong) NSArray>* _Nonnull proxies;
18 | @property (nonatomic, copy) BOOL (^ _Nullable proxyFilter)(id _Nonnull proxy, SEL _Nonnull selector);
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/Sources/CocoaProxy/CocoaProxy.m:
--------------------------------------------------------------------------------
1 | //
2 | // CocoaProxy.m
3 | // CocoaProxy
4 | //
5 | // Created by Arnaud Dorgans on 27/12/2017.
6 | //
7 |
8 | #import "CocoaProxy.h"
9 |
10 | @interface CocoaProxy () { }
11 |
12 | @property (nonatomic, strong) NSPointerArray *pointerArray;
13 |
14 | @end
15 |
16 | @implementation CocoaProxy
17 |
18 | - (instancetype _Nonnull)initWithProxies:(nonnull NSArray*)proxies {
19 | [self setProxies: proxies];
20 | return self;
21 | }
22 |
23 | - (instancetype _Nonnull)init {
24 | return [self initWithProxies: @[]];
25 | }
26 |
27 | - (BOOL)respondsToSelector:(SEL)aSelector
28 | {
29 | return ([self methodSignatureForSelector: aSelector] != nil);
30 | }
31 |
32 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
33 | {
34 | for (NSObject* proxy in [self proxiesForSelector: aSelector]) {
35 | if ([proxy respondsToSelector: aSelector]) {
36 | return [proxy methodSignatureForSelector: aSelector];
37 | }
38 | }
39 | return nil;
40 | }
41 |
42 | - (NSArray> *_Nonnull)proxiesForSelector:(SEL _Nonnull )aSelector
43 | {
44 | if (self.proxyFilter) {
45 | NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) {
46 | return self.proxyFilter(evaluatedObject, aSelector);
47 | }];
48 | return [self.proxies filteredArrayUsingPredicate: predicate];
49 | }
50 | return self.proxies;
51 | }
52 |
53 | - (void)forwardInvocation:(NSInvocation *)invocation
54 | {
55 | for (NSObject* proxy in [self proxiesForSelector: invocation.selector]) {
56 | [self invokeInvocation: invocation onProxy: proxy];
57 | }
58 | }
59 |
60 | - (BOOL)invokeInvocation:(NSInvocation *)invocation onProxy:(id)proxy
61 | {
62 | if ([proxy respondsToSelector: invocation.selector]) {
63 | [invocation invokeWithTarget: proxy];
64 | return YES;
65 | }
66 | return NO;
67 | }
68 |
69 | - (void)setProxies:(NSArray> *)proxies {
70 | self.pointerArray = [NSPointerArray weakObjectsPointerArray];
71 | for (NSObject* proxy in proxies) {
72 | [self.pointerArray addPointer: (void *)proxy];
73 | }
74 | }
75 |
76 | - (NSArray> *)proxies {
77 | return self.pointerArray.allObjects;
78 | }
79 |
80 | @end
81 |
--------------------------------------------------------------------------------
/Sources/InfiniteLayout/InfiniteCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfiniteCollectionView.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 20/12/2017.
6 | //
7 |
8 | import UIKit
9 |
10 | @objc public protocol InfiniteCollectionViewDelegate {
11 |
12 | @objc optional func infiniteCollectionView(_ infiniteCollectionView: InfiniteCollectionView, didChangeCenteredIndexPath from: IndexPath?, to: IndexPath?)
13 | }
14 |
15 | open class InfiniteCollectionView: UICollectionView {
16 |
17 | lazy var dataSourceProxy = InfiniteCollectionViewDataSourceProxy(collectionView: self)
18 | lazy var delegateProxy = InfiniteCollectionViewDelegateProxy(collectionView: self)
19 |
20 | @IBOutlet open weak var infiniteDelegate: InfiniteCollectionViewDelegate?
21 |
22 | open private(set) var centeredIndexPath: IndexPath?
23 | open var preferredCenteredIndexPath: IndexPath? = IndexPath(item: 0, section: 0)
24 |
25 | open var forwardDelegate: Bool { return true }
26 | var _contentSize: CGSize?
27 |
28 | override open weak var delegate: UICollectionViewDelegate? {
29 | get { return super.delegate }
30 | set {
31 | guard forwardDelegate else {
32 | super.delegate = newValue
33 | return
34 | }
35 | guard let newValue = newValue else {
36 | super.delegate = nil
37 | return
38 | }
39 | let isProxy = newValue is InfiniteCollectionViewDelegateProxy
40 | let delegate = isProxy ? newValue : delegateProxy
41 | if !isProxy {
42 | delegateProxy.delegate = newValue
43 | }
44 | super.delegate = delegate
45 | }
46 | }
47 |
48 | override open weak var dataSource: UICollectionViewDataSource? {
49 | get { return super.dataSource }
50 | set {
51 | guard forwardDelegate else {
52 | super.dataSource = newValue
53 | return
54 | }
55 | guard let newValue = newValue else {
56 | super.dataSource = nil
57 | return
58 | }
59 | let isProxy = newValue is InfiniteCollectionViewDataSourceProxy
60 | let dataSource = isProxy ? newValue : dataSourceProxy
61 | if !isProxy {
62 | dataSourceProxy.delegate = newValue
63 | }
64 | super.dataSource = dataSource
65 | }
66 | }
67 |
68 | @IBInspectable open var isItemPagingEnabled: Bool = false
69 | @IBInspectable open var velocityMultiplier: CGFloat = 1 {
70 | didSet {
71 | self.infiniteLayout.velocityMultiplier = velocityMultiplier
72 | }
73 | }
74 |
75 | public var infiniteLayout: InfiniteLayout! {
76 | return self.collectionViewLayout as? InfiniteLayout
77 | }
78 |
79 | private static func infiniteLayout(layout: UICollectionViewLayout) -> InfiniteLayout {
80 | guard let infiniteLayout = layout as? InfiniteLayout else {
81 | return InfiniteLayout(layout: layout)
82 | }
83 | return infiniteLayout
84 | }
85 |
86 | public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
87 | super.init(frame: frame, collectionViewLayout: InfiniteCollectionView.infiniteLayout(layout: layout))
88 | sharedInit()
89 | }
90 |
91 | required public init?(coder aDecoder: NSCoder) {
92 | super.init(coder: aDecoder)
93 |
94 | let infiniteLayout = InfiniteCollectionView.infiniteLayout(layout: self.collectionViewLayout)
95 | if self.collectionViewLayout != infiniteLayout {
96 | self.collectionViewLayout = infiniteLayout
97 | }
98 | }
99 |
100 | open override func awakeFromNib() {
101 | super.awakeFromNib()
102 | sharedInit()
103 | }
104 |
105 | private func sharedInit() {
106 | self.showsVerticalScrollIndicator = false
107 | self.showsHorizontalScrollIndicator = false
108 | #if os(iOS)
109 | self.scrollsToTop = false
110 | #endif
111 | }
112 |
113 | open override func layoutSubviews() {
114 | super.layoutSubviews()
115 | self.updateLayoutIfNeeded()
116 | }
117 | }
118 |
119 | // MARK: DataSource
120 | extension InfiniteCollectionView: UICollectionViewDataSource {
121 |
122 | private var delegateNumberOfSections: Int {
123 | guard let sections = dataSourceProxy.delegate.flatMap({ $0.numberOfSections?(in: self) ?? 1 }) else {
124 | fatalError("collectionView dataSource is required")
125 | }
126 | return sections
127 | }
128 |
129 | private func delegateNumberOfItems(in section: Int) -> Int {
130 | guard let items = dataSourceProxy.delegate.flatMap({ $0.collectionView(self, numberOfItemsInSection: self.section(from: section)) }) else {
131 | fatalError("collectionView dataSource is required")
132 | }
133 | return items
134 | }
135 |
136 | private var multiplier: Int {
137 | return InfiniteDataSources.multiplier(estimatedItemSize: self.infiniteLayout.itemSize, enabled: self.infiniteLayout.isEnabled)
138 | }
139 |
140 | public func section(from infiniteSection: Int) -> Int {
141 | return InfiniteDataSources.section(from: infiniteSection, numberOfSections: delegateNumberOfSections)
142 | }
143 |
144 | public func indexPath(from infiniteIndexPath: IndexPath) -> IndexPath {
145 | return InfiniteDataSources.indexPath(from: infiniteIndexPath,
146 | numberOfSections: delegateNumberOfSections,
147 | numberOfItems: delegateNumberOfItems(in: infiniteIndexPath.section))
148 | }
149 |
150 | public func numberOfSections(in collectionView: UICollectionView) -> Int {
151 | return InfiniteDataSources.numberOfSections(numberOfSections: delegateNumberOfSections, multiplier: multiplier)
152 | }
153 |
154 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
155 | return InfiniteDataSources.numberOfItemsInSection(numberOfItemsInSection: delegateNumberOfItems(in: section),
156 | numberOfSections: delegateNumberOfSections,
157 | multiplier: multiplier)
158 | }
159 |
160 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
161 | fatalError("collectionView dataSource is required")
162 | }
163 | }
164 |
165 | extension InfiniteCollectionView: UICollectionViewDelegate {
166 |
167 | func updateLayoutIfNeeded() {
168 | self.loopCollectionViewIfNeeded()
169 | self.centerCollectionViewIfNeeded()
170 |
171 | let preferredVisibleIndexPath = infiniteLayout.preferredVisibleLayoutAttributes()?.indexPath
172 | if self.centeredIndexPath != preferredVisibleIndexPath {
173 | let previousCenteredIndexPath = self.centeredIndexPath
174 | self.centeredIndexPath = preferredVisibleIndexPath
175 | self.infiniteDelegate?.infiniteCollectionView?(self, didChangeCenteredIndexPath: previousCenteredIndexPath, to: self.centeredIndexPath)
176 | }
177 | }
178 |
179 | // MARK: Loop
180 | func loopCollectionViewIfNeeded() {
181 | self.infiniteLayout.loopCollectionViewIfNeeded()
182 | }
183 |
184 | public func scrollViewDidScroll(_ scrollView: UIScrollView) {
185 | delegateProxy.delegate?.scrollViewDidScroll?(scrollView)
186 | self.updateLayoutIfNeeded()
187 | }
188 |
189 | // MARK: Paging
190 | func centerCollectionViewIfNeeded() {
191 | guard isItemPagingEnabled,
192 | !self.isDragging && !self.isDecelerating else {
193 | return
194 | }
195 | guard self._contentSize != self.contentSize else {
196 | return
197 | }
198 | self._contentSize = self.contentSize
199 | self.infiniteLayout.centerCollectionViewIfNeeded(indexPath: self.preferredCenteredIndexPath)
200 | }
201 |
202 | public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) {
203 | if isItemPagingEnabled {
204 | self.infiniteLayout.centerCollectionView(withVelocity: velocity, targetContentOffset: targetContentOffset)
205 | }
206 | self.delegateProxy.delegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/Sources/InfiniteLayout/InfiniteCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfiniteCollectionViewController.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 23/12/2017.
6 | //
7 |
8 | import UIKit
9 |
10 | open class InfiniteCollectionViewController: UICollectionViewController {
11 |
12 | public var infiniteCollectionView: InfiniteCollectionView? {
13 | return self.collectionView as? InfiniteCollectionView
14 | }
15 |
16 | override open func viewDidLoad() {
17 | super.viewDidLoad()
18 |
19 | guard let _ = self.infiniteCollectionView else {
20 | fatalError("InfiniteCollectionView is needed")
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/InfiniteLayout/InfiniteCollectionViewProxy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Proxy.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 20/12/2017.
6 | //
7 |
8 | import UIKit
9 |
10 | class InfiniteCollectionViewProxy: CocoaProxy {
11 |
12 | var collectionView: InfiniteCollectionView! {
13 | get { return self.proxies.first as? InfiniteCollectionView }
14 | set {
15 | if !self.proxies.isEmpty {
16 | self.proxies.removeFirst()
17 | }
18 | self.proxies.insert(newValue, at: 0)
19 | }
20 | }
21 |
22 | var delegate: T? {
23 | get {
24 | guard self.proxies.count > 1 else {
25 | return nil
26 | }
27 | return self.proxies.last as? T
28 | } set {
29 | while self.proxies.count > 1 {
30 | self.proxies.removeLast()
31 | }
32 | guard let delegate = newValue else {
33 | return
34 | }
35 | self.proxies.append(delegate)
36 | }
37 | }
38 |
39 | override func proxies(for aSelector: Selector) -> [NSObjectProtocol] {
40 | return super.proxies(for: aSelector).reversed()
41 | }
42 |
43 | init(collectionView: InfiniteCollectionView) {
44 | super.init(proxies: [])
45 | self.collectionView = collectionView
46 | }
47 |
48 | deinit {
49 | self.proxies.removeAll()
50 | }
51 | }
52 |
53 | class InfiniteCollectionViewDelegateProxy: InfiniteCollectionViewProxy, UICollectionViewDelegate {
54 |
55 | override func proxies(for aSelector: Selector) -> [NSObjectProtocol] {
56 | return super.proxies(for: aSelector)
57 | .first { proxy in
58 | guard !(aSelector == #selector(UIScrollViewDelegate.scrollViewDidScroll(_:)) ||
59 | aSelector == #selector(UIScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:))) else {
60 | return proxy is InfiniteCollectionView
61 | }
62 | return true
63 | }.flatMap { [$0] } ?? []
64 | }
65 | }
66 |
67 | class InfiniteCollectionViewDataSourceProxy: InfiniteCollectionViewProxy, UICollectionViewDataSource {
68 |
69 | override func proxies(for aSelector: Selector) -> [NSObjectProtocol] {
70 | return super.proxies(for: aSelector)
71 | .first { proxy in
72 | guard !(aSelector == #selector(UICollectionViewDataSource.numberOfSections(in:)) ||
73 | aSelector == #selector(UICollectionViewDataSource.collectionView(_:numberOfItemsInSection:))) else {
74 | return proxy is InfiniteCollectionView
75 | }
76 | return true
77 | }.flatMap { [$0] } ?? []
78 | }
79 |
80 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
81 | return self.collectionView.collectionView(collectionView, numberOfItemsInSection: section)
82 | }
83 |
84 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
85 | return self.delegate?.collectionView(collectionView, cellForItemAt: indexPath) ??
86 | self.collectionView.collectionView(collectionView, cellForItemAt: indexPath)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/InfiniteLayout/InfiniteDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfiniteDataSources.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 03/01/2018.
6 | //
7 |
8 | import UIKit
9 |
10 | open class InfiniteDataSources {
11 | public static var originCount: Int = 0
12 | public static func section(from infiniteSection: Int, numberOfSections: Int) -> Int {
13 | return infiniteSection % numberOfSections
14 | }
15 |
16 | public static func indexPath(from infiniteIndexPath: IndexPath, numberOfSections: Int, numberOfItems: Int) -> IndexPath {
17 | return IndexPath(item: infiniteIndexPath.item % numberOfItems, section: self.section(from: infiniteIndexPath.section, numberOfSections: numberOfSections))
18 | }
19 |
20 | public static func multiplier(estimatedItemSize: CGSize, enabled: Bool) -> Int {
21 | guard enabled else {
22 | return 1
23 | }
24 | let min = Swift.min(estimatedItemSize.width, estimatedItemSize.height)
25 | let count = ceil(InfiniteLayout.minimumContentSize / min)
26 | return Int(count)
27 | }
28 |
29 | public static func numberOfSections(numberOfSections: Int, multiplier: Int) -> Int {
30 | return numberOfSections > 1 ? numberOfSections * multiplier : numberOfSections
31 | }
32 |
33 | public static func numberOfItemsInSection(numberOfItemsInSection: Int, numberOfSections: Int, multiplier: Int) -> Int {
34 | originCount = numberOfItemsInSection
35 | return numberOfSections > 1 ? numberOfItemsInSection : numberOfItemsInSection * multiplier
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/InfiniteLayout/InfiniteLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfiniteCollectionView.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 20/12/2017.
6 | //
7 |
8 | import UIKit
9 |
10 | open class InfiniteLayout: UICollectionViewFlowLayout {
11 |
12 | public var velocityMultiplier: CGFloat = 1 // used to simulate paging
13 |
14 | private let multiplier: CGFloat = 500 // contentOffset multiplier
15 |
16 | private var contentSize: CGSize = .zero
17 |
18 | private var hasValidLayout: Bool = false
19 |
20 | @IBInspectable public var isEnabled: Bool = true {
21 | didSet {
22 | self.invalidateLayout()
23 | }
24 | }
25 |
26 | public var currentPage: CGPoint {
27 | guard let collectionView = self.collectionView else {
28 | return .zero
29 | }
30 | return self.page(for: collectionView.contentOffset)
31 | }
32 |
33 | open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
34 | return true
35 | }
36 |
37 | public convenience init(layout: UICollectionViewLayout) {
38 | self.init()
39 | guard let layout = layout as? UICollectionViewFlowLayout else {
40 | return
41 | }
42 | self.scrollDirection = layout.scrollDirection
43 | self.minimumLineSpacing = layout.minimumLineSpacing
44 | self.minimumInteritemSpacing = layout.minimumInteritemSpacing
45 | self.itemSize = layout.itemSize
46 | self.sectionInset = layout.sectionInset
47 | self.headerReferenceSize = layout.headerReferenceSize
48 | self.footerReferenceSize = layout.footerReferenceSize
49 | }
50 |
51 | static var minimumContentSize: CGFloat {
52 | return max(UIScreen.main.bounds.width, UIScreen.main.bounds.height) * 4
53 | }
54 |
55 | override open func prepare() {
56 | let collectionViewContentSize = super.collectionViewContentSize
57 | self.contentSize = CGSize(width: collectionViewContentSize.width, height: collectionViewContentSize.height)
58 | self.hasValidLayout = {
59 | guard let collectionView = self.collectionView, collectionView.bounds != .zero, self.isEnabled else {
60 | return false
61 | }
62 | return (scrollDirection == .horizontal ? self.contentSize.width : self.contentSize.height) >=
63 | InfiniteLayout.minimumContentSize
64 | }()
65 | super.prepare()
66 | }
67 |
68 | override open var collectionViewContentSize: CGSize {
69 | guard hasValidLayout else {
70 | return self.contentSize
71 | }
72 | return CGSize(width: scrollDirection == .horizontal ? self.contentSize.width * multiplier : self.contentSize.width,
73 | height: scrollDirection == .vertical ? self.contentSize.height * multiplier : self.contentSize.height)
74 | }
75 |
76 | open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
77 | guard let attributes = super.layoutAttributesForItem(at: indexPath) else {
78 | return nil
79 | }
80 | return self.layoutAttributes(from: attributes, page: currentPage)
81 | }
82 |
83 | override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
84 | guard hasValidLayout else {
85 | return super.layoutAttributesForElements(in: rect)
86 | }
87 | let page = self.page(for: rect.origin)
88 | var elements = [UICollectionViewLayoutAttributes]()
89 | var rect = self.rect(from: rect)
90 | if (self.scrollDirection == .horizontal && rect.maxX > contentSize.width) ||
91 | (self.scrollDirection == .vertical && rect.maxY > contentSize.height) {
92 | let diffRect = CGRect(origin: .zero, size: CGSize(width: self.scrollDirection == .horizontal ? rect.maxX - contentSize.width : rect.width,
93 | height: self.scrollDirection == .vertical ? rect.maxY - contentSize.height : rect.height))
94 | elements.append(contentsOf: self.elements(in: diffRect, page: self.page(from: page, offset: 1)))
95 | if self.scrollDirection == .horizontal {
96 | rect.size.width -= diffRect.width
97 | } else {
98 | rect.size.height -= diffRect.height
99 | }
100 | }
101 | elements.append(contentsOf: self.elements(in: rect, page: page))
102 | return elements
103 | }
104 |
105 | private func page(for point: CGPoint) -> CGPoint {
106 | let xPage: CGFloat = floor(point.x / contentSize.width)
107 | let yPage: CGFloat = floor(point.y / contentSize.height)
108 |
109 | return CGPoint(x: self.scrollDirection == .horizontal ? xPage : 0,
110 | y: self.scrollDirection == .vertical ? yPage : 0)
111 | }
112 |
113 | private func page(from page: CGPoint, offset: CGFloat) -> CGPoint {
114 | return CGPoint(x: self.scrollDirection == .horizontal ? page.x + offset : page.x,
115 | y: self.scrollDirection == .vertical ? page.y + offset : page.y)
116 | }
117 |
118 | private func pageIndex(from page: CGPoint) -> CGFloat {
119 | return self.scrollDirection == .horizontal ? page.x : page.y
120 | }
121 |
122 | public func rect(from rect: CGRect, page: CGPoint = .zero) -> CGRect {
123 | var rect = rect
124 | if self.scrollDirection == .horizontal && rect.origin.x < 0 {
125 | rect.origin.x += abs(floor(contentSize.width / rect.origin.x)) * contentSize.width
126 | } else if self.scrollDirection == .vertical && rect.origin.y < 0 {
127 | rect.origin.y += abs(floor(contentSize.height / rect.origin.y)) * contentSize.height
128 | }
129 | rect.origin.x = rect.origin.x.truncatingRemainder(dividingBy: contentSize.width)
130 | rect.origin.y = rect.origin.y.truncatingRemainder(dividingBy: contentSize.height)
131 | rect.origin.x += page.x * contentSize.width
132 | rect.origin.y += page.y * contentSize.height
133 | return rect
134 | }
135 |
136 | private func elements(in rect: CGRect, page: CGPoint) -> [UICollectionViewLayoutAttributes] {
137 | let rect = self.rect(from: rect)
138 | let elements = super.layoutAttributesForElements(in: rect)?
139 | .map { self.layoutAttributes(from: $0, page: page) }
140 | .filter { $0 != nil }
141 | .map { $0! } ?? []
142 | return elements
143 | }
144 |
145 | private func layoutAttributes(from layoutAttributes: UICollectionViewLayoutAttributes, page: CGPoint) -> UICollectionViewLayoutAttributes! {
146 | guard let attributes = self.copyLayoutAttributes(layoutAttributes) else {
147 | return nil
148 | }
149 | attributes.frame = rect(from: attributes.frame, page: page)
150 | return attributes
151 | }
152 |
153 | // MARK: Loop
154 | private func updateContentOffset(_ offset: CGPoint) {
155 | guard let collectionView = self.collectionView else {
156 | return
157 | }
158 | collectionView.contentOffset = offset
159 | collectionView.layoutIfNeeded()
160 | }
161 | private func preferredContentOffset(forContentOffset contentOffset: CGPoint) -> CGPoint {
162 | return rect(from: CGRect(origin: contentOffset, size: .zero), page: self.page(from: .zero, offset: multiplier / 2)).origin
163 | }
164 |
165 | public func loopCollectionViewIfNeeded() {
166 | guard let collectionView = self.collectionView, self.hasValidLayout else {
167 | return
168 | }
169 | let page = self.pageIndex(from: self.page(for: collectionView.contentOffset))
170 | let offset = self.preferredContentOffset(forContentOffset: collectionView.contentOffset)
171 | if (page < 2 || page > self.multiplier - 2) && collectionView.contentOffset != offset {
172 | self.updateContentOffset(offset)
173 | }
174 | }
175 |
176 | // MARK: Paging
177 | public func collectionViewRect() -> CGRect? {
178 | guard let collectionView = self.collectionView else {
179 | return nil
180 | }
181 | let margins = UIEdgeInsets(top: collectionView.contentInset.top + collectionView.layoutMargins.top,
182 | left: collectionView.contentInset.left + collectionView.layoutMargins.left,
183 | bottom: collectionView.contentInset.bottom + collectionView.layoutMargins.bottom,
184 | right: collectionView.contentInset.right + collectionView.layoutMargins.right)
185 |
186 | var visibleRect = CGRect()
187 | visibleRect.origin.x = margins.left
188 | visibleRect.origin.y = margins.top
189 | visibleRect.size.width = collectionView.bounds.width - visibleRect.origin.x - margins.right
190 | visibleRect.size.height = collectionView.bounds.height - visibleRect.origin.y - margins.bottom
191 | return visibleRect
192 | }
193 |
194 | public func visibleCollectionViewRect() -> CGRect? {
195 | guard let collectionView = self.collectionView,
196 | var collectionViewRect = self.collectionViewRect() else {
197 | return nil
198 | }
199 | collectionViewRect.origin.x += collectionView.contentOffset.x
200 | collectionViewRect.origin.y += collectionView.contentOffset.y
201 | return collectionViewRect
202 | }
203 |
204 | public func visibleLayoutAttributes(at offset: CGPoint? = nil) -> [UICollectionViewLayoutAttributes] {
205 | guard let collectionView = self.collectionView else {
206 | return []
207 | }
208 | return (self.layoutAttributesForElements(in: CGRect(origin: offset ?? collectionView.contentOffset, size: collectionView.frame.size)) ?? [])
209 | .sorted(by: { lhs, rhs in
210 | guard let lhs = self.centeredContentOffset(forRect: lhs.frame) else {
211 | return false
212 | }
213 | guard let rhs = self.centeredContentOffset(forRect: rhs.frame) else {
214 | return true
215 | }
216 | let value: (CGPoint)->CGFloat = {
217 | return self.scrollDirection == .horizontal ? abs(collectionView.contentOffset.x - $0.x) : abs(collectionView.contentOffset.y - $0.y)
218 | }
219 | return value(lhs) < value(rhs)
220 | })
221 | }
222 |
223 | public func preferredVisibleLayoutAttributes(at offset: CGPoint? = nil, velocity: CGPoint = .zero, targetOffset: CGPoint? = nil, indexPath: IndexPath? = nil) -> UICollectionViewLayoutAttributes? {
224 | guard let currentOffset = self.collectionView?.contentOffset else {
225 | return nil
226 | }
227 | let direction: (CGPoint)->Bool = {
228 | return self.scrollDirection == .horizontal ? $0.x > currentOffset.x : $0.y > currentOffset.y
229 | }
230 | let velocity = self.scrollDirection == .horizontal ? velocity.x : velocity.y
231 | let targetDirection = direction(targetOffset ?? currentOffset)
232 | let attributes = self.visibleLayoutAttributes(at: offset)
233 | if let indexPath = indexPath,
234 | let attributes = attributes.first(where: { $0.indexPath == indexPath }) {
235 | return attributes
236 | }
237 | return attributes
238 | .first { attributes in
239 | guard let offset = self.centeredContentOffset(forRect: attributes.frame) else {
240 | return false
241 | }
242 | return direction(offset) == targetDirection || velocity == 0
243 | }
244 | }
245 |
246 | func centeredContentOffset(forRect rect: CGRect) -> CGPoint? {
247 | guard let collectionView = self.collectionView,
248 | let collectionRect = self.collectionViewRect() else {
249 | return nil
250 | }
251 | return CGPoint(x: self.scrollDirection == .horizontal ? abs(rect.midX - collectionRect.origin.x - collectionRect.width/2) : collectionView.contentOffset.x,
252 | y: self.scrollDirection == .vertical ? abs(rect.midY - collectionRect.origin.y - collectionRect.height/2) : collectionView.contentOffset.y)
253 | }
254 |
255 | public func centerCollectionView(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) {
256 | guard let collectionView = self.collectionView, self.hasValidLayout else {
257 | return
258 | }
259 | let newTarget = CGPoint(x: self.scrollDirection == .horizontal ? collectionView.contentOffset.x + velocity.x * velocityMultiplier : targetContentOffset.pointee.x,
260 | y: self.scrollDirection == .vertical ? collectionView.contentOffset.y + velocity.y * velocityMultiplier : targetContentOffset.pointee.y)
261 |
262 | guard let preferredAttributes = self.preferredVisibleLayoutAttributes(at: newTarget, velocity: velocity, targetOffset: targetContentOffset.pointee),
263 | let offset = self.centeredContentOffset(forRect: preferredAttributes.frame) else {
264 | return
265 | }
266 | targetContentOffset.pointee = offset
267 | }
268 |
269 | public func centerCollectionViewIfNeeded(indexPath: IndexPath? = nil) {
270 | guard let collectionView = self.collectionView, self.hasValidLayout else {
271 | return
272 | }
273 | guard let preferredAttributes = self.preferredVisibleLayoutAttributes(indexPath: indexPath),
274 | let offset = self.centeredContentOffset(forRect: preferredAttributes.frame),
275 | collectionView.contentOffset != offset else {
276 | return
277 | }
278 | self.updateContentOffset(offset)
279 | }
280 |
281 | // MARK: Copy
282 | public func copyLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes? {
283 | return attributes.copy() as? UICollectionViewLayoutAttributes
284 | }
285 |
286 | public func copyLayoutAttributes(from array: [UICollectionViewLayoutAttributes]) -> [UICollectionViewLayoutAttributes] {
287 | return array.map { self.copyLayoutAttributes($0) }
288 | .filter { $0 != nil }
289 | .map { $0! }
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/Sources/InfiniteLayout/SPMBridge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfiniteCollectionView.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 05/09/2019.
6 | //
7 |
8 | @_exported import CocoaProxy
9 |
--------------------------------------------------------------------------------
/Sources/Rx/InfiniteCollectionView+Rx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfiniteCollectionView+Rx.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 03/01/2018.
6 | //
7 |
8 | #if canImport(InfiniteLayout)
9 | import InfiniteLayout
10 | #endif
11 |
12 | import UIKit
13 | import RxSwift
14 | import RxCocoa
15 | import RxDataSources
16 |
17 | class RxInfiniteCollectionViewDelegate: DelegateProxy, DelegateProxyType, InfiniteCollectionViewDelegate {
18 |
19 | init(infiniteCollectionView: InfiniteCollectionView) {
20 | super.init(parentObject: infiniteCollectionView, delegateProxy: RxInfiniteCollectionViewDelegate.self)
21 | }
22 |
23 | static func registerKnownImplementations() {
24 | RxInfiniteCollectionViewDelegate.register {
25 | RxInfiniteCollectionViewDelegate(infiniteCollectionView: $0)
26 | }
27 | }
28 |
29 | static func currentDelegate(for object: InfiniteCollectionView) -> InfiniteCollectionViewDelegate? {
30 | return object.infiniteDelegate
31 | }
32 |
33 | static func setCurrentDelegate(_ delegate: InfiniteCollectionViewDelegate?, to object: InfiniteCollectionView) {
34 | object.infiniteDelegate = delegate
35 | }
36 | }
37 |
38 | extension Reactive where Base: InfiniteCollectionView {
39 |
40 | private var infiniteDelegate: RxInfiniteCollectionViewDelegate {
41 | return RxInfiniteCollectionViewDelegate.proxy(for: self.base)
42 | }
43 |
44 | public var itemCentered: ControlEvent {
45 | let source = infiniteDelegate.sentMessage(#selector(InfiniteCollectionViewDelegate.infiniteCollectionView(_:didChangeCenteredIndexPath:to:)))
46 | .map { $0.last as? IndexPath }
47 | return ControlEvent(events: source)
48 | }
49 |
50 | public func modelCentered(_ type: T.Type) -> ControlEvent {
51 | let source: Observable = itemCentered.flatMap { [weak view = self.base as InfiniteCollectionView] indexPath -> Observable in
52 | guard let view = view, var indexPath = indexPath else {
53 | return Observable.empty()
54 | }
55 |
56 | indexPath.row %= InfiniteDataSources.originCount
57 | return Observable.just(try view.rx.model(at: indexPath))
58 | }
59 | return ControlEvent(events: source)
60 | }
61 |
62 | public func modelSelected(_ modelType: T.Type) -> ControlEvent {
63 | let source: Observable = itemSelected.flatMap { [weak view = self.base as InfiniteCollectionView] indexPath -> Observable in
64 | guard let view = view else {
65 | return Observable.empty()
66 | }
67 |
68 | var indexPath = indexPath
69 | indexPath.row %= InfiniteDataSources.originCount
70 | return Observable.just(try view.rx.model(at: indexPath))
71 | }
72 |
73 | return ControlEvent(events: source)
74 | }
75 | }
76 |
77 | extension Reactive where Base: RxInfiniteCollectionView {
78 |
79 | public func items
80 | (infinite: Bool)
81 | -> (_ source: O)
82 | -> (_ cellFactory: @escaping (UICollectionView, Int, S.Iterator.Element) -> UICollectionViewCell)
83 | -> Disposable where O.Element == S {
84 | return { source in
85 | guard infinite else {
86 | return self.items(source)
87 | }
88 | return { cellFactory in
89 | let dataSource = RxInfiniteCollectionViewSectionedReloadDataSource>(configureCell: { _, collectionView, indexPath, element in
90 | return cellFactory(collectionView, indexPath.item, element)
91 | })
92 | return self.items(dataSource: dataSource)(source.map { [SectionModel(model: 0, items: $0.map { $0 })] })
93 | }
94 | }
95 | }
96 |
97 | public func items
98 | (cellIdentifier: String, cellType: Cell.Type = Cell.self, infinite: Bool)
99 | -> (_ source: O)
100 | -> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void)
101 | -> Disposable where O.Element == S {
102 | guard infinite else {
103 | return self.items(cellIdentifier: cellIdentifier, cellType: cellType)
104 | }
105 | return { source in
106 | return { configureCell in
107 | let dataSource = RxInfiniteCollectionViewSectionedReloadDataSource>(configureCell: { _, collectionView, indexPath, element in
108 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! Cell
109 | configureCell(indexPath.item, element, cell)
110 | return cell
111 | })
112 | return self.items(dataSource: dataSource)(source.map { [SectionModel(model: 0, items: $0.map { $0 })] })
113 | }
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Sources/Rx/RxInfiniteCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxInfiniteCollectionView.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 03/01/2018.
6 | //
7 |
8 | #if canImport(InfiniteLayout)
9 | import InfiniteLayout
10 | #endif
11 |
12 | import UIKit
13 | import RxSwift
14 | import RxCocoa
15 |
16 | open class RxInfiniteCollectionView: InfiniteCollectionView {
17 |
18 | let disposeBag = DisposeBag()
19 |
20 | override open var forwardDelegate: Bool {
21 | return false
22 | }
23 |
24 | public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
25 | super.init(frame: frame, collectionViewLayout: layout)
26 | setupRx()
27 | }
28 |
29 | required public init?(coder aDecoder: NSCoder) {
30 | super.init(coder: aDecoder)
31 | }
32 |
33 | open override func awakeFromNib() {
34 | super.awakeFromNib()
35 |
36 | setupRx()
37 | }
38 |
39 | func setupRx() {
40 |
41 | self.rx.setDelegate(self)
42 | .disposed(by: disposeBag)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Rx/RxInfiniteCollectionViewDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxInfiniteCollectionViewDelegate.swift
3 | // InfiniteLayout
4 | //
5 | // Created by Arnaud Dorgans on 03/01/2018.
6 | //
7 |
8 | #if canImport(InfiniteLayout)
9 | import InfiniteLayout
10 | #endif
11 |
12 | import UIKit
13 | import RxDataSources
14 |
15 | open class RxInfiniteCollectionViewSectionedReloadDataSource: RxCollectionViewSectionedReloadDataSource {
16 |
17 | public var isEnabled: Bool = true
18 |
19 | open override subscript(section: Int) -> S {
20 | let section = InfiniteDataSources.section(from: section, numberOfSections: sectionModels.count)
21 | return self.sectionModels[section]
22 | }
23 |
24 | open override subscript(indexPath: IndexPath) -> Item {
25 | get {
26 | let indexPath = InfiniteDataSources.indexPath(from: indexPath,
27 | numberOfSections: sectionModels.count,
28 | numberOfItems: self[indexPath.section].items.count)
29 | return super[indexPath]
30 | } set {
31 | let indexPath = InfiniteDataSources.indexPath(from: indexPath,
32 | numberOfSections: sectionModels.count,
33 | numberOfItems: self[indexPath.section].items.count)
34 | super[indexPath] = newValue
35 | }
36 | }
37 |
38 | private func multiplier(for collectionView: UICollectionView) -> Int {
39 | guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
40 | fatalError()
41 | }
42 | return InfiniteDataSources.multiplier(estimatedItemSize: layout.itemSize, enabled: isEnabled)
43 | }
44 |
45 | open override func numberOfSections(in collectionView: UICollectionView) -> Int {
46 | return InfiniteDataSources.numberOfSections(numberOfSections: self.sectionModels.count,
47 | multiplier: self.multiplier(for: collectionView))
48 | }
49 |
50 | open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
51 | return InfiniteDataSources.numberOfItemsInSection(numberOfItemsInSection: self[section].items.count,
52 | numberOfSections: self.sectionModels.count,
53 | multiplier: self.multiplier(for: collectionView))
54 | }
55 |
56 | open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
57 | return configureCell(self, collectionView, indexPath, self[indexPath])
58 | }
59 | }
60 |
61 | open class RxInfiniteCollectionViewSectionedAnimatedDataSource: RxCollectionViewSectionedAnimatedDataSource {
62 |
63 | public var isEnabled: Bool = true
64 |
65 | open override subscript(section: Int) -> S {
66 | let section = InfiniteDataSources.section(from: section, numberOfSections: sectionModels.count)
67 | return self.sectionModels[section]
68 | }
69 |
70 | open override subscript(indexPath: IndexPath) -> Item {
71 | get {
72 | let indexPath = InfiniteDataSources.indexPath(from: indexPath,
73 | numberOfSections: sectionModels.count,
74 | numberOfItems: self[indexPath.section].items.count)
75 | return super[indexPath]
76 | } set {
77 | let indexPath = InfiniteDataSources.indexPath(from: indexPath,
78 | numberOfSections: sectionModels.count,
79 | numberOfItems: self[indexPath.section].items.count)
80 | super[indexPath] = newValue
81 | }
82 | }
83 |
84 | private func multiplier(for collectionView: UICollectionView) -> Int {
85 | guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
86 | fatalError()
87 | }
88 | return InfiniteDataSources.multiplier(estimatedItemSize: layout.itemSize, enabled: isEnabled)
89 | }
90 |
91 | open override func numberOfSections(in collectionView: UICollectionView) -> Int {
92 | return InfiniteDataSources.numberOfSections(numberOfSections: self.sectionModels.count,
93 | multiplier: self.multiplier(for: collectionView))
94 | }
95 |
96 | open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
97 | return InfiniteDataSources.numberOfItemsInSection(numberOfItemsInSection: self[section].items.count,
98 | numberOfSections: self.sectionModels.count,
99 | multiplier: self.multiplier(for: collectionView))
100 | }
101 |
102 | open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
103 | return configureCell(self, collectionView, indexPath, self[indexPath])
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------
/custom.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arnauddorgans/InfiniteLayout/a576a83530a94ccb3367ab12c8eac7ec5520e8c1/custom.gif
--------------------------------------------------------------------------------
/delegate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arnauddorgans/InfiniteLayout/a576a83530a94ccb3367ab12c8eac7ec5520e8c1/delegate.gif
--------------------------------------------------------------------------------
/horizontal.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arnauddorgans/InfiniteLayout/a576a83530a94ccb3367ab12c8eac7ec5520e8c1/horizontal.gif
--------------------------------------------------------------------------------
/vertical.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arnauddorgans/InfiniteLayout/a576a83530a94ccb3367ab12c8eac7ec5520e8c1/vertical.gif
--------------------------------------------------------------------------------