├── .gitignore
├── JZSpringRefreshDemo
├── JZSpringRefresh
│ ├── Info.plist
│ └── JZSpringRefresh.h
├── JZSpringRefreshDemo.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── JZSpringRefresh.xcscheme
└── JZSpringRefreshDemo
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── JZSpringRefresh
│ ├── JZSpringExpandView.swift
│ └── JZSpringRefresh.swift
│ └── ViewController.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.pbxuser
3 | !default.pbxuser
4 | *.mode1v3
5 | !default.mode1v3
6 | *.mode2v3
7 | !default.mode2v3
8 | *.perspectivev3
9 | !default.perspectivev3
10 | xcuserdata
11 | *.xccheckout
12 | *.moved-aside
13 | DerivedData
14 | *.hmap
15 | *.ipa
16 | *.xcuserstate
17 | .DS_Store
18 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefresh/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 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.3
19 | CFBundleVersion
20 | 2
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefresh/JZSpringRefresh.h:
--------------------------------------------------------------------------------
1 | //
2 | // JZSpringRefresh.h
3 | // JZSpringRefresh
4 | //
5 | // Created by JackyZ on 2017/01/10.
6 | // Copyright © 2017年 Salmonapps. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for JZSpringRefresh.
12 | FOUNDATION_EXPORT double JZSpringRefreshVersionNumber;
13 |
14 | //! Project version string for JZSpringRefresh.
15 | FOUNDATION_EXPORT const unsigned char JZSpringRefreshVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | CE7EA5271E23B2080043357D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EA5261E23B2080043357D /* AppDelegate.swift */; };
11 | CE7EA5291E23B2080043357D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EA5281E23B2080043357D /* ViewController.swift */; };
12 | CE7EA52C1E23B2080043357D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE7EA52A1E23B2080043357D /* Main.storyboard */; };
13 | CE7EA52E1E23B2080043357D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE7EA52D1E23B2080043357D /* Assets.xcassets */; };
14 | CE7EA5311E23B2080043357D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE7EA52F1E23B2080043357D /* LaunchScreen.storyboard */; };
15 | CE7EA53A1E23B5A80043357D /* JZSpringRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EA5391E23B5A80043357D /* JZSpringRefresh.swift */; };
16 | CE7EA53E1E23BC1D0043357D /* JZSpringExpandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EA53D1E23BC1D0043357D /* JZSpringExpandView.swift */; };
17 | CE7EA5481E251F970043357D /* JZSpringRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = CE7EA5461E251F970043357D /* JZSpringRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; };
18 | CE7EA54B1E251F970043357D /* JZSpringRefresh.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE7EA5441E251F970043357D /* JZSpringRefresh.framework */; };
19 | CE7EA54C1E251F970043357D /* JZSpringRefresh.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE7EA5441E251F970043357D /* JZSpringRefresh.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | CE7EA5511E251FA00043357D /* JZSpringExpandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EA53D1E23BC1D0043357D /* JZSpringExpandView.swift */; };
21 | CE7EA5521E251FA30043357D /* JZSpringRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EA5391E23B5A80043357D /* JZSpringRefresh.swift */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | CE7EA5491E251F970043357D /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = CE7EA51B1E23B2080043357D /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = CE7EA5431E251F970043357D;
30 | remoteInfo = JZSpringRefresh;
31 | };
32 | /* End PBXContainerItemProxy section */
33 |
34 | /* Begin PBXCopyFilesBuildPhase section */
35 | CE7EA5501E251F970043357D /* Embed Frameworks */ = {
36 | isa = PBXCopyFilesBuildPhase;
37 | buildActionMask = 2147483647;
38 | dstPath = "";
39 | dstSubfolderSpec = 10;
40 | files = (
41 | CE7EA54C1E251F970043357D /* JZSpringRefresh.framework in Embed Frameworks */,
42 | );
43 | name = "Embed Frameworks";
44 | runOnlyForDeploymentPostprocessing = 0;
45 | };
46 | /* End PBXCopyFilesBuildPhase section */
47 |
48 | /* Begin PBXFileReference section */
49 | CE7EA5231E23B2080043357D /* JZSpringRefreshDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JZSpringRefreshDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
50 | CE7EA5261E23B2080043357D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
51 | CE7EA5281E23B2080043357D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
52 | CE7EA52B1E23B2080043357D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
53 | CE7EA52D1E23B2080043357D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
54 | CE7EA5301E23B2080043357D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
55 | CE7EA5321E23B2080043357D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
56 | CE7EA5391E23B5A80043357D /* JZSpringRefresh.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JZSpringRefresh.swift; sourceTree = ""; };
57 | CE7EA53D1E23BC1D0043357D /* JZSpringExpandView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JZSpringExpandView.swift; sourceTree = ""; };
58 | CE7EA5441E251F970043357D /* JZSpringRefresh.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JZSpringRefresh.framework; sourceTree = BUILT_PRODUCTS_DIR; };
59 | CE7EA5461E251F970043357D /* JZSpringRefresh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JZSpringRefresh.h; sourceTree = ""; };
60 | CE7EA5471E251F970043357D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | /* End PBXFileReference section */
62 |
63 | /* Begin PBXFrameworksBuildPhase section */
64 | CE7EA5201E23B2080043357D /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | CE7EA54B1E251F970043357D /* JZSpringRefresh.framework in Frameworks */,
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | CE7EA5401E251F970043357D /* Frameworks */ = {
73 | isa = PBXFrameworksBuildPhase;
74 | buildActionMask = 2147483647;
75 | files = (
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | /* End PBXFrameworksBuildPhase section */
80 |
81 | /* Begin PBXGroup section */
82 | CE7EA51A1E23B2080043357D = {
83 | isa = PBXGroup;
84 | children = (
85 | CE7EA5251E23B2080043357D /* JZSpringRefreshDemo */,
86 | CE7EA5451E251F970043357D /* JZSpringRefresh */,
87 | CE7EA5241E23B2080043357D /* Products */,
88 | );
89 | sourceTree = "";
90 | };
91 | CE7EA5241E23B2080043357D /* Products */ = {
92 | isa = PBXGroup;
93 | children = (
94 | CE7EA5231E23B2080043357D /* JZSpringRefreshDemo.app */,
95 | CE7EA5441E251F970043357D /* JZSpringRefresh.framework */,
96 | );
97 | name = Products;
98 | sourceTree = "";
99 | };
100 | CE7EA5251E23B2080043357D /* JZSpringRefreshDemo */ = {
101 | isa = PBXGroup;
102 | children = (
103 | CE7EA5381E23B2170043357D /* JZSpringRefresh */,
104 | CE7EA5261E23B2080043357D /* AppDelegate.swift */,
105 | CE7EA5281E23B2080043357D /* ViewController.swift */,
106 | CE7EA52A1E23B2080043357D /* Main.storyboard */,
107 | CE7EA52D1E23B2080043357D /* Assets.xcassets */,
108 | CE7EA52F1E23B2080043357D /* LaunchScreen.storyboard */,
109 | CE7EA5321E23B2080043357D /* Info.plist */,
110 | );
111 | path = JZSpringRefreshDemo;
112 | sourceTree = "";
113 | };
114 | CE7EA5381E23B2170043357D /* JZSpringRefresh */ = {
115 | isa = PBXGroup;
116 | children = (
117 | CE7EA53D1E23BC1D0043357D /* JZSpringExpandView.swift */,
118 | CE7EA5391E23B5A80043357D /* JZSpringRefresh.swift */,
119 | );
120 | path = JZSpringRefresh;
121 | sourceTree = "";
122 | };
123 | CE7EA5451E251F970043357D /* JZSpringRefresh */ = {
124 | isa = PBXGroup;
125 | children = (
126 | CE7EA5461E251F970043357D /* JZSpringRefresh.h */,
127 | CE7EA5471E251F970043357D /* Info.plist */,
128 | );
129 | path = JZSpringRefresh;
130 | sourceTree = "";
131 | };
132 | /* End PBXGroup section */
133 |
134 | /* Begin PBXHeadersBuildPhase section */
135 | CE7EA5411E251F970043357D /* Headers */ = {
136 | isa = PBXHeadersBuildPhase;
137 | buildActionMask = 2147483647;
138 | files = (
139 | CE7EA5481E251F970043357D /* JZSpringRefresh.h in Headers */,
140 | );
141 | runOnlyForDeploymentPostprocessing = 0;
142 | };
143 | /* End PBXHeadersBuildPhase section */
144 |
145 | /* Begin PBXNativeTarget section */
146 | CE7EA5221E23B2080043357D /* JZSpringRefreshDemo */ = {
147 | isa = PBXNativeTarget;
148 | buildConfigurationList = CE7EA5351E23B2080043357D /* Build configuration list for PBXNativeTarget "JZSpringRefreshDemo" */;
149 | buildPhases = (
150 | CE7EA51F1E23B2080043357D /* Sources */,
151 | CE7EA5201E23B2080043357D /* Frameworks */,
152 | CE7EA5211E23B2080043357D /* Resources */,
153 | CE7EA5501E251F970043357D /* Embed Frameworks */,
154 | );
155 | buildRules = (
156 | );
157 | dependencies = (
158 | CE7EA54A1E251F970043357D /* PBXTargetDependency */,
159 | );
160 | name = JZSpringRefreshDemo;
161 | productName = JZSpringRefreshDemo;
162 | productReference = CE7EA5231E23B2080043357D /* JZSpringRefreshDemo.app */;
163 | productType = "com.apple.product-type.application";
164 | };
165 | CE7EA5431E251F970043357D /* JZSpringRefresh */ = {
166 | isa = PBXNativeTarget;
167 | buildConfigurationList = CE7EA54F1E251F970043357D /* Build configuration list for PBXNativeTarget "JZSpringRefresh" */;
168 | buildPhases = (
169 | CE7EA53F1E251F970043357D /* Sources */,
170 | CE7EA5401E251F970043357D /* Frameworks */,
171 | CE7EA5411E251F970043357D /* Headers */,
172 | CE7EA5421E251F970043357D /* Resources */,
173 | );
174 | buildRules = (
175 | );
176 | dependencies = (
177 | );
178 | name = JZSpringRefresh;
179 | productName = JZSpringRefresh;
180 | productReference = CE7EA5441E251F970043357D /* JZSpringRefresh.framework */;
181 | productType = "com.apple.product-type.framework";
182 | };
183 | /* End PBXNativeTarget section */
184 |
185 | /* Begin PBXProject section */
186 | CE7EA51B1E23B2080043357D /* Project object */ = {
187 | isa = PBXProject;
188 | attributes = {
189 | LastSwiftUpdateCheck = 0820;
190 | LastUpgradeCheck = 0820;
191 | ORGANIZATIONNAME = Salmonapps;
192 | TargetAttributes = {
193 | CE7EA5221E23B2080043357D = {
194 | CreatedOnToolsVersion = 8.2.1;
195 | DevelopmentTeam = 72T2SXUWHC;
196 | ProvisioningStyle = Automatic;
197 | };
198 | CE7EA5431E251F970043357D = {
199 | CreatedOnToolsVersion = 8.2.1;
200 | DevelopmentTeam = 72T2SXUWHC;
201 | ProvisioningStyle = Automatic;
202 | };
203 | };
204 | };
205 | buildConfigurationList = CE7EA51E1E23B2080043357D /* Build configuration list for PBXProject "JZSpringRefreshDemo" */;
206 | compatibilityVersion = "Xcode 3.2";
207 | developmentRegion = English;
208 | hasScannedForEncodings = 0;
209 | knownRegions = (
210 | en,
211 | Base,
212 | );
213 | mainGroup = CE7EA51A1E23B2080043357D;
214 | productRefGroup = CE7EA5241E23B2080043357D /* Products */;
215 | projectDirPath = "";
216 | projectRoot = "";
217 | targets = (
218 | CE7EA5221E23B2080043357D /* JZSpringRefreshDemo */,
219 | CE7EA5431E251F970043357D /* JZSpringRefresh */,
220 | );
221 | };
222 | /* End PBXProject section */
223 |
224 | /* Begin PBXResourcesBuildPhase section */
225 | CE7EA5211E23B2080043357D /* Resources */ = {
226 | isa = PBXResourcesBuildPhase;
227 | buildActionMask = 2147483647;
228 | files = (
229 | CE7EA5311E23B2080043357D /* LaunchScreen.storyboard in Resources */,
230 | CE7EA52E1E23B2080043357D /* Assets.xcassets in Resources */,
231 | CE7EA52C1E23B2080043357D /* Main.storyboard in Resources */,
232 | );
233 | runOnlyForDeploymentPostprocessing = 0;
234 | };
235 | CE7EA5421E251F970043357D /* Resources */ = {
236 | isa = PBXResourcesBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | };
242 | /* End PBXResourcesBuildPhase section */
243 |
244 | /* Begin PBXSourcesBuildPhase section */
245 | CE7EA51F1E23B2080043357D /* Sources */ = {
246 | isa = PBXSourcesBuildPhase;
247 | buildActionMask = 2147483647;
248 | files = (
249 | CE7EA5291E23B2080043357D /* ViewController.swift in Sources */,
250 | CE7EA53E1E23BC1D0043357D /* JZSpringExpandView.swift in Sources */,
251 | CE7EA5271E23B2080043357D /* AppDelegate.swift in Sources */,
252 | CE7EA53A1E23B5A80043357D /* JZSpringRefresh.swift in Sources */,
253 | );
254 | runOnlyForDeploymentPostprocessing = 0;
255 | };
256 | CE7EA53F1E251F970043357D /* Sources */ = {
257 | isa = PBXSourcesBuildPhase;
258 | buildActionMask = 2147483647;
259 | files = (
260 | CE7EA5521E251FA30043357D /* JZSpringRefresh.swift in Sources */,
261 | CE7EA5511E251FA00043357D /* JZSpringExpandView.swift in Sources */,
262 | );
263 | runOnlyForDeploymentPostprocessing = 0;
264 | };
265 | /* End PBXSourcesBuildPhase section */
266 |
267 | /* Begin PBXTargetDependency section */
268 | CE7EA54A1E251F970043357D /* PBXTargetDependency */ = {
269 | isa = PBXTargetDependency;
270 | target = CE7EA5431E251F970043357D /* JZSpringRefresh */;
271 | targetProxy = CE7EA5491E251F970043357D /* PBXContainerItemProxy */;
272 | };
273 | /* End PBXTargetDependency section */
274 |
275 | /* Begin PBXVariantGroup section */
276 | CE7EA52A1E23B2080043357D /* Main.storyboard */ = {
277 | isa = PBXVariantGroup;
278 | children = (
279 | CE7EA52B1E23B2080043357D /* Base */,
280 | );
281 | name = Main.storyboard;
282 | sourceTree = "";
283 | };
284 | CE7EA52F1E23B2080043357D /* LaunchScreen.storyboard */ = {
285 | isa = PBXVariantGroup;
286 | children = (
287 | CE7EA5301E23B2080043357D /* Base */,
288 | );
289 | name = LaunchScreen.storyboard;
290 | sourceTree = "";
291 | };
292 | /* End PBXVariantGroup section */
293 |
294 | /* Begin XCBuildConfiguration section */
295 | CE7EA5331E23B2080043357D /* Debug */ = {
296 | isa = XCBuildConfiguration;
297 | buildSettings = {
298 | ALWAYS_SEARCH_USER_PATHS = NO;
299 | CLANG_ANALYZER_NONNULL = YES;
300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
301 | CLANG_CXX_LIBRARY = "libc++";
302 | CLANG_ENABLE_MODULES = YES;
303 | CLANG_ENABLE_OBJC_ARC = YES;
304 | CLANG_WARN_BOOL_CONVERSION = YES;
305 | CLANG_WARN_CONSTANT_CONVERSION = YES;
306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
307 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
308 | CLANG_WARN_EMPTY_BODY = YES;
309 | CLANG_WARN_ENUM_CONVERSION = YES;
310 | CLANG_WARN_INFINITE_RECURSION = YES;
311 | CLANG_WARN_INT_CONVERSION = YES;
312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
313 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
314 | CLANG_WARN_UNREACHABLE_CODE = YES;
315 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
316 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
317 | COPY_PHASE_STRIP = NO;
318 | DEBUG_INFORMATION_FORMAT = dwarf;
319 | ENABLE_STRICT_OBJC_MSGSEND = YES;
320 | ENABLE_TESTABILITY = YES;
321 | GCC_C_LANGUAGE_STANDARD = gnu99;
322 | GCC_DYNAMIC_NO_PIC = NO;
323 | GCC_NO_COMMON_BLOCKS = YES;
324 | GCC_OPTIMIZATION_LEVEL = 0;
325 | GCC_PREPROCESSOR_DEFINITIONS = (
326 | "DEBUG=1",
327 | "$(inherited)",
328 | );
329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
331 | GCC_WARN_UNDECLARED_SELECTOR = YES;
332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
333 | GCC_WARN_UNUSED_FUNCTION = YES;
334 | GCC_WARN_UNUSED_VARIABLE = YES;
335 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
336 | MTL_ENABLE_DEBUG_INFO = YES;
337 | ONLY_ACTIVE_ARCH = YES;
338 | SDKROOT = iphoneos;
339 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
340 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
341 | TARGETED_DEVICE_FAMILY = "1,2";
342 | };
343 | name = Debug;
344 | };
345 | CE7EA5341E23B2080043357D /* Release */ = {
346 | isa = XCBuildConfiguration;
347 | buildSettings = {
348 | ALWAYS_SEARCH_USER_PATHS = NO;
349 | CLANG_ANALYZER_NONNULL = YES;
350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
351 | CLANG_CXX_LIBRARY = "libc++";
352 | CLANG_ENABLE_MODULES = YES;
353 | CLANG_ENABLE_OBJC_ARC = YES;
354 | CLANG_WARN_BOOL_CONVERSION = YES;
355 | CLANG_WARN_CONSTANT_CONVERSION = YES;
356 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
357 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
358 | CLANG_WARN_EMPTY_BODY = YES;
359 | CLANG_WARN_ENUM_CONVERSION = YES;
360 | CLANG_WARN_INFINITE_RECURSION = YES;
361 | CLANG_WARN_INT_CONVERSION = YES;
362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
363 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
364 | CLANG_WARN_UNREACHABLE_CODE = YES;
365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
366 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
367 | COPY_PHASE_STRIP = NO;
368 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
369 | ENABLE_NS_ASSERTIONS = NO;
370 | ENABLE_STRICT_OBJC_MSGSEND = YES;
371 | GCC_C_LANGUAGE_STANDARD = gnu99;
372 | GCC_NO_COMMON_BLOCKS = YES;
373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
375 | GCC_WARN_UNDECLARED_SELECTOR = YES;
376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
377 | GCC_WARN_UNUSED_FUNCTION = YES;
378 | GCC_WARN_UNUSED_VARIABLE = YES;
379 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
380 | MTL_ENABLE_DEBUG_INFO = NO;
381 | SDKROOT = iphoneos;
382 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
383 | TARGETED_DEVICE_FAMILY = "1,2";
384 | VALIDATE_PRODUCT = YES;
385 | };
386 | name = Release;
387 | };
388 | CE7EA5361E23B2080043357D /* Debug */ = {
389 | isa = XCBuildConfiguration;
390 | buildSettings = {
391 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
392 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
393 | DEVELOPMENT_TEAM = 72T2SXUWHC;
394 | INFOPLIST_FILE = JZSpringRefreshDemo/Info.plist;
395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
396 | PRODUCT_BUNDLE_IDENTIFIER = com.salmonapps.app.JZSpringRefreshDemo;
397 | PRODUCT_NAME = "$(TARGET_NAME)";
398 | SWIFT_VERSION = 3.0;
399 | };
400 | name = Debug;
401 | };
402 | CE7EA5371E23B2080043357D /* Release */ = {
403 | isa = XCBuildConfiguration;
404 | buildSettings = {
405 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
406 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
407 | DEVELOPMENT_TEAM = 72T2SXUWHC;
408 | INFOPLIST_FILE = JZSpringRefreshDemo/Info.plist;
409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
410 | PRODUCT_BUNDLE_IDENTIFIER = com.salmonapps.app.JZSpringRefreshDemo;
411 | PRODUCT_NAME = "$(TARGET_NAME)";
412 | SWIFT_VERSION = 3.0;
413 | };
414 | name = Release;
415 | };
416 | CE7EA54D1E251F970043357D /* Debug */ = {
417 | isa = XCBuildConfiguration;
418 | buildSettings = {
419 | CODE_SIGN_IDENTITY = "";
420 | CURRENT_PROJECT_VERSION = 1;
421 | DEFINES_MODULE = YES;
422 | DEVELOPMENT_TEAM = 72T2SXUWHC;
423 | DYLIB_COMPATIBILITY_VERSION = 1;
424 | DYLIB_CURRENT_VERSION = 1;
425 | DYLIB_INSTALL_NAME_BASE = "@rpath";
426 | INFOPLIST_FILE = JZSpringRefresh/Info.plist;
427 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
429 | PRODUCT_BUNDLE_IDENTIFIER = com.salmonapps.app.JZSpringRefresh;
430 | PRODUCT_NAME = "$(TARGET_NAME)";
431 | SKIP_INSTALL = YES;
432 | SWIFT_VERSION = 3.0;
433 | VERSIONING_SYSTEM = "apple-generic";
434 | VERSION_INFO_PREFIX = "";
435 | };
436 | name = Debug;
437 | };
438 | CE7EA54E1E251F970043357D /* Release */ = {
439 | isa = XCBuildConfiguration;
440 | buildSettings = {
441 | CODE_SIGN_IDENTITY = "";
442 | CURRENT_PROJECT_VERSION = 1;
443 | DEFINES_MODULE = YES;
444 | DEVELOPMENT_TEAM = 72T2SXUWHC;
445 | DYLIB_COMPATIBILITY_VERSION = 1;
446 | DYLIB_CURRENT_VERSION = 1;
447 | DYLIB_INSTALL_NAME_BASE = "@rpath";
448 | INFOPLIST_FILE = JZSpringRefresh/Info.plist;
449 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
451 | PRODUCT_BUNDLE_IDENTIFIER = com.salmonapps.app.JZSpringRefresh;
452 | PRODUCT_NAME = "$(TARGET_NAME)";
453 | SKIP_INSTALL = YES;
454 | SWIFT_VERSION = 3.0;
455 | VERSIONING_SYSTEM = "apple-generic";
456 | VERSION_INFO_PREFIX = "";
457 | };
458 | name = Release;
459 | };
460 | /* End XCBuildConfiguration section */
461 |
462 | /* Begin XCConfigurationList section */
463 | CE7EA51E1E23B2080043357D /* Build configuration list for PBXProject "JZSpringRefreshDemo" */ = {
464 | isa = XCConfigurationList;
465 | buildConfigurations = (
466 | CE7EA5331E23B2080043357D /* Debug */,
467 | CE7EA5341E23B2080043357D /* Release */,
468 | );
469 | defaultConfigurationIsVisible = 0;
470 | defaultConfigurationName = Release;
471 | };
472 | CE7EA5351E23B2080043357D /* Build configuration list for PBXNativeTarget "JZSpringRefreshDemo" */ = {
473 | isa = XCConfigurationList;
474 | buildConfigurations = (
475 | CE7EA5361E23B2080043357D /* Debug */,
476 | CE7EA5371E23B2080043357D /* Release */,
477 | );
478 | defaultConfigurationIsVisible = 0;
479 | defaultConfigurationName = Release;
480 | };
481 | CE7EA54F1E251F970043357D /* Build configuration list for PBXNativeTarget "JZSpringRefresh" */ = {
482 | isa = XCConfigurationList;
483 | buildConfigurations = (
484 | CE7EA54D1E251F970043357D /* Debug */,
485 | CE7EA54E1E251F970043357D /* Release */,
486 | );
487 | defaultConfigurationIsVisible = 0;
488 | defaultConfigurationName = Release;
489 | };
490 | /* End XCConfigurationList section */
491 | };
492 | rootObject = CE7EA51B1E23B2080043357D /* Project object */;
493 | }
494 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo.xcodeproj/xcshareddata/xcschemes/JZSpringRefresh.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // JZSpringRefreshDemo
4 | //
5 | // Created by JackyZ on 2017/01/09.
6 | // Copyright © 2017年 Salmonapps. 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: [UIApplicationLaunchOptionsKey: 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 invalidate graphics rendering callbacks. 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 active 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 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/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 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/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.3
19 | CFBundleVersion
20 | 2
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/JZSpringRefresh/JZSpringExpandView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JZSpringExpandView.swift
3 | // JZSpringRefreshDemo
4 | //
5 | // Created by JackyZ on 2017/01/09.
6 | // Copyright © 2017年 Salmonapps. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class JZSpringExpandView: UIView {
12 |
13 | var isSidePosition:Bool = false
14 | private var expended:Bool = false
15 | var isExpanded:Bool {
16 | return expended
17 | }
18 |
19 | let stretchingView:UIView = UIView(frame: CGRect.zero)
20 |
21 | override init(frame: CGRect) {
22 | super.init(frame: frame)
23 | self.addSubview(stretchingView)
24 | }
25 |
26 | required init?(coder aDecoder: NSCoder) {
27 | fatalError("init(coder:) has not been implemented")
28 | }
29 |
30 | override func layoutSubviews() {
31 | super.layoutSubviews()
32 | configureViewsForExpandedState(expanded: isExpanded, animated: false)
33 | stretchingView.layer.cornerRadius = isSidePosition ? stretchingView.bounds.midX : stretchingView.bounds.maxY
34 | }
35 |
36 | // - MARK: Public
37 | func setExpanded(expanded:Bool, animated:Bool) {
38 | if isExpanded != expanded {
39 | configureViewsForExpandedState(expanded: expanded, animated: animated)
40 | }
41 | }
42 |
43 | func setColor(color:UIColor) {
44 | stretchingView.backgroundColor = color
45 | }
46 |
47 | // - MARK: Private
48 | func configureViewsForExpandedState(expanded:Bool, animated:Bool) {
49 |
50 | if expanded {
51 | expandAnimated(animated: animated)
52 | }else {
53 | collapseAnimated(animated: animated)
54 | }
55 | }
56 |
57 | func expandAnimated(animated:Bool) {
58 | let expandBlock = { () -> Void in
59 | self.stretchingView.frame = self.frameForExpandedState()
60 | self.expended = true
61 | }
62 |
63 | if animated {
64 | performBlockInAnimation(blockToAnimate: expandBlock)
65 | }else {
66 | expandBlock()
67 | }
68 | }
69 |
70 | func collapseAnimated(animated:Bool) {
71 | let collapseBlock = { () -> Void in
72 | self.stretchingView.frame = self.frameForCollapsedState()
73 | self.expended = false
74 | }
75 |
76 | if animated {
77 | performBlockInAnimation(blockToAnimate: collapseBlock)
78 | }else {
79 | collapseBlock()
80 | }
81 | }
82 |
83 | func performBlockInAnimation(blockToAnimate: @escaping () -> () ) {
84 | UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.55, initialSpringVelocity: 0.4, options: [.beginFromCurrentState], animations: {
85 | blockToAnimate()
86 | }, completion: nil)
87 | }
88 |
89 | // - MARK: Helpers
90 | func frameForCollapsedState() -> CGRect {
91 | if isSidePosition {
92 | return CGRect(x: 0.0,
93 | y: bounds.midY - bounds.width / 2.0,
94 | width: bounds.width,
95 | height: bounds.width)
96 | } else {
97 | return CGRect(x: bounds.midX - bounds.height / 2.0,
98 | y: 0.0,
99 | width: bounds.height,
100 | height: bounds.height)
101 | }
102 | }
103 |
104 | func frameForExpandedState() -> CGRect {
105 | if isSidePosition {
106 | return CGRect(x: 0.0,
107 | y: 0.0,
108 | width: bounds.width / 2.0,
109 | height: bounds.height)
110 | } else {
111 | return CGRect(x: 0.0,
112 | y: 0.0,
113 | width: bounds.width,
114 | height: bounds.height / 2.0)
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/JZSpringRefresh/JZSpringRefresh.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JZSpringRefresh.swift
3 | // JZSpringRefreshDemo
4 | //
5 | // Created by JackyZ on 2017/01/09.
6 | // Copyright © 2017年 Salmonapps. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum JZSpringRefreshPosition {
12 | case top
13 | case bottom
14 | case left
15 | case right
16 | }
17 |
18 | public extension UIScrollView {
19 | public func addSpringRefresh(position:JZSpringRefreshPosition, actionHandlere handler: @escaping (_ springRefresh:JZSpringRefresh) -> Void ) -> JZSpringRefresh {
20 |
21 | // Don't add two instance to same position.
22 | for v in subviews {
23 | if v is JZSpringRefresh {
24 | if (v as? JZSpringRefresh)?.position == position {
25 | return v as! JZSpringRefresh
26 | }
27 | }
28 | }
29 |
30 | let springRefresh = JZSpringRefresh(position: position)
31 | springRefresh.scrollView = self
32 | springRefresh.pullToRefreshHandler = handler
33 | springRefresh.showed = true
34 | addSubview(springRefresh)
35 | return springRefresh
36 | }
37 | }
38 |
39 | public class JZSpringRefresh: UIView {
40 |
41 | public var unExpandedColor = UIColor.gray
42 | public var expandedColor = UIColor.black
43 | public var readyColor = UIColor.red
44 | public var text:String? = nil {
45 | didSet {
46 | if text != "" && ( self.position == .top || self.position == .bottom ) {
47 | // dont add multiple margin per change text.
48 | if oldValue == nil {
49 | self.affordanceMargin = self.affordanceMargin + 20.0
50 | }
51 | self.label.text = text
52 | self.addSubview(self.label)
53 | } else {
54 | if self.text != nil {
55 | self.affordanceMargin = self.affordanceMargin - 20.0
56 | }
57 | self.label.removeFromSuperview()
58 | }
59 | self.setNeedsLayout()
60 | }
61 | }// available for position Top or Bottom.
62 | public var borderThickness:CGFloat = 6.0 {
63 | didSet {
64 | self.setNeedsLayout()
65 | }
66 | } // default: 6.0.
67 | public var affordanceMargin:CGFloat = 10.0 // default: 10.0. to adjust space between scrollView edge and affordanceView.
68 | public var offsetMargin:CGFloat = 30.0 // default: 30.0. to adjust threshold of offset.
69 | public var threshold:CGFloat = 0.0 // default is width or height of size.
70 | public var size = CGSize.zero {
71 | didSet {
72 | self.setNeedsLayout()
73 | }
74 | } // to adjust expanded size and each interval space.
75 | public var showed:Bool = false {
76 | didSet {
77 | self.isHidden = !showed
78 | if self.showed != oldValue {
79 | if showed {
80 | self.scrollView!.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
81 | self.scrollView!.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
82 | self.scrollView!.addObserver(self, forKeyPath: "frame", options: .new, context: nil)
83 | }else {
84 | self.scrollView!.removeObserver(self, forKeyPath: "contentOffset")
85 | self.scrollView!.removeObserver(self, forKeyPath: "contentSize")
86 | self.scrollView!.removeObserver(self, forKeyPath: "frame")
87 | }
88 | }
89 | }
90 | }
91 | public var isShowed:Bool { // dynamic show/hide affordanceView and add/remove KVO observer.
92 | return showed
93 | }
94 | public var pullToRefreshHandler:((_ springRefresh:JZSpringRefresh)->Void)? = nil
95 | public var progress:CGFloat = 0.0 {
96 |
97 | didSet {
98 | var p:CGFloat = 0.0
99 | if progress > 0 {
100 | p = progress
101 | }
102 | let progressInterval = 1.0 / CGFloat(self.springExpandViews.count)
103 | var index = 1
104 | for springExpandView in self.springExpandViews {
105 | let expanded = (CGFloat(index) * progressInterval) <= p
106 | if p >= 1.0 {
107 | springExpandView.setColor(color: self.readyColor)
108 | self.label.textColor = self.readyColor
109 | } else if (expanded) {
110 | springExpandView.setColor(color: expandedColor)
111 | } else {
112 | springExpandView.setColor(color: self.unExpandedColor)
113 | self.label.textColor = self.expandedColor
114 | }
115 | springExpandView.setExpanded(expanded: expanded, animated: true)
116 | index += 1
117 | }
118 | }
119 | }
120 | private(set) var position:JZSpringRefreshPosition = .top
121 |
122 | public var isUserAction:Bool = false
123 | var springExpandViews:[JZSpringExpandView] = []
124 | var scrollView:UIScrollView? = nil
125 | public var label:UILabel = UILabel(frame: CGRect.zero)
126 |
127 | // - MARK: init
128 | public init(position:JZSpringRefreshPosition) {
129 | super.init(frame:CGRect.zero)
130 | let isSidePosition = position == .left || position == .right
131 | self.position = position
132 | size = isSidePosition ? CGSize(width: 40.0, height: 60.0) : CGSize(width: 60.0, height: 40.0)
133 | label.backgroundColor = UIColor.clear
134 | label.textAlignment = .center
135 | label.font = UIFont(name: "AvenirNext-Regular", size: 12.0)
136 | label.alpha = 0.0
137 |
138 | let springExpandView1 = JZSpringExpandView(frame: CGRect.zero)
139 | springExpandView1.isSidePosition = isSidePosition
140 | addSubview(springExpandView1)
141 |
142 | let springExpandView2 = JZSpringExpandView(frame: CGRect.zero)
143 | springExpandView2.isSidePosition = isSidePosition
144 | addSubview(springExpandView2)
145 |
146 | let springExpandView3 = JZSpringExpandView(frame: CGRect.zero)
147 | springExpandView3.isSidePosition = isSidePosition
148 | addSubview(springExpandView3)
149 |
150 | springExpandViews = [springExpandView1, springExpandView2, springExpandView3]
151 | }
152 |
153 | public required init?(coder aDecoder: NSCoder) {
154 | fatalError("init(coder:) has not been implemented")
155 | }
156 |
157 | // - MARK: override
158 | override public func layoutSubviews() {
159 | super.layoutSubviews()
160 |
161 | switch position {
162 | case .top:
163 | let frame = CGRect(x: scrollView!.bounds.midX - size.width / 2.0,
164 | y: -self.affordanceMargin,
165 | width: size.width,
166 | height: size.height)
167 | if self.text != nil {
168 | label.frame = CGRect(x: (-self.scrollView!.bounds.size.width+self.size.width)/2.0,
169 | y: 5,
170 | width: self.scrollView!.frame.size.width,
171 | height: 15.0)
172 | }
173 | self.frame = frame
174 | break
175 | case .bottom:
176 | let y = self.scrollView!.contentSize.height;
177 | self.frame = CGRect(x:self.scrollView!.bounds.midX - (self.size.width / 2.0),
178 | y: y + self.affordanceMargin,
179 | width: self.size.width,
180 | height: self.size.height);
181 | if (self.text != nil) {
182 | self.label.frame = CGRect(x: (-self.scrollView!.bounds.size.width+self.size.width)/2.0,
183 | y: -20.0,
184 | width: self.scrollView!.frame.size.width,
185 | height: 15.0);
186 | }
187 | if self.frame.origin.y > 100.0 {
188 | self.alpha = 1.0;
189 | }
190 | break
191 | case .left:
192 | self.frame = CGRect(x: -self.affordanceMargin,
193 | y: self.scrollView!.bounds.midY - (self.size.height / 2.0),
194 | width: self.size.width,
195 | height: self.size.height)
196 | break
197 | case .right:
198 | let x = max(self.scrollView!.bounds.size.width, self.scrollView!.contentSize.width);
199 | self.frame = CGRect(x: x + self.affordanceMargin,
200 | y: (self.scrollView!.bounds.midY) - (self.size.height / 2.0),
201 | width: self.size.width,
202 | height: self.size.height);
203 | break
204 | }
205 |
206 | //let isSidePosition = (self.position == .left || self.position == .right)
207 | //magic number is a ture magic (isSidePosition) ? 40.0 : 40.0
208 | let interItemSpace:Double = (40.0) / Double(self.springExpandViews.count)
209 |
210 | // layout affordance.
211 | var index:Double = 0.0
212 | for springExpandView in springExpandViews {
213 | switch self.position {
214 | case .top:
215 | springExpandView.frame = CGRect(x: 0.0, y: -interItemSpace * index, width: Double(self.bounds.width), height: Double(self.borderThickness));
216 | break;
217 | case .bottom:
218 | springExpandView.frame = CGRect(x: 0.0, y: interItemSpace * index, width: Double(self.bounds.width), height: Double(self.borderThickness));
219 | break;
220 | case .left:
221 | springExpandView.frame = CGRect(x: -interItemSpace * index, y: 0.0, width: Double(self.borderThickness), height: Double(self.bounds.height));
222 | break;
223 | case .right:
224 | springExpandView.frame = CGRect(x: interItemSpace * index, y: 0.0, width: Double(self.borderThickness), height: Double(self.bounds.height));
225 | break;
226 | }
227 | index += 1
228 | }
229 | }
230 |
231 | override public func willMove(toSuperview newSuperview: UIView?) {
232 | if self.superview != nil && newSuperview == nil {
233 | if isShowed {
234 | self.showed = false
235 | }
236 | }
237 | }
238 |
239 | // - MARK: KVO
240 | override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
241 | if keyPath == "contentOffset" {
242 | if change == nil {
243 | return
244 | }
245 | guard let v = change?[.newKey] as? NSValue else {
246 | return
247 | }
248 | self.scrollViewDidScroll(contentOffset: v.cgPointValue)
249 | } else if (keyPath == "contentSize") {
250 | self.setNeedsLayout()
251 | } else if (keyPath == "frame") {
252 | self.setNeedsLayout()
253 | }
254 | }
255 |
256 | func scrollViewDidScroll(contentOffset:CGPoint) {
257 |
258 | if self.scrollView == nil {
259 | return
260 | }
261 |
262 | // Do Action prev progress value.
263 | if self.isUserAction && !self.scrollView!.isDragging && !self.scrollView!.isZooming && self.progress > 0.99 {
264 | if self.pullToRefreshHandler != nil {
265 | self.pullToRefreshHandler!(self)
266 | }
267 | }
268 |
269 | let yOffset = contentOffset.y
270 | let xOffset = contentOffset.x
271 |
272 | switch self.position {
273 | case .top:
274 | let threshold = self.threshold != 0 ? self.threshold : self.bounds.height
275 | self.progress = (-yOffset - self.offsetMargin - self.scrollView!.contentInset.top) / threshold
276 | if self.text != nil {
277 | self.label.alpha = (-yOffset - self.scrollView!.contentInset.top) / 40.0
278 | }
279 | self.alpha = (-yOffset - self.scrollView!.contentInset.top) / 40.0
280 | break
281 | case .bottom:
282 | var overBottomOffsetY = yOffset
283 | let threshold = self.threshold != 0 ? self.threshold : self.bounds.height
284 | if self.scrollView!.contentSize.height > self.scrollView!.frame.size.height {
285 | overBottomOffsetY += -self.scrollView!.contentSize.height + self.scrollView!.frame.size.height
286 | self.progress = (overBottomOffsetY - self.offsetMargin - self.scrollView!.contentInset.bottom) / threshold
287 | if self.text != nil {
288 | self.label.alpha = (overBottomOffsetY - self.scrollView!.contentInset.bottom) / threshold
289 | }
290 | }else {
291 | overBottomOffsetY += 20.0 + self.scrollView!.contentInset.bottom
292 | self.progress = (overBottomOffsetY - self.offsetMargin) / threshold
293 | if self.text != nil {
294 | self.label.alpha = (overBottomOffsetY) / threshold
295 | }
296 | }
297 | break
298 | case .left:
299 | let threshold = self.threshold != 0 ? self.threshold : self.bounds.width;
300 | self.progress = (-xOffset - self.offsetMargin) / threshold;
301 | break
302 | case .right:
303 | let rightEdgeOffset = self.scrollView!.contentSize.width - self.scrollView!.bounds.size.width;
304 | let threshold = self.threshold != 0 ? self.threshold : self.bounds.width;
305 | self.progress = (xOffset - rightEdgeOffset - self.offsetMargin) / threshold;
306 | break
307 | }
308 |
309 | self.isUserAction = self.scrollView!.isDragging
310 | }
311 |
312 | }
313 |
314 |
--------------------------------------------------------------------------------
/JZSpringRefreshDemo/JZSpringRefreshDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // JZSpringRefreshDemo
4 | //
5 | // Created by JackyZ on 2017/01/09.
6 | // Copyright © 2017年 Salmonapps. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | var scrollView:UIScrollView? = nil
14 | var textView:UITextView? = nil
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 |
19 | scrollView = UIScrollView(frame: self.view.bounds)
20 | scrollView?.backgroundColor = UIColor.white
21 | scrollView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
22 | scrollView?.alwaysBounceHorizontal = true
23 | scrollView?.alwaysBounceVertical = true
24 | scrollView?.contentSize = view.bounds.size
25 | view.addSubview(scrollView!)
26 |
27 | textView = UITextView(frame: view.bounds, textContainer: nil)
28 | textView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
29 | textView?.textContainerInset = UIEdgeInsetsMake(40, 20, 20, 20)
30 | textView?.font = UIFont(name: "AvenirNext-Regular", size: 16)
31 | textView?.textColor = UIColor.darkGray
32 | textView?.isEditable = false
33 | textView?.text = "AASpringRefresh\n\n AASpringRefresh is Unread.app like pull-to-refresh library that can put to 4 direction (top/bottom/left/right).\n\n License under the MIT License."
34 | scrollView?.addSubview(textView!)
35 |
36 | // top
37 | let top = self.scrollView!.addSpringRefresh(position: .top) { (v:JZSpringRefresh) in
38 | print("top")
39 | }
40 | top.text = "REFRESH"
41 | // bottom
42 | let bottom = self.scrollView!.addSpringRefresh(position: .bottom) { (v:JZSpringRefresh) in
43 | print("bottom")
44 | }
45 | bottom.size = CGSize(width: 120.0, height: 40.0)
46 | bottom.text = "Size property customized"
47 | // left
48 | let left = self.scrollView!.addSpringRefresh(position: .left) { (v:JZSpringRefresh) in
49 | print("left")
50 | }
51 | left.unExpandedColor = UIColor(red: 0.80, green: 0.93, blue: 0.93, alpha: 1.0)
52 | left.expandedColor = UIColor(red: 0.50, green: 0.81, blue: 1.0, alpha: 1.0)
53 | left.readyColor = UIColor(red: 0.00, green: 0.42, blue: 1.0, alpha: 1.0)
54 | // right
55 | let right = self.scrollView!.addSpringRefresh(position: .right) { (v:JZSpringRefresh) in
56 | print("right")
57 | }
58 | right.borderThickness = 2.0
59 | }
60 |
61 | override func didReceiveMemoryWarning() {
62 | super.didReceiveMemoryWarning()
63 | // Dispose of any resources that can be recreated.
64 | }
65 |
66 |
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | All around Unread.app like pull to refresh library.
4 |
5 |
6 |
7 | ---
8 |
9 | Swift version of https://github.com/r-plus/AASpringRefresh
10 |
11 | ## Install
12 | ### Carthage
13 | Add `github "newbdez33/JZSpringRefresh"` to your Cartfile.
14 |
15 | ### Manually
16 |
17 | 1. Copy `JZSpringRefresh` directory to your project.
18 |
19 | ## Usage
20 |
21 | import JZSpringRefresh
22 | ...
23 | let top = self.scrollView.addSpringRefresh(position: .top) { (v:JZSpringRefresh) in
24 | print("top")
25 | }
26 | top.text = "REFRESH"
27 |
28 | ### Customization
29 | #### Property
30 | You can customize below properties.
31 |
32 | springRefresh.unExpandedColor = UIColor.gray;
33 | springRefresh.expandedColor = UIColor.black;
34 | springRefresh.readyColor = UIColor.red;
35 | springRefresh.text = "REFRESH"; // available for position Top or Bottom.
36 | springRefresh.borderThickness = 6.0;
37 | springRefresh.affordanceMargin = 10.0; // to adjust space between scrollView edge and affordanceView.
38 | springRefresh.offsetMargin = 30.0; // to adjust threshold of offset.
39 | springRefresh.threshold = 60.0; // default is width or height of size.
40 | springRefresh.size = CGSize(width:60.0, height40.0); // to adjust expanded size and each interval space.
41 | springRefresh.show = false; // dynamic show/hide affordanceView and add/remove KVO observer.
42 |
43 | ## TODO
44 | * Cocoapods support
45 |
46 | ## LICENSE
47 | MIT License
48 |
--------------------------------------------------------------------------------