├── PULLTOREFRESH.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── PULLTOREFRESH ├── AppDelegate.h ├── AppDelegate.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Info.plist ├── SSAPullToRefresh.png ├── SSAPullToRefresh │ ├── Ressources.bundle │ │ └── angle-mask@2x.png │ ├── SSACircleView.h │ ├── SSACircleView.m │ ├── SSAPullToRefreshAnimator.h │ ├── SSAPullToRefreshAnimator.m │ ├── SSARefreshControl.h │ └── SSARefreshControl.m ├── ViewController.h ├── ViewController.m └── main.m ├── PULLTOREFRESHTests ├── Info.plist └── PULLTOREFRESHTests.m └── README.md /PULLTOREFRESH.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DC20D8031A1A65EC0073344E /* SSAPullToRefresh.png in Resources */ = {isa = PBXBuildFile; fileRef = DC20D8021A1A65EC0073344E /* SSAPullToRefresh.png */; }; 11 | DC52D32B1A1BDA1500E8095D /* Ressources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DC52D3241A1BDA1500E8095D /* Ressources.bundle */; }; 12 | DC52D32C1A1BDA1500E8095D /* SSACircleView.m in Sources */ = {isa = PBXBuildFile; fileRef = DC52D3261A1BDA1500E8095D /* SSACircleView.m */; }; 13 | DC52D32D1A1BDA1500E8095D /* SSAPullToRefreshAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = DC52D3281A1BDA1500E8095D /* SSAPullToRefreshAnimator.m */; }; 14 | DC52D32E1A1BDA1500E8095D /* SSARefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = DC52D32A1A1BDA1500E8095D /* SSARefreshControl.m */; }; 15 | DCC62F251A193AC80088A81D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC62F241A193AC80088A81D /* main.m */; }; 16 | DCC62F281A193AC80088A81D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC62F271A193AC80088A81D /* AppDelegate.m */; }; 17 | DCC62F2B1A193AC80088A81D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC62F2A1A193AC80088A81D /* ViewController.m */; }; 18 | DCC62F301A193AC80088A81D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCC62F2F1A193AC80088A81D /* Images.xcassets */; }; 19 | DCC62F3F1A193AC80088A81D /* PULLTOREFRESHTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC62F3E1A193AC80088A81D /* PULLTOREFRESHTests.m */; }; 20 | DCC62F5C1A193F850088A81D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC62F5B1A193F850088A81D /* QuartzCore.framework */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | DCC62F391A193AC80088A81D /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = DCC62F171A193AC80088A81D /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = DCC62F1E1A193AC80088A81D; 29 | remoteInfo = PULLTOREFRESH; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | DC20D8021A1A65EC0073344E /* SSAPullToRefresh.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = SSAPullToRefresh.png; sourceTree = ""; }; 35 | DC52D3241A1BDA1500E8095D /* Ressources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Ressources.bundle; path = SSAPullToRefresh/Ressources.bundle; sourceTree = ""; }; 36 | DC52D3251A1BDA1500E8095D /* SSACircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SSACircleView.h; path = SSAPullToRefresh/SSACircleView.h; sourceTree = ""; }; 37 | DC52D3261A1BDA1500E8095D /* SSACircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SSACircleView.m; path = SSAPullToRefresh/SSACircleView.m; sourceTree = ""; }; 38 | DC52D3271A1BDA1500E8095D /* SSAPullToRefreshAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SSAPullToRefreshAnimator.h; path = SSAPullToRefresh/SSAPullToRefreshAnimator.h; sourceTree = ""; }; 39 | DC52D3281A1BDA1500E8095D /* SSAPullToRefreshAnimator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SSAPullToRefreshAnimator.m; path = SSAPullToRefresh/SSAPullToRefreshAnimator.m; sourceTree = ""; }; 40 | DC52D3291A1BDA1500E8095D /* SSARefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SSARefreshControl.h; path = SSAPullToRefresh/SSARefreshControl.h; sourceTree = ""; }; 41 | DC52D32A1A1BDA1500E8095D /* SSARefreshControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SSARefreshControl.m; path = SSAPullToRefresh/SSARefreshControl.m; sourceTree = ""; }; 42 | DCC62F1F1A193AC80088A81D /* PULLTOREFRESH.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PULLTOREFRESH.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | DCC62F231A193AC80088A81D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | DCC62F241A193AC80088A81D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | DCC62F261A193AC80088A81D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | DCC62F271A193AC80088A81D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | DCC62F291A193AC80088A81D /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 48 | DCC62F2A1A193AC80088A81D /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 49 | DCC62F2F1A193AC80088A81D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 50 | DCC62F381A193AC80088A81D /* PULLTOREFRESHTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PULLTOREFRESHTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | DCC62F3D1A193AC80088A81D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | DCC62F3E1A193AC80088A81D /* PULLTOREFRESHTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PULLTOREFRESHTests.m; sourceTree = ""; }; 53 | DCC62F5B1A193F850088A81D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | DCC62F1C1A193AC80088A81D /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | DCC62F5C1A193F850088A81D /* QuartzCore.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | DCC62F351A193AC80088A81D /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | DCC62F161A193AC80088A81D = { 76 | isa = PBXGroup; 77 | children = ( 78 | DCC62F211A193AC80088A81D /* PULLTOREFRESH */, 79 | DCC62F3B1A193AC80088A81D /* PULLTOREFRESHTests */, 80 | DCC62F201A193AC80088A81D /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | DCC62F201A193AC80088A81D /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | DCC62F1F1A193AC80088A81D /* PULLTOREFRESH.app */, 88 | DCC62F381A193AC80088A81D /* PULLTOREFRESHTests.xctest */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | DCC62F211A193AC80088A81D /* PULLTOREFRESH */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | DC20D8021A1A65EC0073344E /* SSAPullToRefresh.png */, 97 | DCC62F571A193B810088A81D /* SSAPullToRefresh */, 98 | DCC62F261A193AC80088A81D /* AppDelegate.h */, 99 | DCC62F271A193AC80088A81D /* AppDelegate.m */, 100 | DCC62F291A193AC80088A81D /* ViewController.h */, 101 | DCC62F2A1A193AC80088A81D /* ViewController.m */, 102 | DCC62F2F1A193AC80088A81D /* Images.xcassets */, 103 | DCC62F221A193AC80088A81D /* Supporting Files */, 104 | ); 105 | path = PULLTOREFRESH; 106 | sourceTree = ""; 107 | }; 108 | DCC62F221A193AC80088A81D /* Supporting Files */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | DCC62F5B1A193F850088A81D /* QuartzCore.framework */, 112 | DCC62F231A193AC80088A81D /* Info.plist */, 113 | DCC62F241A193AC80088A81D /* main.m */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | DCC62F3B1A193AC80088A81D /* PULLTOREFRESHTests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | DCC62F3E1A193AC80088A81D /* PULLTOREFRESHTests.m */, 122 | DCC62F3C1A193AC80088A81D /* Supporting Files */, 123 | ); 124 | path = PULLTOREFRESHTests; 125 | sourceTree = ""; 126 | }; 127 | DCC62F3C1A193AC80088A81D /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | DCC62F3D1A193AC80088A81D /* Info.plist */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | DCC62F571A193B810088A81D /* SSAPullToRefresh */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | DC52D3241A1BDA1500E8095D /* Ressources.bundle */, 139 | DC52D3251A1BDA1500E8095D /* SSACircleView.h */, 140 | DC52D3261A1BDA1500E8095D /* SSACircleView.m */, 141 | DC52D3271A1BDA1500E8095D /* SSAPullToRefreshAnimator.h */, 142 | DC52D3281A1BDA1500E8095D /* SSAPullToRefreshAnimator.m */, 143 | DC52D3291A1BDA1500E8095D /* SSARefreshControl.h */, 144 | DC52D32A1A1BDA1500E8095D /* SSARefreshControl.m */, 145 | ); 146 | name = SSAPullToRefresh; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | DCC62F1E1A193AC80088A81D /* PULLTOREFRESH */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = DCC62F421A193AC80088A81D /* Build configuration list for PBXNativeTarget "PULLTOREFRESH" */; 155 | buildPhases = ( 156 | DCC62F1B1A193AC80088A81D /* Sources */, 157 | DCC62F1C1A193AC80088A81D /* Frameworks */, 158 | DCC62F1D1A193AC80088A81D /* Resources */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = PULLTOREFRESH; 165 | productName = PULLTOREFRESH; 166 | productReference = DCC62F1F1A193AC80088A81D /* PULLTOREFRESH.app */; 167 | productType = "com.apple.product-type.application"; 168 | }; 169 | DCC62F371A193AC80088A81D /* PULLTOREFRESHTests */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = DCC62F451A193AC80088A81D /* Build configuration list for PBXNativeTarget "PULLTOREFRESHTests" */; 172 | buildPhases = ( 173 | DCC62F341A193AC80088A81D /* Sources */, 174 | DCC62F351A193AC80088A81D /* Frameworks */, 175 | DCC62F361A193AC80088A81D /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | DCC62F3A1A193AC80088A81D /* PBXTargetDependency */, 181 | ); 182 | name = PULLTOREFRESHTests; 183 | productName = PULLTOREFRESHTests; 184 | productReference = DCC62F381A193AC80088A81D /* PULLTOREFRESHTests.xctest */; 185 | productType = "com.apple.product-type.bundle.unit-test"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | DCC62F171A193AC80088A81D /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastUpgradeCheck = 0610; 194 | ORGANIZATIONNAME = SebastianA; 195 | TargetAttributes = { 196 | DCC62F1E1A193AC80088A81D = { 197 | CreatedOnToolsVersion = 6.1; 198 | }; 199 | DCC62F371A193AC80088A81D = { 200 | CreatedOnToolsVersion = 6.1; 201 | TestTargetID = DCC62F1E1A193AC80088A81D; 202 | }; 203 | }; 204 | }; 205 | buildConfigurationList = DCC62F1A1A193AC80088A81D /* Build configuration list for PBXProject "PULLTOREFRESH" */; 206 | compatibilityVersion = "Xcode 3.2"; 207 | developmentRegion = English; 208 | hasScannedForEncodings = 0; 209 | knownRegions = ( 210 | en, 211 | Base, 212 | ); 213 | mainGroup = DCC62F161A193AC80088A81D; 214 | productRefGroup = DCC62F201A193AC80088A81D /* Products */; 215 | projectDirPath = ""; 216 | projectRoot = ""; 217 | targets = ( 218 | DCC62F1E1A193AC80088A81D /* PULLTOREFRESH */, 219 | DCC62F371A193AC80088A81D /* PULLTOREFRESHTests */, 220 | ); 221 | }; 222 | /* End PBXProject section */ 223 | 224 | /* Begin PBXResourcesBuildPhase section */ 225 | DCC62F1D1A193AC80088A81D /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | DC20D8031A1A65EC0073344E /* SSAPullToRefresh.png in Resources */, 230 | DC52D32B1A1BDA1500E8095D /* Ressources.bundle in Resources */, 231 | DCC62F301A193AC80088A81D /* Images.xcassets in Resources */, 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | DCC62F361A193AC80088A81D /* Resources */ = { 236 | isa = PBXResourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | DCC62F1B1A193AC80088A81D /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | DC52D32D1A1BDA1500E8095D /* SSAPullToRefreshAnimator.m in Sources */, 250 | DCC62F2B1A193AC80088A81D /* ViewController.m in Sources */, 251 | DCC62F281A193AC80088A81D /* AppDelegate.m in Sources */, 252 | DC52D32C1A1BDA1500E8095D /* SSACircleView.m in Sources */, 253 | DCC62F251A193AC80088A81D /* main.m in Sources */, 254 | DC52D32E1A1BDA1500E8095D /* SSARefreshControl.m in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | DCC62F341A193AC80088A81D /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | DCC62F3F1A193AC80088A81D /* PULLTOREFRESHTests.m in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXSourcesBuildPhase section */ 267 | 268 | /* Begin PBXTargetDependency section */ 269 | DCC62F3A1A193AC80088A81D /* PBXTargetDependency */ = { 270 | isa = PBXTargetDependency; 271 | target = DCC62F1E1A193AC80088A81D /* PULLTOREFRESH */; 272 | targetProxy = DCC62F391A193AC80088A81D /* PBXContainerItemProxy */; 273 | }; 274 | /* End PBXTargetDependency section */ 275 | 276 | /* Begin XCBuildConfiguration section */ 277 | DCC62F401A193AC80088A81D /* Debug */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_EMPTY_BODY = YES; 289 | CLANG_WARN_ENUM_CONVERSION = YES; 290 | CLANG_WARN_INT_CONVERSION = YES; 291 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 292 | CLANG_WARN_UNREACHABLE_CODE = YES; 293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 294 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 295 | COPY_PHASE_STRIP = NO; 296 | ENABLE_STRICT_OBJC_MSGSEND = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_OPTIMIZATION_LEVEL = 0; 300 | GCC_PREPROCESSOR_DEFINITIONS = ( 301 | "DEBUG=1", 302 | "$(inherited)", 303 | ); 304 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 312 | MTL_ENABLE_DEBUG_INFO = YES; 313 | ONLY_ACTIVE_ARCH = YES; 314 | SDKROOT = iphoneos; 315 | }; 316 | name = Debug; 317 | }; 318 | DCC62F411A193AC80088A81D /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_CONSTANT_CONVERSION = YES; 328 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 329 | CLANG_WARN_EMPTY_BODY = YES; 330 | CLANG_WARN_ENUM_CONVERSION = YES; 331 | CLANG_WARN_INT_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = YES; 337 | ENABLE_NS_ASSERTIONS = NO; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | GCC_C_LANGUAGE_STANDARD = gnu99; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 347 | MTL_ENABLE_DEBUG_INFO = NO; 348 | SDKROOT = iphoneos; 349 | VALIDATE_PRODUCT = YES; 350 | }; 351 | name = Release; 352 | }; 353 | DCC62F431A193AC80088A81D /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 358 | INFOPLIST_FILE = PULLTOREFRESH/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 360 | PRODUCT_NAME = "$(TARGET_NAME)"; 361 | }; 362 | name = Debug; 363 | }; 364 | DCC62F441A193AC80088A81D /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 368 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 369 | INFOPLIST_FILE = PULLTOREFRESH/Info.plist; 370 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | }; 373 | name = Release; 374 | }; 375 | DCC62F461A193AC80088A81D /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | BUNDLE_LOADER = "$(TEST_HOST)"; 379 | FRAMEWORK_SEARCH_PATHS = ( 380 | "$(SDKROOT)/Developer/Library/Frameworks", 381 | "$(inherited)", 382 | ); 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | INFOPLIST_FILE = PULLTOREFRESHTests/Info.plist; 388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PULLTOREFRESH.app/PULLTOREFRESH"; 391 | }; 392 | name = Debug; 393 | }; 394 | DCC62F471A193AC80088A81D /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | BUNDLE_LOADER = "$(TEST_HOST)"; 398 | FRAMEWORK_SEARCH_PATHS = ( 399 | "$(SDKROOT)/Developer/Library/Frameworks", 400 | "$(inherited)", 401 | ); 402 | INFOPLIST_FILE = PULLTOREFRESHTests/Info.plist; 403 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PULLTOREFRESH.app/PULLTOREFRESH"; 406 | }; 407 | name = Release; 408 | }; 409 | /* End XCBuildConfiguration section */ 410 | 411 | /* Begin XCConfigurationList section */ 412 | DCC62F1A1A193AC80088A81D /* Build configuration list for PBXProject "PULLTOREFRESH" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | DCC62F401A193AC80088A81D /* Debug */, 416 | DCC62F411A193AC80088A81D /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Release; 420 | }; 421 | DCC62F421A193AC80088A81D /* Build configuration list for PBXNativeTarget "PULLTOREFRESH" */ = { 422 | isa = XCConfigurationList; 423 | buildConfigurations = ( 424 | DCC62F431A193AC80088A81D /* Debug */, 425 | DCC62F441A193AC80088A81D /* Release */, 426 | ); 427 | defaultConfigurationIsVisible = 0; 428 | defaultConfigurationName = Release; 429 | }; 430 | DCC62F451A193AC80088A81D /* Build configuration list for PBXNativeTarget "PULLTOREFRESHTests" */ = { 431 | isa = XCConfigurationList; 432 | buildConfigurations = ( 433 | DCC62F461A193AC80088A81D /* Debug */, 434 | DCC62F471A193AC80088A81D /* Release */, 435 | ); 436 | defaultConfigurationIsVisible = 0; 437 | defaultConfigurationName = Release; 438 | }; 439 | /* End XCConfigurationList section */ 440 | }; 441 | rootObject = DCC62F171A193AC80088A81D /* Project object */; 442 | } 443 | -------------------------------------------------------------------------------- /PULLTOREFRESH.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PULLTOREFRESH/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PULLTOREFRESH 4 | // 5 | // Created by Sebastian Andersen on 16/11/14. 6 | // Copyright (c) 2014 SebastianA. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /PULLTOREFRESH/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PULLTOREFRESH 4 | // 5 | // Created by Sebastian Andersen on 16/11/14. 6 | // Copyright (c) 2014 SebastianA. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "ViewController.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | 22 | // Override point for customization after application launch. 23 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 24 | // Override point for customization after application launch. 25 | self.window.backgroundColor = [UIColor whiteColor]; 26 | 27 | self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; 28 | 29 | [self.window makeKeyAndVisible]; 30 | 31 | 32 | return YES; 33 | } 34 | 35 | - (void)applicationWillResignActive:(UIApplication *)application { 36 | // 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. 37 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 38 | } 39 | 40 | - (void)applicationDidEnterBackground:(UIApplication *)application { 41 | // 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. 42 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 43 | } 44 | 45 | - (void)applicationWillEnterForeground:(UIApplication *)application { 46 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 47 | } 48 | 49 | - (void)applicationDidBecomeActive:(UIApplication *)application { 50 | // 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. 51 | } 52 | 53 | - (void)applicationWillTerminate:(UIApplication *)application { 54 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /PULLTOREFRESH/Images.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 | } -------------------------------------------------------------------------------- /PULLTOREFRESH/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "minimum-system-version" : "7.0", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "orientation" : "portrait", 11 | "idiom" : "iphone", 12 | "minimum-system-version" : "7.0", 13 | "subtype" : "retina4", 14 | "scale" : "2x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PULLTOREFRESH/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | SebastianA.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSA111/SSAPullToRefresh/1f8a78a082357bc28a67dd1345ea358927037ecc/PULLTOREFRESH/SSAPullToRefresh.png -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/Ressources.bundle/angle-mask@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSA111/SSAPullToRefresh/1f8a78a082357bc28a67dd1345ea358927037ecc/PULLTOREFRESH/SSAPullToRefresh/Ressources.bundle/angle-mask@2x.png -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/SSACircleView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSACircleView.h 3 | // 4 | // Created by Sebastian Andersen on 11 / 8 - 2014 5 | // Copyright (C) 2014 Sebastian Andersen. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #define kSSARefreshCircleViewHeight 40 11 | 12 | @interface SSACircleView : UIView 13 | 14 | @property (nonatomic, assign) CGFloat heightBeginToRefresh; 15 | 16 | @property (nonatomic, assign) CGFloat offsetY; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/SSACircleView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSACircleView.m 3 | // 4 | // Created by Sebastian Andersen on 11 / 8 - 2014 5 | // Copyright (C) 2014 Sebastian Andersen. All rights reserved. 6 | // 7 | 8 | #import "SSACircleView.h" 9 | 10 | 11 | @implementation SSACircleView 12 | 13 | - (void)setOffsetY:(CGFloat)offsetY { 14 | _offsetY = offsetY; 15 | [self setNeedsDisplay]; 16 | } 17 | 18 | - (id)initWithFrame:(CGRect)frame { 19 | self = [super initWithFrame:frame]; 20 | if (self) { 21 | 22 | self.backgroundColor = [UIColor clearColor]; 23 | 24 | _heightBeginToRefresh = kSSARefreshCircleViewHeight; 25 | _offsetY = 0; 26 | 27 | CALayer *maskLayer = [CALayer layer]; 28 | maskLayer.contents = (id)[[UIImage imageNamed:@"Ressources.bundle/angle-mask@2x.png"] CGImage]; 29 | maskLayer.frame = self.bounds; 30 | self.layer.mask = maskLayer; 31 | 32 | } 33 | return self; 34 | } 35 | 36 | 37 | - (void)drawRect:(CGRect)rect { 38 | 39 | CGContextRef context = UIGraphicsGetCurrentContext(); 40 | 41 | CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor); 42 | CGContextSetLineWidth(context, 2.f); 43 | 44 | CGFloat radius = 12; 45 | 46 | static CGFloat startAngle = 3 * M_PI / 2.0; 47 | CGFloat endAngle = (ABS(_offsetY) / _heightBeginToRefresh) * (M_PI * 19 / 10) + startAngle; 48 | CGContextAddArc(context, CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2, radius, startAngle, endAngle, 0); 49 | 50 | CGContextDrawPath(context, kCGPathStroke); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/SSAPullToRefreshAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Animator.h 3 | // 4 | // 5 | // Created by Sebastian Andersen on 1/31/14. 6 | // Copyright (c) 2014 Sebastian Andersen All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef CGFloat (*ViewEasingFunctionPointerType)(CGFloat); 13 | 14 | @interface SSAPullToRefreshAnimator : NSObject 15 | 16 | + (CABasicAnimation *)repeatRotateAnimation; 17 | 18 | + (CAKeyframeAnimation *)popAnimation; 19 | 20 | +(CAKeyframeAnimation *) animationWithCATransform3DForKeyPath:(NSString *)keyPath 21 | easingFunction:(ViewEasingFunctionPointerType)function 22 | fromMatrix:(CATransform3D)fromMatrix 23 | toMatrix:(CATransform3D)toMatrix; 24 | 25 | @end 26 | 27 | CGFloat SSAElasticEaseIn(CGFloat p); 28 | 29 | 30 | -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/SSAPullToRefreshAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // Animator.m 3 | // 4 | // 5 | // Created by Sebastian Andersen on 1/31/14. 6 | // Copyright (c) 2014 Sebastian Andersen All rights reserved. 7 | // 8 | 9 | #import "SSAPullToRefreshAnimator.h" 10 | 11 | static const NSUInteger KeyframeCount = 60; 12 | 13 | @implementation SSAPullToRefreshAnimator 14 | 15 | + (CABasicAnimation*)repeatRotateAnimation { 16 | 17 | 18 | NSTimeInterval animationDuration = 1; 19 | CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 20 | 21 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 22 | animation.fromValue = 0; 23 | animation.toValue = [NSNumber numberWithFloat:M_PI*2]; 24 | animation.duration = animationDuration; 25 | animation.timingFunction = linearCurve; 26 | animation.removedOnCompletion = NO; 27 | animation.repeatCount = INFINITY; 28 | animation.fillMode = kCAFillModeForwards; 29 | animation.autoreverses = NO; 30 | 31 | return animation; 32 | 33 | } 34 | 35 | + (CAKeyframeAnimation *)popAnimation { 36 | 37 | CATransform3D fromMatrix = CATransform3DMakeScale(0.0, 0.0, 0.0); 38 | CATransform3D toMatrix = CATransform3DMakeScale(1.0f, 1.0f, 1.0f); 39 | CAKeyframeAnimation *animation = [SSAPullToRefreshAnimator animationWithCATransform3DForKeyPath:@"transform" 40 | easingFunction:SSAElasticEaseOut 41 | fromMatrix:fromMatrix 42 | toMatrix:toMatrix]; 43 | animation.duration = 1.0f; 44 | animation.removedOnCompletion = NO; 45 | 46 | return animation; 47 | 48 | } 49 | 50 | +(CAKeyframeAnimation *)animationWithCATransform3DForKeyPath:(NSString *)keyPath 51 | easingFunction:(ViewEasingFunctionPointerType)function 52 | fromMatrix:(CATransform3D)fromMatrix 53 | toMatrix:(CATransform3D)toMatrix { 54 | 55 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:KeyframeCount]; 56 | 57 | CGFloat t = 0.0; 58 | CGFloat dt = 1.0 / (KeyframeCount - 1); 59 | 60 | for(size_t frame = 0; frame < KeyframeCount; ++frame, t += dt) { 61 | 62 | CATransform3D value; 63 | 64 | value.m11 = fromMatrix.m11 + function(t) * (toMatrix.m11 - fromMatrix.m11); 65 | value.m12 = fromMatrix.m12 + function(t) * (toMatrix.m12 - fromMatrix.m12); 66 | value.m13 = fromMatrix.m13 + function(t) * (toMatrix.m13 - fromMatrix.m13); 67 | value.m14 = fromMatrix.m14 + function(t) * (toMatrix.m14 - fromMatrix.m14); 68 | 69 | value.m21 = fromMatrix.m21 + function(t) * (toMatrix.m21 - fromMatrix.m21); 70 | value.m22 = fromMatrix.m22 + function(t) * (toMatrix.m22 - fromMatrix.m22); 71 | value.m23 = fromMatrix.m23 + function(t) * (toMatrix.m23 - fromMatrix.m23); 72 | value.m24 = fromMatrix.m24 + function(t) * (toMatrix.m24 - fromMatrix.m24); 73 | 74 | value.m31 = fromMatrix.m31 + function(t) * (toMatrix.m31 - fromMatrix.m31); 75 | value.m32 = fromMatrix.m32 + function(t) * (toMatrix.m32 - fromMatrix.m32); 76 | value.m33 = fromMatrix.m33 + function(t) * (toMatrix.m33 - fromMatrix.m33); 77 | value.m34 = fromMatrix.m34 + function(t) * (toMatrix.m34 - fromMatrix.m34); 78 | 79 | value.m41 = fromMatrix.m41 + function(t) * (toMatrix.m41 - fromMatrix.m41); 80 | value.m42 = fromMatrix.m42 + function(t) * (toMatrix.m42 - fromMatrix.m42); 81 | value.m43 = fromMatrix.m43 + function(t) * (toMatrix.m43 - fromMatrix.m43); 82 | value.m44 = fromMatrix.m44 + function(t) * (toMatrix.m44 - fromMatrix.m44); 83 | 84 | [values addObject:[NSValue valueWithCATransform3D:value]]; 85 | 86 | } 87 | 88 | CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keyPath]; 89 | [animation setValues:values]; 90 | 91 | return animation; 92 | 93 | } 94 | 95 | // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 96 | CGFloat SSAElasticEaseOut(CGFloat p) 97 | { 98 | return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -10 * p) + 1; 99 | } 100 | 101 | 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/SSARefreshControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // SSARefreshControl.h 3 | // 4 | // Created by Sebastian Andersen on 11 / 8 - 2014 5 | // Copyright (C) 2014 Sebastian Andersen. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | #import "SSACircleView.h" 12 | 13 | typedef NS_ENUM(NSInteger, SSARefreshViewLayerType) { 14 | SSARefreshViewLayerTypeOnSuperView = 0, 15 | SSARefreshViewLayerTypeOnScrollView = 1, 16 | }; 17 | 18 | 19 | @protocol SSARefreshControlDelegate 20 | 21 | @required 22 | 23 | - (void)beganRefreshing; 24 | 25 | @end 26 | 27 | @interface SSARefreshControl : NSObject 28 | 29 | @property (nonatomic, assign) UIColor *circleViewTintColor; 30 | 31 | @property (nonatomic, weak) id delegate; 32 | 33 | - (id)initWithScrollView:(UIScrollView *)scrollView andRefreshViewLayerType:(SSARefreshViewLayerType)refreshViewLayerType; 34 | 35 | - (void)beginRefreshing; 36 | 37 | - (void)endRefreshing; 38 | 39 | @end 40 | 41 | @interface SSARefreshCircleContainerView : UIView 42 | 43 | @property (nonatomic, strong) SSACircleView *circleView; 44 | 45 | @end 46 | 47 | -------------------------------------------------------------------------------- /PULLTOREFRESH/SSAPullToRefresh/SSARefreshControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // SSARefreshControl.m 3 | // 4 | // Created by Sebastian Andersen on 11 / 8 - 2014 5 | // Copyright (C) 2014 Sebastian Andersen. All rights reserved. 6 | // 7 | 8 | #import "SSARefreshControl.h" 9 | #import "SSAPullToRefreshAnimator.h" 10 | 11 | #define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON) 12 | #define fequalzero(a) (fabs(a) < FLT_EPSILON) 13 | 14 | #define kSSADefaultRefreshTotalPixels 60 15 | 16 | typedef NS_ENUM(NSInteger, SSARefreshState) { 17 | SSARefreshStatePulling = 0, 18 | SSARefreshStateNormal = 1, 19 | SSARefreshStateLoading = 2, 20 | SSARefreshStateStopped = 3, 21 | }; 22 | 23 | @interface SSARefreshControl () 24 | 25 | @property (nonatomic, readwrite) CGFloat originalTopInset; 26 | @property (nonatomic, assign) CGFloat refreshTotalPixels; 27 | 28 | @property (nonatomic, strong) UIScrollView *scrollView; 29 | 30 | @property (nonatomic, strong) SSARefreshCircleContainerView *refreshCircleContainerView; 31 | @property (nonatomic, assign) SSARefreshState refreshState; 32 | @property (nonatomic, assign) SSARefreshViewLayerType refreshViewLayerType; 33 | 34 | @property (nonatomic, assign) BOOL isRefreshing; 35 | 36 | @end 37 | 38 | @implementation SSARefreshControl 39 | 40 | #pragma mark Init 41 | 42 | - (id)initWithScrollView:(UIScrollView *)scrollView andRefreshViewLayerType:(SSARefreshViewLayerType)refreshViewLayerType; { 43 | self = [super init]; 44 | if (self) { 45 | self.scrollView = scrollView; 46 | self.refreshViewLayerType = refreshViewLayerType; 47 | [self setupRefreshControl]; 48 | } 49 | return self; 50 | } 51 | 52 | #pragma mark - Pull Down Refreshing Method 53 | 54 | - (void)beginRefreshing { 55 | 56 | self.isRefreshing = YES; 57 | 58 | self.refreshState = SSARefreshStatePulling; 59 | 60 | self.refreshState = SSARefreshStateLoading; 61 | 62 | } 63 | 64 | - (void)createCircleViewAnimations { 65 | 66 | if (self.refreshCircleContainerView.circleView.offsetY != kSSADefaultRefreshTotalPixels - kSSARefreshCircleViewHeight) { 67 | self.refreshCircleContainerView.circleView.offsetY = kSSADefaultRefreshTotalPixels - kSSARefreshCircleViewHeight; 68 | } 69 | 70 | 71 | [self.refreshCircleContainerView.circleView.layer removeAllAnimations]; 72 | 73 | [self.refreshCircleContainerView.circleView.layer addAnimation:[SSAPullToRefreshAnimator popAnimation] forKey:@"transform"]; 74 | 75 | [self.refreshCircleContainerView.circleView.layer addAnimation:[SSAPullToRefreshAnimator repeatRotateAnimation] forKey:@"rotateAnimation"]; 76 | 77 | 78 | [self beganRefreshing]; 79 | } 80 | 81 | 82 | 83 | - (void)beganRefreshing { 84 | 85 | [self setScrollViewContentInset]; 86 | 87 | if ([self.delegate respondsToSelector:@selector(beganRefreshing)]) { 88 | 89 | [self.delegate beganRefreshing]; 90 | } 91 | 92 | } 93 | 94 | 95 | - (void)endRefreshing { 96 | 97 | self.isRefreshing = NO; 98 | self.refreshState = SSARefreshStateStopped; 99 | 100 | [self resetScrollViewContentInset]; 101 | 102 | 103 | } 104 | 105 | 106 | #pragma mark - Scroll View 107 | 108 | - (void)resetScrollViewContentInset { 109 | UIEdgeInsets contentInset = self.scrollView.contentInset; 110 | contentInset.top = self.originalTopInset; 111 | 112 | [UIView animateWithDuration:0.3f animations:^{ 113 | self.scrollView.contentInset = contentInset; 114 | } completion:^(BOOL finished) { 115 | 116 | self.refreshState = SSARefreshStateNormal; 117 | 118 | self.refreshCircleContainerView.circleView.offsetY = 0; 119 | 120 | if (self.refreshCircleContainerView.circleView) { 121 | [self.refreshCircleContainerView.circleView.layer removeAllAnimations]; 122 | } 123 | 124 | 125 | }]; 126 | } 127 | 128 | - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { 129 | [UIView animateWithDuration:0.3 130 | delay:0 131 | options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState 132 | animations:^{ 133 | self.scrollView.contentInset = contentInset; 134 | } 135 | completion:^(BOOL finished) { 136 | if (finished && self.refreshState == SSARefreshStateStopped) { 137 | self.refreshState = SSARefreshStateNormal; 138 | 139 | if (self.refreshCircleContainerView.circleView) { 140 | [self.refreshCircleContainerView.circleView.layer removeAllAnimations]; 141 | } 142 | } 143 | }]; 144 | } 145 | 146 | - (void)setScrollViewContentInsetForLoading { 147 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 148 | currentInsets.top = self.refreshTotalPixels; 149 | [self setScrollViewContentInset:currentInsets]; 150 | } 151 | 152 | - (void)setScrollViewContentInset { 153 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 154 | currentInsets.bottom = 0; 155 | [self setScrollViewContentInset:currentInsets]; 156 | } 157 | 158 | #pragma mark - Propertys 159 | 160 | - (SSARefreshCircleContainerView *)refreshCircleContainerView { 161 | if (!_refreshCircleContainerView) { 162 | _refreshCircleContainerView = [[SSARefreshCircleContainerView alloc] initWithFrame:CGRectMake(0, (self.refreshViewLayerType == SSARefreshViewLayerTypeOnScrollView ? -kSSADefaultRefreshTotalPixels : self.originalTopInset), CGRectGetWidth([[UIScreen mainScreen] bounds]), kSSADefaultRefreshTotalPixels)]; 163 | _refreshCircleContainerView.backgroundColor = [UIColor clearColor]; 164 | _refreshCircleContainerView.circleView.heightBeginToRefresh = kSSADefaultRefreshTotalPixels - kSSARefreshCircleViewHeight; 165 | _refreshCircleContainerView.circleView.offsetY = 10; 166 | 167 | } 168 | return _refreshCircleContainerView; 169 | } 170 | 171 | 172 | 173 | #pragma mark - Getter Method 174 | 175 | 176 | - (CGFloat)refreshTotalPixels { 177 | return kSSADefaultRefreshTotalPixels + [self getAdaptorHeight]; 178 | } 179 | 180 | - (CGFloat)getAdaptorHeight { 181 | return self.originalTopInset; 182 | } 183 | 184 | #pragma mark - Setter Method 185 | 186 | - (void)setRefreshState:(SSARefreshState)refreshState { 187 | switch (refreshState) { 188 | case SSARefreshStateStopped: 189 | case SSARefreshStateNormal: 190 | case SSARefreshStateLoading: { 191 | if (self.isRefreshing) { 192 | 193 | [self setScrollViewContentInsetForLoading]; 194 | 195 | if(_refreshState == SSARefreshStatePulling) { 196 | [self createCircleViewAnimations]; 197 | } 198 | } 199 | break; 200 | } 201 | case SSARefreshStatePulling: 202 | break; 203 | default: 204 | break; 205 | } 206 | 207 | _refreshState = refreshState; 208 | 209 | } 210 | 211 | - (void)setupRefreshControl { 212 | 213 | self.originalTopInset = self.scrollView.contentInset.top; 214 | 215 | self.refreshState = SSARefreshStateNormal; 216 | 217 | [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 218 | 219 | if (self.refreshViewLayerType == SSARefreshViewLayerTypeOnSuperView) { 220 | self.scrollView.backgroundColor = [UIColor clearColor]; 221 | [self.scrollView.superview insertSubview:self.refreshCircleContainerView belowSubview:self.scrollView]; 222 | 223 | } else if (self.refreshViewLayerType == SSARefreshViewLayerTypeOnScrollView) { 224 | [self.scrollView addSubview:self.refreshCircleContainerView]; 225 | } 226 | 227 | 228 | } 229 | 230 | 231 | - (void)dealloc { 232 | 233 | [self.scrollView removeObserver:self forKeyPath:@"contentOffset" context:nil]; 234 | 235 | self.scrollView = nil; 236 | 237 | [self.refreshCircleContainerView removeFromSuperview]; 238 | self.refreshCircleContainerView = nil; 239 | 240 | } 241 | 242 | #pragma mark - Getter Method 243 | 244 | 245 | #pragma mark - KVO 246 | 247 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 248 | 249 | if ([keyPath isEqualToString:@"contentOffset"]) { 250 | 251 | CGPoint contentOffset = [[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]; 252 | 253 | if(self.refreshState != SSARefreshStateLoading) { 254 | 255 | CGFloat pullDownOffset = (MIN(ABS(self.scrollView.contentOffset.y + [self getAdaptorHeight]), kSSADefaultRefreshTotalPixels) - kSSARefreshCircleViewHeight); 256 | if (ABS(self.scrollView.contentOffset.y + [self getAdaptorHeight]) >= kSSARefreshCircleViewHeight) { 257 | if (!self.isRefreshing) { 258 | self.refreshCircleContainerView.circleView.offsetY = pullDownOffset; 259 | } 260 | 261 | } 262 | 263 | CGFloat scrollOffsetThreshold = -(kSSADefaultRefreshTotalPixels + self.originalTopInset); 264 | 265 | if(!self.scrollView.isDragging && self.refreshState == SSARefreshStatePulling) { 266 | if (!self.isRefreshing) { 267 | self.isRefreshing = YES; 268 | self.refreshState = SSARefreshStateLoading; 269 | } 270 | } else if(contentOffset.y < scrollOffsetThreshold && self.scrollView.isDragging && self.refreshState == SSARefreshStateStopped) { 271 | self.refreshState = SSARefreshStatePulling; 272 | } else if(contentOffset.y >= scrollOffsetThreshold && self.refreshState != SSARefreshStateStopped) { 273 | self.refreshState = SSARefreshStateStopped; 274 | } 275 | } else { 276 | if (self.isRefreshing) { 277 | CGFloat offset; 278 | UIEdgeInsets contentInset; 279 | offset = MAX(self.scrollView.contentOffset.y * -1, kSSADefaultRefreshTotalPixels); 280 | offset = MIN(offset, self.refreshTotalPixels); 281 | contentInset = self.scrollView.contentInset; 282 | self.scrollView.contentInset = UIEdgeInsetsMake(offset, contentInset.left, contentInset.bottom, contentInset.right); 283 | } 284 | } 285 | } 286 | } 287 | 288 | 289 | @end 290 | 291 | 292 | 293 | @implementation SSARefreshCircleContainerView 294 | 295 | - (id)initWithFrame:(CGRect)frame { 296 | self = [super initWithFrame:frame]; 297 | if (self) { 298 | 299 | [self addSubview:self.circleView]; 300 | 301 | } 302 | return self; 303 | } 304 | 305 | - (SSACircleView *)circleView { 306 | if (!_circleView) { 307 | _circleView = [[SSACircleView alloc] initWithFrame:CGRectMake((CGRectGetWidth(self.bounds) - kSSARefreshCircleViewHeight) / 2, (CGRectGetHeight(self.bounds) - kSSARefreshCircleViewHeight) / 2 - 5, kSSARefreshCircleViewHeight, kSSARefreshCircleViewHeight)]; 308 | } 309 | return _circleView; 310 | } 311 | 312 | - (void)dealloc { 313 | _circleView = nil; 314 | } 315 | 316 | @end 317 | -------------------------------------------------------------------------------- /PULLTOREFRESH/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PULLTOREFRESH 4 | // 5 | // Created by Sebastian Andersen on 16/11/14. 6 | // Copyright (c) 2014 SebastianA. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @property (nonatomic, strong) UITableView *tableView; 14 | @property (nonatomic, strong) NSMutableArray *dataSource; 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /PULLTOREFRESH/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PULLTOREFRESH 4 | // 5 | // Created by Sebastian Andersen on 16/11/14. 6 | // Copyright (c) 2014 SebastianA. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "SSARefreshControl.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, strong) SSARefreshControl *refreshControl; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | 22 | - (void)viewDidAppear:(BOOL)animated { 23 | [super viewDidAppear:animated]; 24 | 25 | [self.refreshControl beginRefreshing]; 26 | 27 | } 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | 32 | self.refreshControl = [[SSARefreshControl alloc] initWithScrollView:self.tableView andRefreshViewLayerType:SSARefreshViewLayerTypeOnScrollView]; 33 | self.refreshControl.delegate = self; 34 | 35 | self.view.backgroundColor = [UIColor whiteColor]; 36 | [self.view addSubview:self.tableView]; 37 | } 38 | 39 | 40 | 41 | #pragma mark - UITableView DataSource 42 | 43 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 44 | return 1; 45 | } 46 | 47 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 48 | return 5; 49 | } 50 | 51 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 52 | 53 | static NSString *cellIdentifier = @"cellIdentifier"; 54 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 55 | if (!cell) { 56 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; 57 | cell.textLabel.font = [UIFont systemFontOfSize:15]; 58 | cell.detailTextLabel.font = [UIFont systemFontOfSize:12]; 59 | } 60 | 61 | cell.textLabel.text = [NSString stringWithFormat:@"Row %li", (long)indexPath.row]; 62 | return cell; 63 | } 64 | 65 | - (void)beganRefreshing { 66 | 67 | [self loadDataSource]; 68 | 69 | } 70 | 71 | - (void)loadDataSource { 72 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 73 | 74 | sleep(1.5); 75 | dispatch_async(dispatch_get_main_queue(), ^{ 76 | 77 | [self.tableView reloadData]; 78 | [self.refreshControl endRefreshing]; 79 | }); 80 | 81 | }); 82 | } 83 | 84 | #pragma mark - Properties 85 | 86 | - (UITableView *)tableView { 87 | if (!_tableView) { 88 | CGRect tableViewFrame = self.navigationController.view.bounds; 89 | tableViewFrame.origin.y = tableViewFrame.origin.y + 70; 90 | _tableView = [[UITableView alloc] initWithFrame:tableViewFrame]; 91 | _tableView.delegate = self; 92 | _tableView.dataSource = self; 93 | 94 | } 95 | return _tableView; 96 | } 97 | 98 | - (void)dealloc { 99 | 100 | self.tableView.delegate = nil; 101 | self.tableView.dataSource = nil; 102 | self.tableView = nil; 103 | } 104 | 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /PULLTOREFRESH/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PULLTOREFRESH 4 | // 5 | // Created by Sebastian S. Andersen on 16/11/14. 6 | // Copyright (c) 2014 SebastianA. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PULLTOREFRESHTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | SebastianA.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PULLTOREFRESHTests/PULLTOREFRESHTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PULLTOREFRESHTests.m 3 | // PULLTOREFRESHTests 4 | // 5 | // Created by Sebastian S. Andersen on 16/11/14. 6 | // Copyright (c) 2014 SebastianA. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface PULLTOREFRESHTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation PULLTOREFRESHTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SSAPullToRefresh 2 | ================ 3 | 4 | A simple PullToRefresh implementation, heavily inspired by XHRefreshControl, RHRefreshControl, SVProgressHUD & the look of the Facebook iOS App PullToRefresh component. 5 | 6 | 7 | ![My image](https://github.com/SSA111/SSAPullToRefresh/blob/master/PULLTOREFRESH/SSAPullToRefresh.png?raw=true) 8 | 9 | 10 | ##Thanks 11 | https://github.com/rathahin/RHRefreshControl 12 | 13 | https://github.com/xhzengAIB/XHRefreshControl 14 | 15 | https://github.com/TransitApp/SVProgressHUD 16 | 17 | ##License 18 | Do what you want with it. Have fun! :-) 19 | --------------------------------------------------------------------------------