├── .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 | Demo gif 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 | --------------------------------------------------------------------------------