├── .gitignore ├── JKRefreshControl ├── JKRefreshControl.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── JKRefreshControl │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── JKRefreshControl.swift │ └── ViewController.swift ├── LICENSE ├── README.md └── ScreenShot └── ScreenShot1.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F08C6491DAB75D500417BD4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F08C6481DAB75D500417BD4 /* AppDelegate.swift */; }; 11 | 4F08C64B1DAB75D500417BD4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F08C64A1DAB75D500417BD4 /* ViewController.swift */; }; 12 | 4F08C64E1DAB75D500417BD4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F08C64C1DAB75D500417BD4 /* Main.storyboard */; }; 13 | 4F08C6501DAB75D500417BD4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F08C64F1DAB75D500417BD4 /* Assets.xcassets */; }; 14 | 4F08C6531DAB75D500417BD4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F08C6511DAB75D500417BD4 /* LaunchScreen.storyboard */; }; 15 | 4F08C65B1DAB75EF00417BD4 /* JKRefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F08C65A1DAB75EF00417BD4 /* JKRefreshControl.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 4F08C6451DAB75D500417BD4 /* JKRefreshControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JKRefreshControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 4F08C6481DAB75D500417BD4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 4F08C64A1DAB75D500417BD4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 4F08C64D1DAB75D500417BD4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 4F08C64F1DAB75D500417BD4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 4F08C6521DAB75D500417BD4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 4F08C6541DAB75D500417BD4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 4F08C65A1DAB75EF00417BD4 /* JKRefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JKRefreshControl.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 4F08C6421DAB75D500417BD4 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 4F08C63C1DAB75D500417BD4 = { 41 | isa = PBXGroup; 42 | children = ( 43 | 4F08C6471DAB75D500417BD4 /* JKRefreshControl */, 44 | 4F08C6461DAB75D500417BD4 /* Products */, 45 | ); 46 | indentWidth = 4; 47 | sourceTree = ""; 48 | tabWidth = 4; 49 | }; 50 | 4F08C6461DAB75D500417BD4 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 4F08C6451DAB75D500417BD4 /* JKRefreshControl.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 4F08C6471DAB75D500417BD4 /* JKRefreshControl */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 4F08C6481DAB75D500417BD4 /* AppDelegate.swift */, 62 | 4F08C64A1DAB75D500417BD4 /* ViewController.swift */, 63 | 4F08C65A1DAB75EF00417BD4 /* JKRefreshControl.swift */, 64 | 4F08C64C1DAB75D500417BD4 /* Main.storyboard */, 65 | 4F08C64F1DAB75D500417BD4 /* Assets.xcassets */, 66 | 4F08C6511DAB75D500417BD4 /* LaunchScreen.storyboard */, 67 | 4F08C6541DAB75D500417BD4 /* Info.plist */, 68 | ); 69 | path = JKRefreshControl; 70 | sourceTree = ""; 71 | }; 72 | /* End PBXGroup section */ 73 | 74 | /* Begin PBXNativeTarget section */ 75 | 4F08C6441DAB75D500417BD4 /* JKRefreshControl */ = { 76 | isa = PBXNativeTarget; 77 | buildConfigurationList = 4F08C6571DAB75D500417BD4 /* Build configuration list for PBXNativeTarget "JKRefreshControl" */; 78 | buildPhases = ( 79 | 4F08C6411DAB75D500417BD4 /* Sources */, 80 | 4F08C6421DAB75D500417BD4 /* Frameworks */, 81 | 4F08C6431DAB75D500417BD4 /* Resources */, 82 | ); 83 | buildRules = ( 84 | ); 85 | dependencies = ( 86 | ); 87 | name = JKRefreshControl; 88 | productName = JKRefreshControl; 89 | productReference = 4F08C6451DAB75D500417BD4 /* JKRefreshControl.app */; 90 | productType = "com.apple.product-type.application"; 91 | }; 92 | /* End PBXNativeTarget section */ 93 | 94 | /* Begin PBXProject section */ 95 | 4F08C63D1DAB75D500417BD4 /* Project object */ = { 96 | isa = PBXProject; 97 | attributes = { 98 | LastSwiftUpdateCheck = 0800; 99 | LastUpgradeCheck = 0800; 100 | ORGANIZATIONNAME = EnjoySR; 101 | TargetAttributes = { 102 | 4F08C6441DAB75D500417BD4 = { 103 | CreatedOnToolsVersion = 8.0; 104 | DevelopmentTeam = 8UF26TTV2Y; 105 | ProvisioningStyle = Automatic; 106 | }; 107 | }; 108 | }; 109 | buildConfigurationList = 4F08C6401DAB75D500417BD4 /* Build configuration list for PBXProject "JKRefreshControl" */; 110 | compatibilityVersion = "Xcode 3.2"; 111 | developmentRegion = English; 112 | hasScannedForEncodings = 0; 113 | knownRegions = ( 114 | en, 115 | Base, 116 | ); 117 | mainGroup = 4F08C63C1DAB75D500417BD4; 118 | productRefGroup = 4F08C6461DAB75D500417BD4 /* Products */; 119 | projectDirPath = ""; 120 | projectRoot = ""; 121 | targets = ( 122 | 4F08C6441DAB75D500417BD4 /* JKRefreshControl */, 123 | ); 124 | }; 125 | /* End PBXProject section */ 126 | 127 | /* Begin PBXResourcesBuildPhase section */ 128 | 4F08C6431DAB75D500417BD4 /* Resources */ = { 129 | isa = PBXResourcesBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | 4F08C6531DAB75D500417BD4 /* LaunchScreen.storyboard in Resources */, 133 | 4F08C6501DAB75D500417BD4 /* Assets.xcassets in Resources */, 134 | 4F08C64E1DAB75D500417BD4 /* Main.storyboard in Resources */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXResourcesBuildPhase section */ 139 | 140 | /* Begin PBXSourcesBuildPhase section */ 141 | 4F08C6411DAB75D500417BD4 /* Sources */ = { 142 | isa = PBXSourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | 4F08C65B1DAB75EF00417BD4 /* JKRefreshControl.swift in Sources */, 146 | 4F08C64B1DAB75D500417BD4 /* ViewController.swift in Sources */, 147 | 4F08C6491DAB75D500417BD4 /* AppDelegate.swift in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin PBXVariantGroup section */ 154 | 4F08C64C1DAB75D500417BD4 /* Main.storyboard */ = { 155 | isa = PBXVariantGroup; 156 | children = ( 157 | 4F08C64D1DAB75D500417BD4 /* Base */, 158 | ); 159 | name = Main.storyboard; 160 | sourceTree = ""; 161 | }; 162 | 4F08C6511DAB75D500417BD4 /* LaunchScreen.storyboard */ = { 163 | isa = PBXVariantGroup; 164 | children = ( 165 | 4F08C6521DAB75D500417BD4 /* Base */, 166 | ); 167 | name = LaunchScreen.storyboard; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXVariantGroup section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | 4F08C6551DAB75D500417BD4 /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | ALWAYS_SEARCH_USER_PATHS = NO; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 179 | CLANG_CXX_LIBRARY = "libc++"; 180 | CLANG_ENABLE_MODULES = YES; 181 | CLANG_ENABLE_OBJC_ARC = YES; 182 | CLANG_WARN_BOOL_CONVERSION = YES; 183 | CLANG_WARN_CONSTANT_CONVERSION = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INFINITE_RECURSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 192 | CLANG_WARN_UNREACHABLE_CODE = YES; 193 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 194 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 195 | COPY_PHASE_STRIP = NO; 196 | DEBUG_INFORMATION_FORMAT = dwarf; 197 | ENABLE_STRICT_OBJC_MSGSEND = YES; 198 | ENABLE_TESTABILITY = YES; 199 | GCC_C_LANGUAGE_STANDARD = gnu99; 200 | GCC_DYNAMIC_NO_PIC = NO; 201 | GCC_NO_COMMON_BLOCKS = YES; 202 | GCC_OPTIMIZATION_LEVEL = 0; 203 | GCC_PREPROCESSOR_DEFINITIONS = ( 204 | "DEBUG=1", 205 | "$(inherited)", 206 | ); 207 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 208 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 209 | GCC_WARN_UNDECLARED_SELECTOR = YES; 210 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 211 | GCC_WARN_UNUSED_FUNCTION = YES; 212 | GCC_WARN_UNUSED_VARIABLE = YES; 213 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 214 | MTL_ENABLE_DEBUG_INFO = YES; 215 | ONLY_ACTIVE_ARCH = YES; 216 | SDKROOT = iphoneos; 217 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 218 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 219 | }; 220 | name = Debug; 221 | }; 222 | 4F08C6561DAB75D500417BD4 /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_ANALYZER_NONNULL = YES; 227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 228 | CLANG_CXX_LIBRARY = "libc++"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_WARN_BOOL_CONVERSION = YES; 232 | CLANG_WARN_CONSTANT_CONVERSION = YES; 233 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 234 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 235 | CLANG_WARN_EMPTY_BODY = YES; 236 | CLANG_WARN_ENUM_CONVERSION = YES; 237 | CLANG_WARN_INFINITE_RECURSION = YES; 238 | CLANG_WARN_INT_CONVERSION = YES; 239 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 240 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 241 | CLANG_WARN_UNREACHABLE_CODE = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 244 | COPY_PHASE_STRIP = NO; 245 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 246 | ENABLE_NS_ASSERTIONS = NO; 247 | ENABLE_STRICT_OBJC_MSGSEND = YES; 248 | GCC_C_LANGUAGE_STANDARD = gnu99; 249 | GCC_NO_COMMON_BLOCKS = YES; 250 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 251 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 252 | GCC_WARN_UNDECLARED_SELECTOR = YES; 253 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 254 | GCC_WARN_UNUSED_FUNCTION = YES; 255 | GCC_WARN_UNUSED_VARIABLE = YES; 256 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 257 | MTL_ENABLE_DEBUG_INFO = NO; 258 | SDKROOT = iphoneos; 259 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 260 | VALIDATE_PRODUCT = YES; 261 | }; 262 | name = Release; 263 | }; 264 | 4F08C6581DAB75D500417BD4 /* Debug */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 268 | DEVELOPMENT_TEAM = 8UF26TTV2Y; 269 | INFOPLIST_FILE = JKRefreshControl/Info.plist; 270 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 271 | PRODUCT_BUNDLE_IDENTIFIER = EnjoySR.JKRefreshControl; 272 | PRODUCT_NAME = "$(TARGET_NAME)"; 273 | SWIFT_VERSION = 3.0; 274 | }; 275 | name = Debug; 276 | }; 277 | 4F08C6591DAB75D500417BD4 /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 281 | DEVELOPMENT_TEAM = 8UF26TTV2Y; 282 | INFOPLIST_FILE = JKRefreshControl/Info.plist; 283 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 284 | PRODUCT_BUNDLE_IDENTIFIER = EnjoySR.JKRefreshControl; 285 | PRODUCT_NAME = "$(TARGET_NAME)"; 286 | SWIFT_VERSION = 3.0; 287 | }; 288 | name = Release; 289 | }; 290 | /* End XCBuildConfiguration section */ 291 | 292 | /* Begin XCConfigurationList section */ 293 | 4F08C6401DAB75D500417BD4 /* Build configuration list for PBXProject "JKRefreshControl" */ = { 294 | isa = XCConfigurationList; 295 | buildConfigurations = ( 296 | 4F08C6551DAB75D500417BD4 /* Debug */, 297 | 4F08C6561DAB75D500417BD4 /* Release */, 298 | ); 299 | defaultConfigurationIsVisible = 0; 300 | defaultConfigurationName = Release; 301 | }; 302 | 4F08C6571DAB75D500417BD4 /* Build configuration list for PBXNativeTarget "JKRefreshControl" */ = { 303 | isa = XCConfigurationList; 304 | buildConfigurations = ( 305 | 4F08C6581DAB75D500417BD4 /* Debug */, 306 | 4F08C6591DAB75D500417BD4 /* Release */, 307 | ); 308 | defaultConfigurationIsVisible = 0; 309 | }; 310 | /* End XCConfigurationList section */ 311 | }; 312 | rootObject = 4F08C63D1DAB75D500417BD4 /* Project object */; 313 | } 314 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JKRefreshControl 4 | // 5 | // Created by EnjoySR on 2016/10/10. 6 | // Copyright © 2016年 EnjoySR. 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 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/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 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 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 | 38 | 39 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/JKRefreshControl.swift: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2016 EnjoySR (https://github.com/EnjoySR/JKRefreshControl) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import UIKit 24 | 25 | 26 | /// 刷新控件的状态 27 | /// 28 | /// - normal: 默认 29 | /// - pulling: 松开就可刷新 30 | /// - refreshing: 刷新中 31 | enum JKRefreshState: Int { 32 | case normal = 0, pulling, refreshing 33 | } 34 | 35 | private let RefreshControlWH: CGFloat = 45 36 | /// 处于刷新中状态时,顶部多余的刷新高度 37 | private let RefreshingStayHeight: CGFloat = 70 38 | /// 控件从刷新状态重置为默认状态的时间 39 | private let RefreshControlHideDuration: TimeInterval = 0.5 40 | /// 主题颜色 41 | private let ThemeColor = UIColor(red: 59/255, green: 84/255, blue: 106/255, alpha: 1) 42 | /// 线宽 43 | private let LineWidth: CGFloat = 5 44 | /// 顶部矩形高度 45 | private let LineHeight: CGFloat = 16 46 | /// 内圆半径 47 | private let InnerRadius: CGFloat = 8 48 | /// 绘制的中心点 49 | private let DrawCenter = CGPoint(x: RefreshControlWH * 0.5, y: RefreshControlWH * 0.5) 50 | 51 | class JKRefreshControl: UIControl { 52 | 53 | 54 | // MARK: - 一些属性 55 | 56 | // 是否正在执行刷新中的动画,防止用户来回拖动 scrollView 造成重复添加动画 57 | fileprivate var isRefreshingAnim: Bool = false 58 | // 是否已经开始执行刷新,防止用户在未刷新完成的情况下重复触发 59 | fileprivate var isBeginRefreshing: Bool = false 60 | 61 | 62 | /// 刷新状态 63 | fileprivate var refreshState: JKRefreshState = .normal { 64 | didSet { 65 | print(refreshState) 66 | 67 | switch refreshState { 68 | case .refreshing: 69 | 70 | // 调整顶部距离 71 | var inset = self.superView.contentInset 72 | inset.top = inset.top + RefreshingStayHeight 73 | DispatchQueue.main.async { 74 | UIView.animate(withDuration: RefreshControlHideDuration, animations: { 75 | self.superView.contentInset = inset 76 | self.superView.setContentOffset(CGPoint(x: 0, y: -inset.top), animated: false) 77 | }, completion: { (_) in 78 | self.sendActions(for: .valueChanged) 79 | }) 80 | } 81 | case .normal: 82 | // 移除两个layer的路径 83 | bottomLayer.path = nil 84 | topLayer.path = nil 85 | // 为默认状态时,重置属性 86 | bottomLayer.removeAllAnimations() 87 | topLayer.strokeEnd = 1 88 | bottomLayer.lineWidth = LineWidth 89 | isRefreshingAnim = false 90 | // 重置是否开始刷新的状态 91 | isBeginRefreshing = false 92 | 93 | default: 94 | break 95 | } 96 | } 97 | } 98 | /// 父控件 99 | private var superView: UIScrollView! 100 | /// 默认的centerY 101 | lazy var defaultCenterY: CGFloat = { 102 | return -self.frame.height * 0.5 - 12.5 103 | }() 104 | /// 拖动距离计算出来的填充比 105 | var contentOffsetScale: CGFloat = 0 { 106 | didSet { 107 | // 当前比例值大于 1 的时候,就设置为 1 108 | if contentOffsetScale > 1 { 109 | contentOffsetScale = 1 110 | } 111 | // 当比例值小于 0 的时候,就设置为 0 112 | if contentOffsetScale <= 0 { 113 | contentOffsetScale = 0 114 | } 115 | } 116 | } 117 | 118 | // MARK: - 初始化 119 | override init(frame: CGRect) { 120 | super.init(frame: frame) 121 | 122 | setupUI() 123 | } 124 | 125 | required init?(coder aDecoder: NSCoder) { 126 | fatalError("init(coder:) has not been implemented") 127 | } 128 | 129 | private func setupUI(){ 130 | 131 | frame.size = CGSize(width: RefreshControlWH, height: RefreshControlWH) 132 | backgroundColor = UIColor.clear 133 | 134 | // 添加三个layer 135 | layer.addSublayer(bgGrayLayer) 136 | layer.addSublayer(bottomLayer) 137 | layer.addSublayer(topLayer) 138 | 139 | } 140 | 141 | /// 设置控件的初始位置 142 | /// 143 | /// - parameter superViewFrame: 父控件的位置 144 | private func setLocation(superViewFrame: CGRect) { 145 | // 后面的减 12.5 是为了确定其 y 值与 官方 app 的 y 值一样 146 | self.center = CGPoint(x: superViewFrame.width * 0.5, y: -self.frame.height * 0.5 - 12.5) 147 | } 148 | 149 | override func willMove(toSuperview newSuperview: UIView?) { 150 | super.willMove(toSuperview: newSuperview) 151 | 152 | if let superView = newSuperview as? UIScrollView { 153 | self.superView = superView 154 | // 监听superView的frame变化 155 | superView.addObserver(self, forKeyPath: "frame", options: NSKeyValueObservingOptions.new, context: nil) 156 | // 监听superView的滚动 157 | superView.addObserver(self, forKeyPath: "contentOffset", options: NSKeyValueObservingOptions.new, context: nil) 158 | } 159 | } 160 | 161 | // MARK: - 外部接口 162 | 163 | 164 | /// 开始刷新 165 | func beginRefreshing() { 166 | 167 | if isBeginRefreshing { 168 | return 169 | } 170 | 171 | isBeginRefreshing = true 172 | let contentInsetY = superView.contentInset 173 | UIView.animate(withDuration: 0.25, animations: { 174 | self.superView.setContentOffset(CGPoint(x: 0, y: -contentInsetY.top - RefreshingStayHeight), animated: false) 175 | }) { (_) in 176 | self.refreshState = .refreshing 177 | self.drawInLayer() 178 | } 179 | } 180 | 181 | /// 结束刷新 182 | func endRefreshing() { 183 | 184 | // 执行转圈的layer的线宽的动画 185 | let animation = CABasicAnimation(keyPath: "lineWidth") 186 | animation.toValue = 0 187 | animation.duration = 0.5 188 | // 设置最终线宽为 0,保证动画执行完毕之后不再显示 189 | bottomLayer.lineWidth = 0 190 | bottomLayer.add(animation, forKey: nil) 191 | 192 | // 重置 contentInset 193 | var inset = self.superView.contentInset 194 | inset.top = inset.top - RefreshingStayHeight 195 | UIView.animate(withDuration: RefreshControlHideDuration, animations: { 196 | self.superView.contentInset = inset 197 | self.superView.setContentOffset(CGPoint(x: 0, y: -inset.top), animated: false) 198 | }, completion: { (_) in 199 | self.refreshState = .normal 200 | }) 201 | } 202 | 203 | // MARK: - KVO 监听 scrollView 滚动 204 | 205 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 206 | if keyPath == "contentOffset" { 207 | self.dealContentOffsetYChanged() 208 | }else if keyPath == "frame" { 209 | let value = (change![NSKeyValueChangeKey.newKey] as! NSValue).cgRectValue 210 | self.setLocation(superViewFrame: value) 211 | } 212 | } 213 | 214 | 215 | /// 处理contentOffsetY改变 216 | // 1. 改变控件的Y值 217 | // 2. 改变刷新控件的状态 218 | private func dealContentOffsetYChanged() { 219 | // 取出偏移的y值 220 | let contentOffsetY = superView.contentOffset.y; 221 | 222 | print("contentOffsetY = \(contentOffsetY)") 223 | 224 | // 1. 设置 控件的 y 值 225 | // 通过偏移量与顶部间距计算数当前控件的中心点 226 | let result = (contentOffsetY + superView.contentInset.top) / 2 227 | // 判断计算出来的值是否比默认的Y值还要小,如果小,就设置该Y值 228 | if result < defaultCenterY { 229 | self.center = CGPoint(x: self.center.x, y: result) 230 | }else{ 231 | // 否则继续设置为默认Y值 232 | self.center = CGPoint(x: self.center.x, y: defaultCenterY) 233 | } 234 | 235 | // 2. 更改控件的状态 236 | // 如果正在被拖动 237 | if superView.isDragging { 238 | // 如果空白中心点小于控件的默认中心y值,并且当前状态是默认状态,就进行 `松手就刷新的状态` 239 | if result < defaultCenterY && refreshState == .normal { 240 | refreshState = .pulling 241 | }else if result >= defaultCenterY && refreshState == .pulling { 242 | // 如果空白中心点大于等于控件的默认中心y值,并且当前状态是默认状态,就进入 `默认状态` 243 | refreshState = .normal 244 | } 245 | }else { 246 | // 用户已松手,判断当前状态如果是 `pulling` 状态就进行刷新状态 247 | if refreshState == .pulling { 248 | refreshState = .refreshing 249 | } 250 | } 251 | 252 | // 3. 计算 scale 253 | // 通过拖动的距离计算.公式为:比例 = 拖动的距离 / 控件的高度 254 | let scale = -(superView.contentOffset.y + superView.contentInset.top) / RefreshingStayHeight 255 | self.contentOffsetScale = scale 256 | self.drawInLayer() 257 | } 258 | 259 | deinit { 260 | superView.removeObserver(self, forKeyPath: "contentOffset") 261 | superView.removeObserver(self, forKeyPath: "frame") 262 | } 263 | 264 | // MARK: - 懒加载layer 265 | 266 | // 背景灰色的layer,显示 `J` 267 | fileprivate lazy var bgGrayLayer: CAShapeLayer = { 268 | let layer = CAShapeLayer() 269 | let bgColor = UIColor(red: 222/255, green: 226/255, blue: 229/255, alpha: 1) 270 | layer.fillColor = bgColor.cgColor 271 | layer.strokeColor = bgColor.cgColor 272 | return layer 273 | }() 274 | 275 | 276 | // 底部layer,显示 `J` 的下半部分 277 | fileprivate lazy var bottomLayer: CAShapeLayer = { 278 | let layer = CAShapeLayer() 279 | layer.fillColor = UIColor.clear.cgColor 280 | layer.strokeColor = ThemeColor.cgColor 281 | // 设置线宽 282 | layer.lineWidth = LineWidth 283 | // 设置frame,用于转圈 284 | layer.frame = self.bounds 285 | return layer 286 | }() 287 | 288 | // 顶部layer,显示 `J` 的上半部分 289 | fileprivate lazy var topLayer: CAShapeLayer = { 290 | let layer = CAShapeLayer() 291 | layer.strokeColor = ThemeColor.cgColor 292 | layer.lineWidth = LineWidth 293 | return layer 294 | }() 295 | } 296 | 297 | // MARK: - 更新界面 298 | extension JKRefreshControl { 299 | 300 | /// 绘制 layer 中的内容 301 | fileprivate func drawInLayer() { 302 | 303 | // 开始角度 304 | let startAngle = CGFloat(M_PI) / 2 305 | // 结束角度 306 | let endAngle: CGFloat = 0 307 | 308 | if refreshState == .refreshing { 309 | // 判断如果正在刷新的话,就不需要再次执行动画 310 | if isRefreshingAnim { 311 | return 312 | } 313 | // 调整执行动画属性为true 314 | isRefreshingAnim = true 315 | // 清空背景灰色的layer 316 | bgGrayLayer.path = nil 317 | 318 | // 底部半圆到整圆 319 | let bottomPath = UIBezierPath(arcCenter: DrawCenter, radius: InnerRadius + LineWidth * 0.5, startAngle: 0, endAngle: CGFloat(M_PI) * 2 - 0.1, clockwise: true) 320 | bottomLayer.path = bottomPath.cgPath 321 | 322 | // 执行动画 323 | let bottomAnim = CABasicAnimation(keyPath: "strokeEnd") 324 | bottomAnim.fromValue = NSNumber(value: 0.25) 325 | bottomAnim.toValue = NSNumber(value: 1.0) 326 | bottomAnim.duration = 0.15 327 | bottomLayer.add(bottomAnim, forKey: nil) 328 | 329 | // 顶部Path 330 | let topPath = UIBezierPath() 331 | topPath.lineCapStyle = .square 332 | topPath.move(to: CGPoint(x: DrawCenter.x + InnerRadius + LineWidth * 0.5, y: DrawCenter.y)) 333 | topPath.addLine(to: CGPoint(x: DrawCenter.x + InnerRadius + LineWidth * 0.5, y: DrawCenter.y - (contentOffsetScale - 0.5) * 2 * LineHeight)) 334 | topLayer.path = topPath.cgPath 335 | 336 | // 竖线变短动画 337 | let topAnim = CABasicAnimation(keyPath: "strokeEnd") 338 | topAnim.fromValue = NSNumber(value: 1) 339 | topAnim.toValue = NSNumber(value: 0) 340 | topAnim.duration = 0.15 341 | topLayer.strokeEnd = 0; 342 | topLayer.add(topAnim, forKey: nil) 343 | 344 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15, execute: { 345 | // 0.15 秒之后进行转圈 346 | self.runaroundAnim() 347 | }) 348 | return 349 | } 350 | 351 | // 绘制默认状态与松手就刷新状态的代码 352 | // 绘制灰色背景 layer 内容 353 | // 画 1/4 圆 354 | let path = UIBezierPath(arcCenter: DrawCenter, radius: InnerRadius, startAngle: startAngle, endAngle: endAngle, clockwise: false) 355 | 356 | // 添加左边竖线 357 | path.addLine(to: CGPoint(x: path.currentPoint.x, y: DrawCenter.y - LineHeight)) 358 | // 添加顶部横线 359 | path.addLine(to: CGPoint(x: path.currentPoint.x + LineWidth, y: path.currentPoint.y)) 360 | // 添加右边竖线 361 | path.addLine(to: CGPoint(x: path.currentPoint.x, y: path.currentPoint.y + LineHeight)) 362 | // 添加外圆 363 | path.addArc(withCenter: DrawCenter, radius: InnerRadius + LineWidth, startAngle: endAngle, endAngle: startAngle - 0.05, clockwise: true) 364 | path.close() 365 | // 设置路径 366 | bgGrayLayer.path = path.cgPath 367 | 368 | // 如果小于0.016.在画度半圆的时候会反方向画 369 | if contentOffsetScale < 0.016 { 370 | bgGrayLayer.path = nil 371 | bottomLayer.path = nil 372 | topLayer.path = nil 373 | return 374 | } 375 | 376 | /// 提供内部方法,专门用于获取绘制底部的圆的 path 377 | func pathForBottomCircle(contentOffsetScale: CGFloat) -> UIBezierPath { 378 | // 记录传入的比例 379 | var scale = contentOffsetScale 380 | // 如果比例大于 0.5,那么设置为 0.5 381 | if scale > 0.5 { 382 | scale = 0.5 383 | } 384 | // 计算出开始角度与结束角度 385 | let targetStartAngle = startAngle 386 | let targetEndAngle = startAngle - startAngle * scale * 2 387 | // 初始化 path 并返回 388 | let drawPath = UIBezierPath(arcCenter: DrawCenter, radius: InnerRadius + LineWidth * 0.5, startAngle: targetStartAngle, endAngle: targetEndAngle, clockwise: false) 389 | 390 | return drawPath 391 | } 392 | 393 | bottomLayer.path = pathForBottomCircle(contentOffsetScale: contentOffsetScale).cgPath 394 | // 判断如果拖动比例小于0.5,只画半圆 395 | if contentOffsetScale <= 0.5 { 396 | topLayer.path = nil 397 | }else { 398 | // 画顶部竖线 399 | let topPath = UIBezierPath() 400 | topPath.lineCapStyle = .square 401 | topPath.move(to: CGPoint(x: DrawCenter.x + InnerRadius + LineWidth * 0.5, y: DrawCenter.y)) 402 | topPath.addLine(to: CGPoint(x: DrawCenter.x + InnerRadius + LineWidth * 0.5, y: DrawCenter.y - (contentOffsetScale - 0.5) * 2 * LineHeight)) 403 | topLayer.path = topPath.cgPath 404 | } 405 | } 406 | 407 | 408 | /// 转圈动画 409 | private func runaroundAnim() { 410 | // 执行转圈动画 411 | let bottomPath = UIBezierPath(arcCenter: DrawCenter, radius: InnerRadius + LineWidth * 0.5, startAngle: 0, endAngle: CGFloat(M_PI) * 2 - 0.1, clockwise: true) 412 | self.bottomLayer.path = bottomPath.cgPath 413 | 414 | // 围绕 z 轴转圈 415 | let bottomAnim = CABasicAnimation(keyPath: "transform.rotation.z") 416 | bottomAnim.fromValue = NSNumber(value: 0) 417 | bottomAnim.toValue = NSNumber(value: 2 * M_PI) 418 | bottomAnim.duration = 0.5 419 | bottomAnim.repeatCount = MAXFLOAT 420 | self.bottomLayer.add(bottomAnim, forKey: "runaroundAnim") 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /JKRefreshControl/JKRefreshControl/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JKRefreshControl 4 | // 5 | // Created by EnjoySR on 2016/10/10. 6 | // Copyright © 2016年 EnjoySR. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UITableViewController { 12 | 13 | lazy var datas: [UIColor] = [UIColor]() 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | self.setupUI() 19 | self.initData() 20 | } 21 | 22 | private func setupUI() { 23 | self.tableView.separatorStyle = .none 24 | self.tableView.addSubview(refresh) 25 | 26 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Clear", style: UIBarButtonItemStyle.plain, target: self, action: #selector(clean)) 27 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Refresh", style: UIBarButtonItemStyle.plain, target: refresh, action: #selector(JKRefreshControl.beginRefreshing)) 28 | } 29 | 30 | 31 | @objc private func clean() { 32 | self.datas.removeAll() 33 | self.tableView.reloadData() 34 | } 35 | 36 | /// 模拟添加数据 37 | private func initData() { 38 | for _ in 0..<5 { 39 | let color = UIColor(red: CGFloat(arc4random() % 256) / 255, green: CGFloat(arc4random() % 256) / 255, blue: CGFloat(arc4random() % 256) / 255, alpha: 1) 40 | datas.append(color) 41 | } 42 | } 43 | 44 | /// 模拟加载数据 45 | @objc private func loadData() { 46 | 47 | DispatchQueue.global().async { 48 | 49 | for _ in 0..<3 { 50 | let color = UIColor(red: CGFloat(arc4random() % 256) / 255, green: CGFloat(arc4random() % 256) / 255, blue: CGFloat(arc4random() % 256) / 255, alpha: 1) 51 | self.datas.insert(color, at: 0) 52 | } 53 | 54 | Thread.sleep(forTimeInterval: 1) 55 | 56 | DispatchQueue.main.async { 57 | self.tableView.reloadData() 58 | self.refresh.endRefreshing() 59 | } 60 | } 61 | } 62 | 63 | // MARK: - 懒加载控件 64 | lazy var refresh: JKRefreshControl = { 65 | let refresh = JKRefreshControl() 66 | refresh.addTarget(self, action: #selector(loadData), for: .valueChanged) 67 | return refresh 68 | }() 69 | } 70 | 71 | extension ViewController { 72 | 73 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 74 | return datas.count; 75 | } 76 | 77 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 78 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 79 | cell.backgroundColor = datas[indexPath.row] 80 | cell.textLabel?.text = "\(datas.count - indexPath.row)" 81 | return cell 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 EnjoySR 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JKRefreshControl 2 | 仿<即刻>应用的下拉刷新方式 3 | 4 | 具体实现流程见Blog:[仿<即刻>下拉刷新效果实现](https://enjoysr.github.io/2016/10/09/%E4%BB%BF-%E5%8D%B3%E5%88%BB-%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/) 5 | 6 | 开发环境:Xcode 8.0 (Swift 3.0) 7 | 8 | ## 效果图 9 | ![](https://raw.githubusercontent.com/EnjoySR/JKRefreshControl/master/ScreenShot/ScreenShot1.gif) 10 | 11 | ## 使用方式 12 | 与系统的 `UIRefreshControl` 一样,支持 UIScrollView 及其子类 13 | 14 | - 添加控件 15 | 16 | ```swift 17 | let refresh = JKRefreshControl() 18 | tableView.addSubview(refresh) 19 | ``` 20 | - 添加刷新事件监听 21 | 22 | ```swift 23 | refresh.addTarget(self, action: #selector(loadData), for: .valueChanged) 24 | ``` 25 | 26 | - 结束刷新 27 | 28 | ```swift 29 | refresh.endRefreshing() 30 | ``` 31 | 32 | - 主动进入刷新状态 33 | 34 | ```swift 35 | refresh.beginRefreshing() 36 | ``` 37 | 38 | ## 其他 39 | 在控件进入到刷新状态,回到顶部的时候,刷新控件可能会有抖动,原因以及解决方法:[你的下拉刷新是否“抖”了一下](http://www.cnblogs.com/max5945/p/4283225.html)(在 iPhone 7 模拟器上有时候不好使,但是在真机上没有问题) 40 | 41 | ## License 42 | MIT 43 | 44 | 45 | -------------------------------------------------------------------------------- /ScreenShot/ScreenShot1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EnjoySR/JKRefreshControl/c2c09677083ca081aaaa097acbf6bac7039f98b7/ScreenShot/ScreenShot1.gif --------------------------------------------------------------------------------