├── .gitignore ├── Demo ├── HTPullToRefreshDemo.xcodeproj │ └── project.pbxproj └── HTPullToRefreshDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── HTViewController.h │ ├── HTViewController.m │ ├── HorizontalViewController.h │ ├── HorizontalViewController.m │ ├── Info.plist │ ├── SquareCell.h │ ├── SquareCell.m │ ├── SquareCell.xib │ ├── VerticalViewController.h │ ├── VerticalViewController.m │ └── main.m ├── HTPullToRefresh.podspec ├── LICENSE.txt ├── README.md └── SVPullToRefresh ├── NSString+HTPullToRefresh.h ├── NSString+HTPullToRefresh.m ├── SVPullToRefresh.h ├── UIScrollView+SVInfiniteScrolling.h ├── UIScrollView+SVInfiniteScrolling.m ├── UIScrollView+SVPullToRefresh.h └── UIScrollView+SVPullToRefresh.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | *.DS_Store 3 | *.psd 4 | 5 | # Xcode 6 | *.pbxuser 7 | *.mode1v3 8 | *.mode2v3 9 | *.perspectivev3 10 | *.xcuserstate 11 | project.xcworkspace/ 12 | xcuserdata/ 13 | 14 | # Generated files 15 | build/ 16 | *.[oa] 17 | *.pyc 18 | 19 | # Backup files 20 | *~.nib 21 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F39ACCF81D326BB00095F4F3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACCF71D326BB00095F4F3 /* main.m */; }; 11 | F39ACCFB1D326BB00095F4F3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACCFA1D326BB00095F4F3 /* AppDelegate.m */; }; 12 | F39ACCFE1D326BB00095F4F3 /* HTViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACCFD1D326BB00095F4F3 /* HTViewController.m */; }; 13 | F39ACD011D326BB00095F4F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F39ACCFF1D326BB00095F4F3 /* Main.storyboard */; }; 14 | F39ACD031D326BB00095F4F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F39ACD021D326BB00095F4F3 /* Assets.xcassets */; }; 15 | F39ACD061D326BB00095F4F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F39ACD041D326BB00095F4F3 /* LaunchScreen.storyboard */; }; 16 | F39ACD0E1D326C050095F4F3 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F39ACD0D1D326C050095F4F3 /* QuartzCore.framework */; }; 17 | F39ACD101D326C130095F4F3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F39ACD0F1D326C130095F4F3 /* UIKit.framework */; }; 18 | F39ACD121D326C190095F4F3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F39ACD111D326C190095F4F3 /* Foundation.framework */; }; 19 | F39ACD141D326C200095F4F3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F39ACD131D326C200095F4F3 /* CoreGraphics.framework */; }; 20 | F39ACD1A1D326C870095F4F3 /* UIScrollView+SVPullToRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACD191D326C870095F4F3 /* UIScrollView+SVPullToRefresh.m */; }; 21 | F39ACD1D1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACD1C1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.m */; }; 22 | F39ACD1F1D326CF20095F4F3 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = F39ACD1E1D326CF20095F4F3 /* README.md */; }; 23 | F39ACD221D33D4FB0095F4F3 /* VerticalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACD211D33D4FB0095F4F3 /* VerticalViewController.m */; }; 24 | F39ACD251D33D5130095F4F3 /* HorizontalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACD241D33D5130095F4F3 /* HorizontalViewController.m */; }; 25 | F39ACD281D33E0590095F4F3 /* NSString+HTPullToRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACD271D33E0590095F4F3 /* NSString+HTPullToRefresh.m */; }; 26 | F39ACD2B1D351D170095F4F3 /* SquareCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F39ACD2A1D351D170095F4F3 /* SquareCell.m */; }; 27 | F39ACD2D1D351D2B0095F4F3 /* SquareCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F39ACD2C1D351D2B0095F4F3 /* SquareCell.xib */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | F39ACCF31D326BAF0095F4F3 /* HTPullToRefreshDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HTPullToRefreshDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | F39ACCF71D326BB00095F4F3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | F39ACCF91D326BB00095F4F3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 34 | F39ACCFA1D326BB00095F4F3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 35 | F39ACCFC1D326BB00095F4F3 /* HTViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTViewController.h; sourceTree = ""; }; 36 | F39ACCFD1D326BB00095F4F3 /* HTViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HTViewController.m; sourceTree = ""; }; 37 | F39ACD001D326BB00095F4F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | F39ACD021D326BB00095F4F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | F39ACD051D326BB00095F4F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | F39ACD071D326BB00095F4F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | F39ACD0D1D326C050095F4F3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 42 | F39ACD0F1D326C130095F4F3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 43 | F39ACD111D326C190095F4F3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 44 | F39ACD131D326C200095F4F3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 45 | F39ACD171D326C870095F4F3 /* SVPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVPullToRefresh.h; sourceTree = ""; }; 46 | F39ACD181D326C870095F4F3 /* UIScrollView+SVPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SVPullToRefresh.h"; sourceTree = ""; }; 47 | F39ACD191D326C870095F4F3 /* UIScrollView+SVPullToRefresh.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SVPullToRefresh.m"; sourceTree = ""; }; 48 | F39ACD1B1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SVInfiniteScrolling.h"; sourceTree = ""; }; 49 | F39ACD1C1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SVInfiniteScrolling.m"; sourceTree = ""; }; 50 | F39ACD1E1D326CF20095F4F3 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 51 | F39ACD201D33D4FB0095F4F3 /* VerticalViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VerticalViewController.h; sourceTree = ""; }; 52 | F39ACD211D33D4FB0095F4F3 /* VerticalViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VerticalViewController.m; sourceTree = ""; }; 53 | F39ACD231D33D5130095F4F3 /* HorizontalViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HorizontalViewController.h; sourceTree = ""; }; 54 | F39ACD241D33D5130095F4F3 /* HorizontalViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HorizontalViewController.m; sourceTree = ""; }; 55 | F39ACD261D33E0590095F4F3 /* NSString+HTPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+HTPullToRefresh.h"; sourceTree = ""; }; 56 | F39ACD271D33E0590095F4F3 /* NSString+HTPullToRefresh.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+HTPullToRefresh.m"; sourceTree = ""; }; 57 | F39ACD291D351D170095F4F3 /* SquareCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SquareCell.h; sourceTree = ""; }; 58 | F39ACD2A1D351D170095F4F3 /* SquareCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SquareCell.m; sourceTree = ""; }; 59 | F39ACD2C1D351D2B0095F4F3 /* SquareCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SquareCell.xib; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | F39ACCF01D326BAF0095F4F3 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | F39ACD141D326C200095F4F3 /* CoreGraphics.framework in Frameworks */, 68 | F39ACD121D326C190095F4F3 /* Foundation.framework in Frameworks */, 69 | F39ACD101D326C130095F4F3 /* UIKit.framework in Frameworks */, 70 | F39ACD0E1D326C050095F4F3 /* QuartzCore.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | F39ACCEA1D326BAF0095F4F3 = { 78 | isa = PBXGroup; 79 | children = ( 80 | F39ACD1E1D326CF20095F4F3 /* README.md */, 81 | F39ACCF51D326BB00095F4F3 /* Demo */, 82 | F39ACD161D326C870095F4F3 /* SVPullToRefresh */, 83 | F39ACD151D326C260095F4F3 /* Frameworks */, 84 | F39ACCF41D326BAF0095F4F3 /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | F39ACCF41D326BAF0095F4F3 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | F39ACCF31D326BAF0095F4F3 /* HTPullToRefreshDemo.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | F39ACCF51D326BB00095F4F3 /* Demo */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | F39ACCF91D326BB00095F4F3 /* AppDelegate.h */, 100 | F39ACCFA1D326BB00095F4F3 /* AppDelegate.m */, 101 | F39ACCFC1D326BB00095F4F3 /* HTViewController.h */, 102 | F39ACCFD1D326BB00095F4F3 /* HTViewController.m */, 103 | F39ACD201D33D4FB0095F4F3 /* VerticalViewController.h */, 104 | F39ACD211D33D4FB0095F4F3 /* VerticalViewController.m */, 105 | F39ACD231D33D5130095F4F3 /* HorizontalViewController.h */, 106 | F39ACD241D33D5130095F4F3 /* HorizontalViewController.m */, 107 | F39ACCFF1D326BB00095F4F3 /* Main.storyboard */, 108 | F39ACD291D351D170095F4F3 /* SquareCell.h */, 109 | F39ACD2A1D351D170095F4F3 /* SquareCell.m */, 110 | F39ACD2C1D351D2B0095F4F3 /* SquareCell.xib */, 111 | F39ACCF61D326BB00095F4F3 /* Supporting Files */, 112 | ); 113 | name = Demo; 114 | path = HTPullToRefreshDemo; 115 | sourceTree = ""; 116 | }; 117 | F39ACCF61D326BB00095F4F3 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | F39ACD021D326BB00095F4F3 /* Assets.xcassets */, 121 | F39ACD041D326BB00095F4F3 /* LaunchScreen.storyboard */, 122 | F39ACD071D326BB00095F4F3 /* Info.plist */, 123 | F39ACCF71D326BB00095F4F3 /* main.m */, 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | F39ACD151D326C260095F4F3 /* Frameworks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | F39ACD131D326C200095F4F3 /* CoreGraphics.framework */, 132 | F39ACD111D326C190095F4F3 /* Foundation.framework */, 133 | F39ACD0F1D326C130095F4F3 /* UIKit.framework */, 134 | F39ACD0D1D326C050095F4F3 /* QuartzCore.framework */, 135 | ); 136 | name = Frameworks; 137 | sourceTree = ""; 138 | }; 139 | F39ACD161D326C870095F4F3 /* SVPullToRefresh */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | F39ACD171D326C870095F4F3 /* SVPullToRefresh.h */, 143 | F39ACD1B1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.h */, 144 | F39ACD1C1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.m */, 145 | F39ACD181D326C870095F4F3 /* UIScrollView+SVPullToRefresh.h */, 146 | F39ACD191D326C870095F4F3 /* UIScrollView+SVPullToRefresh.m */, 147 | F39ACD261D33E0590095F4F3 /* NSString+HTPullToRefresh.h */, 148 | F39ACD271D33E0590095F4F3 /* NSString+HTPullToRefresh.m */, 149 | ); 150 | name = SVPullToRefresh; 151 | path = ../SVPullToRefresh; 152 | sourceTree = ""; 153 | }; 154 | /* End PBXGroup section */ 155 | 156 | /* Begin PBXNativeTarget section */ 157 | F39ACCF21D326BAF0095F4F3 /* HTPullToRefreshDemo */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = F39ACD0A1D326BB00095F4F3 /* Build configuration list for PBXNativeTarget "HTPullToRefreshDemo" */; 160 | buildPhases = ( 161 | F39ACCEF1D326BAF0095F4F3 /* Sources */, 162 | F39ACCF01D326BAF0095F4F3 /* Frameworks */, 163 | F39ACCF11D326BAF0095F4F3 /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = HTPullToRefreshDemo; 170 | productName = HTPullToRefreshDemo; 171 | productReference = F39ACCF31D326BAF0095F4F3 /* HTPullToRefreshDemo.app */; 172 | productType = "com.apple.product-type.application"; 173 | }; 174 | /* End PBXNativeTarget section */ 175 | 176 | /* Begin PBXProject section */ 177 | F39ACCEB1D326BAF0095F4F3 /* Project object */ = { 178 | isa = PBXProject; 179 | attributes = { 180 | LastUpgradeCheck = 0730; 181 | ORGANIZATIONNAME = "Hoang Tran"; 182 | TargetAttributes = { 183 | F39ACCF21D326BAF0095F4F3 = { 184 | CreatedOnToolsVersion = 7.3; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = F39ACCEE1D326BAF0095F4F3 /* Build configuration list for PBXProject "HTPullToRefreshDemo" */; 189 | compatibilityVersion = "Xcode 3.2"; 190 | developmentRegion = English; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = F39ACCEA1D326BAF0095F4F3; 197 | productRefGroup = F39ACCF41D326BAF0095F4F3 /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | F39ACCF21D326BAF0095F4F3 /* HTPullToRefreshDemo */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | F39ACCF11D326BAF0095F4F3 /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | F39ACD1F1D326CF20095F4F3 /* README.md in Resources */, 212 | F39ACD061D326BB00095F4F3 /* LaunchScreen.storyboard in Resources */, 213 | F39ACD031D326BB00095F4F3 /* Assets.xcassets in Resources */, 214 | F39ACD011D326BB00095F4F3 /* Main.storyboard in Resources */, 215 | F39ACD2D1D351D2B0095F4F3 /* SquareCell.xib in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | F39ACCEF1D326BAF0095F4F3 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | F39ACD2B1D351D170095F4F3 /* SquareCell.m in Sources */, 227 | F39ACD1A1D326C870095F4F3 /* UIScrollView+SVPullToRefresh.m in Sources */, 228 | F39ACCFE1D326BB00095F4F3 /* HTViewController.m in Sources */, 229 | F39ACD221D33D4FB0095F4F3 /* VerticalViewController.m in Sources */, 230 | F39ACD281D33E0590095F4F3 /* NSString+HTPullToRefresh.m in Sources */, 231 | F39ACCFB1D326BB00095F4F3 /* AppDelegate.m in Sources */, 232 | F39ACCF81D326BB00095F4F3 /* main.m in Sources */, 233 | F39ACD251D33D5130095F4F3 /* HorizontalViewController.m in Sources */, 234 | F39ACD1D1D326CD80095F4F3 /* UIScrollView+SVInfiniteScrolling.m in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | F39ACCFF1D326BB00095F4F3 /* Main.storyboard */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | F39ACD001D326BB00095F4F3 /* Base */, 245 | ); 246 | name = Main.storyboard; 247 | sourceTree = ""; 248 | }; 249 | F39ACD041D326BB00095F4F3 /* LaunchScreen.storyboard */ = { 250 | isa = PBXVariantGroup; 251 | children = ( 252 | F39ACD051D326BB00095F4F3 /* Base */, 253 | ); 254 | name = LaunchScreen.storyboard; 255 | sourceTree = ""; 256 | }; 257 | /* End PBXVariantGroup section */ 258 | 259 | /* Begin XCBuildConfiguration section */ 260 | F39ACD081D326BB00095F4F3 /* Debug */ = { 261 | isa = XCBuildConfiguration; 262 | buildSettings = { 263 | ALWAYS_SEARCH_USER_PATHS = NO; 264 | CLANG_ANALYZER_NONNULL = YES; 265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 266 | CLANG_CXX_LIBRARY = "libc++"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 279 | COPY_PHASE_STRIP = NO; 280 | DEBUG_INFORMATION_FORMAT = dwarf; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | ENABLE_TESTABILITY = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu99; 284 | GCC_DYNAMIC_NO_PIC = NO; 285 | GCC_NO_COMMON_BLOCKS = YES; 286 | GCC_OPTIMIZATION_LEVEL = 0; 287 | GCC_PREPROCESSOR_DEFINITIONS = ( 288 | "DEBUG=1", 289 | "$(inherited)", 290 | ); 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 298 | MTL_ENABLE_DEBUG_INFO = YES; 299 | ONLY_ACTIVE_ARCH = YES; 300 | SDKROOT = iphoneos; 301 | }; 302 | name = Debug; 303 | }; 304 | F39ACD091D326BB00095F4F3 /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ALWAYS_SEARCH_USER_PATHS = NO; 308 | CLANG_ANALYZER_NONNULL = YES; 309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 310 | CLANG_CXX_LIBRARY = "libc++"; 311 | CLANG_ENABLE_MODULES = YES; 312 | CLANG_ENABLE_OBJC_ARC = YES; 313 | CLANG_WARN_BOOL_CONVERSION = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 323 | COPY_PHASE_STRIP = NO; 324 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 325 | ENABLE_NS_ASSERTIONS = NO; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu99; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | VALIDATE_PRODUCT = YES; 339 | }; 340 | name = Release; 341 | }; 342 | F39ACD0B1D326BB00095F4F3 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 346 | INFOPLIST_FILE = HTPullToRefreshDemo/Info.plist; 347 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 348 | PRODUCT_BUNDLE_IDENTIFIER = hoang.tran.HTPullToRefreshDemo; 349 | PRODUCT_NAME = "$(TARGET_NAME)"; 350 | }; 351 | name = Debug; 352 | }; 353 | F39ACD0C1D326BB00095F4F3 /* Release */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | INFOPLIST_FILE = HTPullToRefreshDemo/Info.plist; 358 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 359 | PRODUCT_BUNDLE_IDENTIFIER = hoang.tran.HTPullToRefreshDemo; 360 | PRODUCT_NAME = "$(TARGET_NAME)"; 361 | }; 362 | name = Release; 363 | }; 364 | /* End XCBuildConfiguration section */ 365 | 366 | /* Begin XCConfigurationList section */ 367 | F39ACCEE1D326BAF0095F4F3 /* Build configuration list for PBXProject "HTPullToRefreshDemo" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | F39ACD081D326BB00095F4F3 /* Debug */, 371 | F39ACD091D326BB00095F4F3 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | F39ACD0A1D326BB00095F4F3 /* Build configuration list for PBXNativeTarget "HTPullToRefreshDemo" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | F39ACD0B1D326BB00095F4F3 /* Debug */, 380 | F39ACD0C1D326BB00095F4F3 /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | /* End XCConfigurationList section */ 386 | }; 387 | rootObject = F39ACCEB1D326BAF0095F4F3 /* Project object */; 388 | } 389 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/10/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/10/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | @end 13 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/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 | } -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/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 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/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 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/HTViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/10/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HTViewController : UITableViewController 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/HTViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/10/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import "HTViewController.h" 10 | 11 | @implementation HTViewController 12 | @end 13 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/HorizontalViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalViewController.h 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/11/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HorizontalViewController : UIViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/HorizontalViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalViewController.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/11/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import "HorizontalViewController.h" 10 | #import "SVPullToRefresh.h" 11 | #import "SquareCell.h" 12 | 13 | @interface HorizontalViewController () 14 | 15 | @property (nonatomic, strong) NSMutableArray *dataSource; 16 | 17 | @end 18 | 19 | @implementation HorizontalViewController 20 | 21 | NSString *kSquareCellReuseID = @"SquareCell"; 22 | 23 | @synthesize dataSource; 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | [self setupDataSource]; 28 | [self setupPullToRefresh]; 29 | [self.collectionView registerNib:[UINib nibWithNibName:@"SquareCell" bundle:nil] forCellWithReuseIdentifier:kSquareCellReuseID]; 30 | } 31 | 32 | #pragma mark - helper 33 | 34 | - (void)setupDataSource { 35 | dataSource = [NSMutableArray array]; 36 | for(int i = 1; i <= 20; i++) { 37 | [dataSource addObject:@(i)]; 38 | } 39 | } 40 | 41 | - (void)setupPullToRefresh { 42 | __weak HorizontalViewController *weakSelf = self; 43 | 44 | [self.collectionView addPullToRefreshWithActionHandler:^{ 45 | [weakSelf insertRowAtLeft]; 46 | } position:SVPullToRefreshPositionLeft]; 47 | 48 | [self.collectionView addPullToRefreshWithActionHandler:^{ 49 | [weakSelf insertRowAtRight]; 50 | } position:SVPullToRefreshPositionRight]; 51 | 52 | for(SVPullToRefreshView *refreshView in self.collectionView.pullToRefreshViews) { 53 | refreshView.titleLabel.font = [UIFont boldSystemFontOfSize:13]; 54 | refreshView.titleLabel.numberOfLines = 0; 55 | } 56 | } 57 | 58 | - (void)insertRowAtLeft { 59 | __weak HorizontalViewController *weakSelf = self; 60 | NSInteger minValue = [[dataSource firstObject] integerValue]; 61 | 62 | int64_t delayInSeconds = 1.0; 63 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 64 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 65 | [weakSelf.dataSource insertObject:@(minValue - 1) atIndex:0]; 66 | [weakSelf.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]]; 67 | 68 | [[weakSelf.collectionView pullToRefreshViewAtPosition:SVPullToRefreshPositionLeft] stopAnimating]; 69 | }); 70 | } 71 | 72 | - (void)insertRowAtRight { 73 | __weak HorizontalViewController *weakSelf = self; 74 | NSInteger maxValue = [[dataSource lastObject] integerValue]; 75 | 76 | int64_t delayInSeconds = 1.0; 77 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 78 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 79 | [weakSelf.dataSource addObject:@(maxValue + 1)]; 80 | [weakSelf.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:weakSelf.dataSource.count - 1 inSection:0]]]; 81 | 82 | [[weakSelf.collectionView pullToRefreshViewAtPosition:SVPullToRefreshPositionRight] stopAnimating]; 83 | }); 84 | } 85 | 86 | #pragma mark - collectionView 87 | 88 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 89 | return 1; 90 | } 91 | 92 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 93 | return dataSource.count; 94 | } 95 | 96 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 97 | SquareCell *cell = (SquareCell*)[collectionView dequeueReusableCellWithReuseIdentifier:kSquareCellReuseID forIndexPath:indexPath]; 98 | cell.titleLabel.text = [[dataSource objectAtIndex:indexPath.item] stringValue]; 99 | return cell; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/SquareCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SquareCell.h 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/12/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SquareCell : UICollectionViewCell 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/SquareCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SquareCell.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/12/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import "SquareCell.h" 10 | 11 | @implementation SquareCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/SquareCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/VerticalViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VerticalViewController.h 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/11/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VerticalViewController : UIViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/VerticalViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VerticalViewController.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/11/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import "VerticalViewController.h" 10 | #import "SVPullToRefresh.h" 11 | 12 | NSString *kVerticalCellReuseID = @"kVerticalCellReuseID"; 13 | 14 | @interface VerticalViewController () 15 | 16 | @property (nonatomic, strong) NSMutableArray *dataSource; 17 | 18 | @end 19 | 20 | @implementation VerticalViewController 21 | 22 | @synthesize dataSource; 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | [self setupDataSource]; 27 | [self setupPullToRefresh]; 28 | } 29 | 30 | #pragma mark - helper 31 | 32 | - (void)setupDataSource { 33 | dataSource = [NSMutableArray array]; 34 | for(int i = 1; i <= 20; i++) { 35 | [dataSource addObject:@(i)]; 36 | } 37 | } 38 | 39 | - (void)setupPullToRefresh { 40 | __weak VerticalViewController *weakSelf = self; 41 | 42 | [self.tableView addPullToRefreshWithActionHandler:^{ 43 | [weakSelf insertRowAtTop]; 44 | } position:SVPullToRefreshPositionTop]; 45 | 46 | [self.tableView addPullToRefreshWithActionHandler:^{ 47 | [weakSelf insertRowAtBottom]; 48 | } position:SVPullToRefreshPositionBottom]; 49 | } 50 | 51 | 52 | - (void)insertRowAtTop { 53 | __weak VerticalViewController *weakSelf = self; 54 | NSInteger minValue = [[dataSource firstObject] integerValue]; 55 | 56 | int64_t delayInSeconds = 1.0; 57 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 58 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 59 | [weakSelf.tableView beginUpdates]; 60 | [weakSelf.dataSource insertObject:@(minValue - 1) atIndex:0]; 61 | [weakSelf.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationBottom]; 62 | [weakSelf.tableView endUpdates]; 63 | 64 | [[weakSelf.tableView pullToRefreshViewAtPosition:SVPullToRefreshPositionTop] stopAnimating]; 65 | }); 66 | } 67 | 68 | - (void)insertRowAtBottom { 69 | __weak VerticalViewController *weakSelf = self; 70 | NSInteger maxValue = [[dataSource lastObject] integerValue]; 71 | 72 | int64_t delayInSeconds = 1.0; 73 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 74 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 75 | [weakSelf.tableView beginUpdates]; 76 | [weakSelf.dataSource addObject:@(maxValue + 1)]; 77 | [weakSelf.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:weakSelf.dataSource.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationTop]; 78 | [weakSelf.tableView endUpdates]; 79 | 80 | [[weakSelf.tableView pullToRefreshViewAtPosition:SVPullToRefreshPositionBottom] stopAnimating]; 81 | }); 82 | } 83 | 84 | #pragma mark - tableView 85 | 86 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 87 | return 1; 88 | } 89 | 90 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 91 | return dataSource.count; 92 | } 93 | 94 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 95 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kVerticalCellReuseID]; 96 | if(!cell) { 97 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kVerticalCellReuseID]; 98 | } 99 | cell.textLabel.text = [[dataSource objectAtIndex:indexPath.row] stringValue]; 100 | return cell; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Demo/HTPullToRefreshDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/10/16. 6 | // Copyright © 2016 Hoang Tran. 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 | -------------------------------------------------------------------------------- /HTPullToRefresh.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'HTPullToRefresh' 3 | s.version = '0.0.1' 4 | s.platform = :ios, '5.0' 5 | s.license = 'MIT' 6 | s.summary = 'Give multiple pull-to-refreshes to any UIScrollView (both vertically and/or horizontally) with minial setup' 7 | s.homepage = 'https://github.com/hoang-tran/HTPullToRefresh' 8 | s.author = { 'Hoang Tran' => 'hoangtx.master@gmail.com' } 9 | s.source = { :git => 'https://github.com/hoang-tran/HTPullToRefresh.git', :tag => s.version.to_s } 10 | 11 | s.description = 'HTPullToRefresh allows you to easily add multiple pull-to-refreshes ' \ 12 | 'to your UIScrollView (both vertically and/or horizontally) with minimal setup' 13 | 14 | s.frameworks = 'QuartzCore' 15 | s.source_files = 'SVPullToRefresh/*.{h,m}' 16 | s.preserve_paths = 'Demo' 17 | s.requires_arc = true 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Hoang Tran 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This is a fork from the famous [SVPullToRefresh](https://github.com/samvermette/SVPullToRefresh) pod with 2 additional functionalities: 2 | - [x] Can add multiple pull-to-refresh views into one single UIScrollView 3 | - [x] Support both vertical and horizontal pull to refresh 4 | 5 | ![Vertical](https://media.giphy.com/media/l46CbE0xUpHQXdx1S/giphy.gif) ![Horizontal](https://media.giphy.com/media/26BRx2KZGqMoYHYM8/giphy.gif) 6 | 7 | ## Installation 8 | 9 | Add `pod 'HTPullToRefresh'` to your Podfile (remove the `SVPullToRefresh` pod if you had added it before) 10 | 11 | ## Usage 12 | 13 | (see sample Xcode project in `/Demo`) 14 | 15 | ### Adding Pull to Refresh 16 | 17 | If you don't specify the position, it will add to top by default 18 | 19 | ```objective-c 20 | 21 | [tableView addPullToRefreshWithActionHandler:^{ 22 | //do stuff 23 | }]; 24 | ``` 25 | or if you want it from the bottom 26 | 27 | ```objective-c 28 | [tableView addPullToRefreshWithActionHandler:^{ 29 | //do stuff 30 | } position:SVPullToRefreshPositionBottom]; 31 | ``` 32 | 33 | or from the left 34 | 35 | ```objective-c 36 | [tableView addPullToRefreshWithActionHandler:^{ 37 | //do stuff 38 | } position:SVPullToRefreshPositionLeft]; 39 | ``` 40 | 41 | or right? 42 | 43 | ```objective-c 44 | [tableView addPullToRefreshWithActionHandler:^{ 45 | //do stuff 46 | } position:SVPullToRefreshPositionRight]; 47 | ``` 48 | 49 | or add multiple pull-to-refresh views all at once 50 | 51 | ```objective-c 52 | // add top 53 | [tableView addPullToRefreshWithActionHandler:^{ 54 | //do stuff 55 | } position:SVPullToRefreshPositionBottom]; 56 | 57 | // then add bottom 58 | [tableView addPullToRefreshWithActionHandler:^{ 59 | //do stuff 60 | } position:SVPullToRefreshPositionLeft]; 61 | ``` 62 | 63 | ### Retrieve Pull to Refresh View 64 | 65 | Retrieve first pull-to-refresh view: 66 | 67 | ```objective-c 68 | tableView.pullToRefreshView 69 | ``` 70 | 71 | Retrieve pull-torefresh view at a specific position: 72 | 73 | ```objective-c 74 | [tableView pullToRefreshViewAtPosition:SVPullToRefreshPositionBottom]; 75 | ``` 76 | 77 | Since we have multiple pull-to-refresh views inside a UIScrollView, we can retrieve all of them by: 78 | 79 | ```objective-c 80 | tableView.pullToRefreshViews 81 | ``` 82 | 83 | #### Customization 84 | 85 | The pull to refresh view can be customized using the following properties/methods: 86 | 87 | ```objective-c 88 | @property (nonatomic, strong) UIColor *arrowColor; 89 | @property (nonatomic, strong) UIColor *textColor; 90 | @property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 91 | 92 | - (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state; 93 | - (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state; 94 | - (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state; 95 | ``` 96 | 97 | For instance, you would set the `arrowColor` property using: 98 | 99 | ```objective-c 100 | tableView.pullToRefreshView.arrowColor = [UIColor whiteColor]; 101 | ``` 102 | 103 | ### Adding Infinite Scrolling 104 | 105 | ```objective-c 106 | [tableView addInfiniteScrollingWithActionHandler:^{ 107 | // append data to data source, insert new cells at the end of table view 108 | // call [tableView.infiniteScrollingView stopAnimating] when done 109 | }]; 110 | ``` 111 | 112 | If you’d like to programmatically trigger the loading (for instance in `viewDidAppear:`), you can do so with: 113 | 114 | ```objective-c 115 | [tableView triggerInfiniteScrolling]; 116 | ``` 117 | 118 | You can temporarily hide the infinite scrolling view by setting the `showsInfiniteScrolling` property: 119 | 120 | ```objective-c 121 | tableView.showsInfiniteScrolling = NO; 122 | ``` 123 | 124 | #### Customization 125 | 126 | The infinite scrolling view can be customized using the following methods: 127 | 128 | ```objective-c 129 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle; 130 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state; 131 | ``` 132 | 133 | You can access these properties through your scroll view's `infiniteScrollingView` property. 134 | 135 | ## Under the hood 136 | 137 | SVPullToRefresh extends `UIScrollView` by adding new public methods as well as a dynamic properties. 138 | 139 | It uses key-value observing to track the scrollView's `contentOffset`. 140 | 141 | ## Credits 142 | 143 | SVPullToRefresh is brought to you by [Sam Vermette](http://samvermette.com) and [contributors to the project](https://github.com/samvermette/SVPullToRefresh/contributors). If you have feature suggestions or bug reports, feel free to help out by sending pull requests or by [creating new issues](https://github.com/samvermette/SVPullToRefresh/issues/new). If you're using SVPullToRefresh in your project, attribution would be nice. 144 | 145 | Big thanks to [@seb_morel](http://twitter.com/seb_morel) for his [Demistifying the Objective-C runtime](http://cocoaheadsmtl.s3.amazonaws.com/demistifying-runtime.pdf) talk which really helped for this project. 146 | 147 | Hat tip to [Loren Brichter](http://twitter.com/lorenb) for inventing pull-to-refresh. 148 | -------------------------------------------------------------------------------- /SVPullToRefresh/NSString+HTPullToRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Extensions.h 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/11/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface NSString (HTPullToRefresh) 13 | 14 | - (CGSize)sizeWithFont:(UIFont*)font constraintToSize:(CGSize)constrainedSize; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SVPullToRefresh/NSString+HTPullToRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Extensions.m 3 | // HTPullToRefreshDemo 4 | // 5 | // Created by Hoang Tran on 7/11/16. 6 | // Copyright © 2016 Hoang Tran. All rights reserved. 7 | // 8 | 9 | #import "NSString+HTPullToRefresh.h" 10 | 11 | @implementation NSString (HTPullToRefresh) 12 | 13 | - (CGSize)sizeWithFont:(UIFont *)font constraintToSize:(CGSize)constrainedSize { 14 | CGRect rect; 15 | 16 | if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { 17 | rect = [self boundingRectWithSize:constrainedSize 18 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 19 | attributes:@{NSFontAttributeName:font} 20 | context:nil]; 21 | } 22 | else { 23 | #pragma GCC diagnostic push 24 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 25 | CGSize size = [self sizeWithFont:font constrainedToSize:constrainedSize lineBreakMode:NSLineBreakByWordWrapping]; 26 | rect = CGRectMake(0, 0, size.width, size.height); 27 | #pragma GCC diagnostic pop 28 | } 29 | return rect.size; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /SVPullToRefresh/SVPullToRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // SVPullToRefresh.h 3 | // SVPullToRefreshDemo 4 | // 5 | // Created by Sam Vermette on 23.04.12. 6 | // Copyright (c) 2012 samvermette.com. All rights reserved. 7 | // 8 | // https://github.com/samvermette/SVPullToRefresh 9 | // 10 | 11 | // this header file is provided for backwards compatibility and will be removed in the future 12 | // here's how you should import SVPullToRefresh now: 13 | 14 | #import "UIScrollView+SVPullToRefresh.h" 15 | #import "UIScrollView+SVInfiniteScrolling.h" 16 | -------------------------------------------------------------------------------- /SVPullToRefresh/UIScrollView+SVInfiniteScrolling.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVInfiniteScrolling.h 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | 12 | @class SVInfiniteScrollingView; 13 | 14 | @interface UIScrollView (SVInfiniteScrolling) 15 | 16 | - (void)addInfiniteScrollingWithActionHandler:(void (^)(void))actionHandler; 17 | - (void)triggerInfiniteScrolling; 18 | 19 | @property (nonatomic, strong, readonly) SVInfiniteScrollingView *infiniteScrollingView; 20 | @property (nonatomic, assign) BOOL showsInfiniteScrolling; 21 | 22 | @end 23 | 24 | 25 | enum { 26 | SVInfiniteScrollingStateStopped = 0, 27 | SVInfiniteScrollingStateTriggered, 28 | SVInfiniteScrollingStateLoading, 29 | SVInfiniteScrollingStateAll = 10 30 | }; 31 | 32 | typedef NSUInteger SVInfiniteScrollingState; 33 | 34 | @interface SVInfiniteScrollingView : UIView 35 | 36 | @property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 37 | @property (nonatomic, readonly) SVInfiniteScrollingState state; 38 | @property (nonatomic, readwrite) BOOL enabled; 39 | 40 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state; 41 | 42 | - (void)startAnimating; 43 | - (void)stopAnimating; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /SVPullToRefresh/UIScrollView+SVInfiniteScrolling.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVInfiniteScrolling.m 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import "UIScrollView+SVInfiniteScrolling.h" 12 | 13 | 14 | static CGFloat const SVInfiniteScrollingViewHeight = 60; 15 | 16 | @interface SVInfiniteScrollingDotView : UIView 17 | 18 | @property (nonatomic, strong) UIColor *arrowColor; 19 | 20 | @end 21 | 22 | 23 | 24 | @interface SVInfiniteScrollingView () 25 | 26 | @property (nonatomic, copy) void (^infiniteScrollingHandler)(void); 27 | 28 | @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; 29 | @property (nonatomic, readwrite) SVInfiniteScrollingState state; 30 | @property (nonatomic, strong) NSMutableArray *viewForState; 31 | @property (nonatomic, weak) UIScrollView *scrollView; 32 | @property (nonatomic, readwrite) CGFloat originalBottomInset; 33 | @property (nonatomic, assign) BOOL wasTriggeredByUser; 34 | @property (nonatomic, assign) BOOL isObserving; 35 | 36 | - (void)resetScrollViewContentInset; 37 | - (void)setScrollViewContentInsetForInfiniteScrolling; 38 | - (void)setScrollViewContentInset:(UIEdgeInsets)insets; 39 | 40 | @end 41 | 42 | 43 | 44 | #pragma mark - UIScrollView (SVInfiniteScrollingView) 45 | #import 46 | 47 | static char UIScrollViewInfiniteScrollingView; 48 | UIEdgeInsets scrollViewOriginalContentInsets; 49 | 50 | @implementation UIScrollView (SVInfiniteScrolling) 51 | 52 | @dynamic infiniteScrollingView; 53 | 54 | - (void)addInfiniteScrollingWithActionHandler:(void (^)(void))actionHandler { 55 | 56 | if(!self.infiniteScrollingView) { 57 | SVInfiniteScrollingView *view = [[SVInfiniteScrollingView alloc] initWithFrame:CGRectMake(0, self.contentSize.height, self.bounds.size.width, SVInfiniteScrollingViewHeight)]; 58 | view.infiniteScrollingHandler = actionHandler; 59 | view.scrollView = self; 60 | [self addSubview:view]; 61 | 62 | view.originalBottomInset = self.contentInset.bottom; 63 | self.infiniteScrollingView = view; 64 | self.showsInfiniteScrolling = YES; 65 | } 66 | } 67 | 68 | - (void)triggerInfiniteScrolling { 69 | self.infiniteScrollingView.state = SVInfiniteScrollingStateTriggered; 70 | [self.infiniteScrollingView startAnimating]; 71 | } 72 | 73 | - (void)setInfiniteScrollingView:(SVInfiniteScrollingView *)infiniteScrollingView { 74 | [self willChangeValueForKey:@"UIScrollViewInfiniteScrollingView"]; 75 | objc_setAssociatedObject(self, &UIScrollViewInfiniteScrollingView, 76 | infiniteScrollingView, 77 | OBJC_ASSOCIATION_ASSIGN); 78 | [self didChangeValueForKey:@"UIScrollViewInfiniteScrollingView"]; 79 | } 80 | 81 | - (SVInfiniteScrollingView *)infiniteScrollingView { 82 | return objc_getAssociatedObject(self, &UIScrollViewInfiniteScrollingView); 83 | } 84 | 85 | - (void)setShowsInfiniteScrolling:(BOOL)showsInfiniteScrolling { 86 | self.infiniteScrollingView.hidden = !showsInfiniteScrolling; 87 | 88 | if(!showsInfiniteScrolling) { 89 | if (self.infiniteScrollingView.isObserving) { 90 | [self removeObserver:self.infiniteScrollingView forKeyPath:@"contentOffset"]; 91 | [self removeObserver:self.infiniteScrollingView forKeyPath:@"contentSize"]; 92 | [self.infiniteScrollingView resetScrollViewContentInset]; 93 | self.infiniteScrollingView.isObserving = NO; 94 | } 95 | } 96 | else { 97 | if (!self.infiniteScrollingView.isObserving) { 98 | [self addObserver:self.infiniteScrollingView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 99 | [self addObserver:self.infiniteScrollingView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; 100 | [self.infiniteScrollingView setScrollViewContentInsetForInfiniteScrolling]; 101 | self.infiniteScrollingView.isObserving = YES; 102 | 103 | [self.infiniteScrollingView setNeedsLayout]; 104 | self.infiniteScrollingView.frame = CGRectMake(0, self.contentSize.height, self.infiniteScrollingView.bounds.size.width, SVInfiniteScrollingViewHeight); 105 | } 106 | } 107 | } 108 | 109 | - (BOOL)showsInfiniteScrolling { 110 | return !self.infiniteScrollingView.hidden; 111 | } 112 | 113 | @end 114 | 115 | 116 | #pragma mark - SVInfiniteScrollingView 117 | @implementation SVInfiniteScrollingView 118 | 119 | // public properties 120 | @synthesize infiniteScrollingHandler, activityIndicatorViewStyle; 121 | 122 | @synthesize state = _state; 123 | @synthesize scrollView = _scrollView; 124 | @synthesize activityIndicatorView = _activityIndicatorView; 125 | 126 | 127 | - (id)initWithFrame:(CGRect)frame { 128 | if(self = [super initWithFrame:frame]) { 129 | 130 | // default styling values 131 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 132 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 133 | self.state = SVInfiniteScrollingStateStopped; 134 | self.enabled = YES; 135 | 136 | self.viewForState = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 137 | } 138 | 139 | return self; 140 | } 141 | 142 | - (void)willMoveToSuperview:(UIView *)newSuperview { 143 | if (self.superview && newSuperview == nil) { 144 | UIScrollView *scrollView = (UIScrollView *)self.superview; 145 | if (scrollView.showsInfiniteScrolling) { 146 | if (self.isObserving) { 147 | [scrollView removeObserver:self forKeyPath:@"contentOffset"]; 148 | [scrollView removeObserver:self forKeyPath:@"contentSize"]; 149 | self.isObserving = NO; 150 | } 151 | } 152 | } 153 | } 154 | 155 | - (void)layoutSubviews { 156 | self.activityIndicatorView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 157 | } 158 | 159 | #pragma mark - Scroll View 160 | 161 | - (void)resetScrollViewContentInset { 162 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 163 | currentInsets.bottom = self.originalBottomInset; 164 | [self setScrollViewContentInset:currentInsets]; 165 | } 166 | 167 | - (void)setScrollViewContentInsetForInfiniteScrolling { 168 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 169 | currentInsets.bottom = self.originalBottomInset + SVInfiniteScrollingViewHeight; 170 | [self setScrollViewContentInset:currentInsets]; 171 | } 172 | 173 | - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { 174 | [UIView animateWithDuration:0.3 175 | delay:0 176 | options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState 177 | animations:^{ 178 | self.scrollView.contentInset = contentInset; 179 | } 180 | completion:NULL]; 181 | } 182 | 183 | #pragma mark - Observing 184 | 185 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 186 | if([keyPath isEqualToString:@"contentOffset"]) 187 | [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]]; 188 | else if([keyPath isEqualToString:@"contentSize"]) { 189 | [self layoutSubviews]; 190 | self.frame = CGRectMake(0, self.scrollView.contentSize.height, self.bounds.size.width, SVInfiniteScrollingViewHeight); 191 | } 192 | } 193 | 194 | - (void)scrollViewDidScroll:(CGPoint)contentOffset { 195 | if(self.state != SVInfiniteScrollingStateLoading && self.enabled) { 196 | CGFloat scrollViewContentHeight = self.scrollView.contentSize.height; 197 | CGFloat scrollOffsetThreshold = scrollViewContentHeight-self.scrollView.bounds.size.height; 198 | 199 | if(!self.scrollView.isDragging && self.state == SVInfiniteScrollingStateTriggered) 200 | self.state = SVInfiniteScrollingStateLoading; 201 | else if(contentOffset.y > scrollOffsetThreshold && self.state == SVInfiniteScrollingStateStopped && self.scrollView.isDragging) 202 | self.state = SVInfiniteScrollingStateTriggered; 203 | else if(contentOffset.y < scrollOffsetThreshold && self.state != SVInfiniteScrollingStateStopped) 204 | self.state = SVInfiniteScrollingStateStopped; 205 | } 206 | } 207 | 208 | #pragma mark - Getters 209 | 210 | - (UIActivityIndicatorView *)activityIndicatorView { 211 | if(!_activityIndicatorView) { 212 | _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 213 | _activityIndicatorView.hidesWhenStopped = YES; 214 | [self addSubview:_activityIndicatorView]; 215 | } 216 | return _activityIndicatorView; 217 | } 218 | 219 | - (UIActivityIndicatorViewStyle)activityIndicatorViewStyle { 220 | return self.activityIndicatorView.activityIndicatorViewStyle; 221 | } 222 | 223 | #pragma mark - Setters 224 | 225 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state { 226 | id viewPlaceholder = view; 227 | 228 | if(!viewPlaceholder) 229 | viewPlaceholder = @""; 230 | 231 | if(state == SVInfiniteScrollingStateAll) 232 | [self.viewForState replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[viewPlaceholder, viewPlaceholder, viewPlaceholder]]; 233 | else 234 | [self.viewForState replaceObjectAtIndex:state withObject:viewPlaceholder]; 235 | 236 | self.state = self.state; 237 | } 238 | 239 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)viewStyle { 240 | self.activityIndicatorView.activityIndicatorViewStyle = viewStyle; 241 | } 242 | 243 | #pragma mark - 244 | 245 | - (void)triggerRefresh { 246 | self.state = SVInfiniteScrollingStateTriggered; 247 | self.state = SVInfiniteScrollingStateLoading; 248 | } 249 | 250 | - (void)startAnimating{ 251 | self.state = SVInfiniteScrollingStateLoading; 252 | } 253 | 254 | - (void)stopAnimating { 255 | self.state = SVInfiniteScrollingStateStopped; 256 | } 257 | 258 | - (void)setState:(SVInfiniteScrollingState)newState { 259 | 260 | if(_state == newState) 261 | return; 262 | 263 | SVInfiniteScrollingState previousState = _state; 264 | _state = newState; 265 | 266 | for(id otherView in self.viewForState) { 267 | if([otherView isKindOfClass:[UIView class]]) 268 | [otherView removeFromSuperview]; 269 | } 270 | 271 | id customView = [self.viewForState objectAtIndex:newState]; 272 | BOOL hasCustomView = [customView isKindOfClass:[UIView class]]; 273 | 274 | if(hasCustomView) { 275 | [self addSubview:customView]; 276 | CGRect viewBounds = [customView bounds]; 277 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 278 | [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 279 | } 280 | else { 281 | CGRect viewBounds = [self.activityIndicatorView bounds]; 282 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 283 | [self.activityIndicatorView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 284 | 285 | switch (newState) { 286 | case SVInfiniteScrollingStateStopped: 287 | [self.activityIndicatorView stopAnimating]; 288 | break; 289 | 290 | case SVInfiniteScrollingStateTriggered: 291 | [self.activityIndicatorView startAnimating]; 292 | break; 293 | 294 | case SVInfiniteScrollingStateLoading: 295 | [self.activityIndicatorView startAnimating]; 296 | break; 297 | } 298 | } 299 | 300 | if(previousState == SVInfiniteScrollingStateTriggered && newState == SVInfiniteScrollingStateLoading && self.infiniteScrollingHandler && self.enabled) 301 | self.infiniteScrollingHandler(); 302 | } 303 | 304 | @end 305 | -------------------------------------------------------------------------------- /SVPullToRefresh/UIScrollView+SVPullToRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVPullToRefresh.h 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import 12 | 13 | 14 | @class SVPullToRefreshView; 15 | 16 | @interface UIScrollView (SVPullToRefresh) 17 | 18 | typedef NS_ENUM(NSUInteger, SVPullToRefreshPosition) { 19 | SVPullToRefreshPositionTop = 0, 20 | SVPullToRefreshPositionBottom = 1, 21 | SVPullToRefreshPositionLeft = 2, 22 | SVPullToRefreshPositionRight = 3 23 | }; 24 | 25 | - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler; 26 | - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler position:(SVPullToRefreshPosition)position; 27 | - (void)triggerPullToRefresh; //only apply for the first SVPullToRefreshView in the pullToRefreshViews array 28 | - (void)triggerPullToRefreshAtPosition:(SVPullToRefreshPosition)position; 29 | 30 | @property (nonatomic, strong, readonly) SVPullToRefreshView *pullToRefreshView; //get the first SVPullToRefreshView in the pullToRefreshViews array 31 | @property (nonatomic, strong, readonly) NSArray *pullToRefreshViews; 32 | @property (nonatomic, assign) BOOL showsPullToRefresh; 33 | 34 | //getter 35 | - (SVPullToRefreshView*)pullToRefreshViewAtPosition:(SVPullToRefreshPosition)position; 36 | 37 | @end 38 | 39 | 40 | typedef NS_ENUM(NSUInteger, SVPullToRefreshState) { 41 | SVPullToRefreshStateStopped = 0, 42 | SVPullToRefreshStateTriggered, 43 | SVPullToRefreshStateLoading, 44 | SVPullToRefreshStateAll = 10 45 | }; 46 | 47 | @interface SVPullToRefreshView : UIView 48 | 49 | @property (nonatomic, strong) UIColor *arrowColor; 50 | @property (nonatomic, strong) UIColor *textColor; 51 | @property (nonatomic, strong, readonly) UILabel *titleLabel; 52 | @property (nonatomic, strong, readonly) UILabel *subtitleLabel; 53 | @property (nonatomic, strong, readwrite) UIColor *activityIndicatorViewColor NS_AVAILABLE_IOS(5_0); 54 | @property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 55 | 56 | @property (nonatomic, readonly) SVPullToRefreshState state; 57 | @property (nonatomic, readonly) SVPullToRefreshPosition position; 58 | 59 | - (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state; 60 | - (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state; 61 | - (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state; 62 | 63 | - (void)startAnimating; 64 | - (void)stopAnimating; 65 | 66 | + (CGRect)rectForRefreshViewAtPosition:(SVPullToRefreshPosition)position scrollView:(UIScrollView*)scrollView; 67 | 68 | // deprecated; use setSubtitle:forState: instead 69 | @property (nonatomic, strong, readonly) UILabel *dateLabel DEPRECATED_ATTRIBUTE; 70 | @property (nonatomic, strong) NSDate *lastUpdatedDate DEPRECATED_ATTRIBUTE; 71 | @property (nonatomic, strong) NSDateFormatter *dateFormatter DEPRECATED_ATTRIBUTE; 72 | 73 | // deprecated; use [self.scrollView triggerPullToRefresh] instead 74 | - (void)triggerRefresh DEPRECATED_ATTRIBUTE; 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /SVPullToRefresh/UIScrollView+SVPullToRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVPullToRefresh.m 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import "UIScrollView+SVPullToRefresh.h" 12 | #import "NSString+HTPullToRefresh.h" 13 | 14 | //fequal() and fequalzro() from http://stackoverflow.com/a/1614761/184130 15 | #define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON) 16 | #define fequalzero(a) (fabs(a) < FLT_EPSILON) 17 | 18 | static CGFloat const SVPullToRefreshViewHeight = 60; 19 | static CGFloat const SVPullToRefreshViewWidth = 60; 20 | 21 | @interface SVPullToRefreshArrow : UIView 22 | 23 | @property (nonatomic, strong) UIColor *arrowColor; 24 | 25 | @end 26 | 27 | 28 | @interface SVPullToRefreshView () 29 | 30 | @property (nonatomic, copy) void (^pullToRefreshActionHandler)(void); 31 | 32 | @property (nonatomic, strong) SVPullToRefreshArrow *arrow; 33 | @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; 34 | @property (nonatomic, strong, readwrite) UILabel *titleLabel; 35 | @property (nonatomic, strong, readwrite) UILabel *subtitleLabel; 36 | @property (nonatomic, readwrite) SVPullToRefreshState state; 37 | @property (nonatomic, readwrite) SVPullToRefreshPosition position; 38 | 39 | @property (nonatomic, strong) NSMutableArray *titles; 40 | @property (nonatomic, strong) NSMutableArray *subtitles; 41 | @property (nonatomic, strong) NSMutableArray *viewForState; 42 | 43 | @property (nonatomic, weak) UIScrollView *scrollView; 44 | @property (nonatomic, readwrite) CGFloat originalTopInset; 45 | @property (nonatomic, readwrite) CGFloat originalBottomInset; 46 | @property (nonatomic, readwrite) CGFloat originalLeftInset; 47 | @property (nonatomic, readwrite) CGFloat originalRightInset; 48 | 49 | @property (nonatomic, assign) BOOL wasTriggeredByUser; 50 | @property (nonatomic, assign) BOOL showsPullToRefresh; 51 | @property (nonatomic, assign) BOOL showsDateLabel; 52 | @property(nonatomic, assign) BOOL isObserving; 53 | 54 | - (void)resetScrollViewContentInset; 55 | - (void)setScrollViewContentInsetForLoading; 56 | - (void)setScrollViewContentInset:(UIEdgeInsets)insets; 57 | - (void)rotateArrow:(float)degrees hide:(BOOL)hide; 58 | 59 | @end 60 | 61 | #pragma mark - UIScrollView (SVPullToRefresh) 62 | #import 63 | 64 | static char UIScrollViewInternalPullToRefreshViews; 65 | 66 | @interface UIScrollView () 67 | @property (nonatomic, strong) NSMutableArray *internalPullToRefreshViews; 68 | @end 69 | 70 | @implementation UIScrollView (SVPullToRefresh) 71 | 72 | @dynamic showsPullToRefresh; 73 | 74 | - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler position:(SVPullToRefreshPosition)position { 75 | SVPullToRefreshView *view = [self pullToRefreshViewAtPosition:position]; 76 | if(!view) { 77 | CGRect rect = [SVPullToRefreshView rectForRefreshViewAtPosition:position scrollView:self]; 78 | view = [[SVPullToRefreshView alloc] initWithFrame:rect]; 79 | view.scrollView = self; 80 | [self addSubview:view]; 81 | 82 | view.position = position; 83 | view.originalTopInset = self.contentInset.top; 84 | view.originalBottomInset = self.contentInset.bottom; 85 | view.originalLeftInset = self.contentInset.left; 86 | view.originalRightInset = self.contentInset.right; 87 | 88 | [self.internalPullToRefreshViews addObject:view]; 89 | } 90 | view.pullToRefreshActionHandler = actionHandler; 91 | self.showsPullToRefresh = YES; 92 | } 93 | 94 | - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler { 95 | [self addPullToRefreshWithActionHandler:actionHandler position:SVPullToRefreshPositionTop]; 96 | } 97 | 98 | - (void)triggerPullToRefresh { 99 | SVPullToRefreshView* refreshView = self.pullToRefreshView; 100 | refreshView.state = SVPullToRefreshStateTriggered; 101 | [refreshView startAnimating]; 102 | } 103 | 104 | - (void)triggerPullToRefreshAtPosition:(SVPullToRefreshPosition)position { 105 | SVPullToRefreshView* refreshView = [self pullToRefreshViewAtPosition:position]; 106 | if(!refreshView) return; 107 | 108 | refreshView.state = SVPullToRefreshStateTriggered; 109 | [refreshView startAnimating]; 110 | } 111 | 112 | - (void)setShowsPullToRefresh:(BOOL)showsPullToRefresh { 113 | for(SVPullToRefreshView* pullToRefreshView in self.internalPullToRefreshViews) { 114 | pullToRefreshView.hidden = !showsPullToRefresh; 115 | 116 | if(!showsPullToRefresh) { 117 | if (pullToRefreshView.isObserving) { 118 | [self removeObserver:pullToRefreshView forKeyPath:@"contentOffset"]; 119 | [self removeObserver:pullToRefreshView forKeyPath:@"contentSize"]; 120 | [self removeObserver:pullToRefreshView forKeyPath:@"frame"]; 121 | [pullToRefreshView resetScrollViewContentInset]; 122 | pullToRefreshView.isObserving = NO; 123 | } 124 | } 125 | else { 126 | if (!pullToRefreshView.isObserving) { 127 | [self addObserver:pullToRefreshView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 128 | [self addObserver:pullToRefreshView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; 129 | [self addObserver:pullToRefreshView forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil]; 130 | pullToRefreshView.isObserving = YES; 131 | 132 | pullToRefreshView.frame = [SVPullToRefreshView rectForRefreshViewAtPosition:pullToRefreshView.position scrollView:self]; 133 | } 134 | } 135 | } 136 | } 137 | 138 | - (SVPullToRefreshView *)pullToRefreshView { 139 | return self.internalPullToRefreshViews.firstObject; 140 | } 141 | 142 | - (SVPullToRefreshView *)pullToRefreshViewAtPosition:(SVPullToRefreshPosition)position { 143 | for(SVPullToRefreshView* view in self.internalPullToRefreshViews) { 144 | if(view.position == position) return view; 145 | } 146 | return nil; 147 | } 148 | 149 | - (BOOL)showsPullToRefresh { 150 | for(SVPullToRefreshView* view in self.internalPullToRefreshViews) { 151 | if(!view.hidden) return YES; 152 | } 153 | return NO; 154 | } 155 | 156 | - (NSArray *)pullToRefreshViews { 157 | return [NSArray arrayWithArray:self.internalPullToRefreshViews]; 158 | } 159 | 160 | - (void)setInternalPullToRefreshViews:(NSMutableArray *)internalPullToRefreshViews { 161 | objc_setAssociatedObject(self, &UIScrollViewInternalPullToRefreshViews, internalPullToRefreshViews, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 162 | } 163 | - (NSMutableArray *)internalPullToRefreshViews { 164 | NSMutableArray *refreshViews = objc_getAssociatedObject(self, &UIScrollViewInternalPullToRefreshViews); 165 | if(refreshViews) { 166 | return refreshViews; 167 | } else { 168 | refreshViews = [NSMutableArray array]; 169 | self.internalPullToRefreshViews = refreshViews; 170 | return refreshViews; 171 | } 172 | } 173 | 174 | @end 175 | 176 | #pragma mark - SVPullToRefresh 177 | @implementation SVPullToRefreshView 178 | 179 | // public properties 180 | @synthesize pullToRefreshActionHandler, arrowColor, textColor, activityIndicatorViewColor, activityIndicatorViewStyle, lastUpdatedDate, dateFormatter; 181 | 182 | @synthesize state = _state; 183 | @synthesize scrollView = _scrollView; 184 | @synthesize showsPullToRefresh = _showsPullToRefresh; 185 | @synthesize arrow = _arrow; 186 | @synthesize activityIndicatorView = _activityIndicatorView; 187 | 188 | @synthesize titleLabel = _titleLabel; 189 | @synthesize dateLabel = _dateLabel; 190 | 191 | 192 | - (id)initWithFrame:(CGRect)frame { 193 | if(self = [super initWithFrame:frame]) { 194 | 195 | // default styling values 196 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 197 | self.textColor = [UIColor darkGrayColor]; 198 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 199 | self.state = SVPullToRefreshStateStopped; 200 | self.showsDateLabel = NO; 201 | 202 | self.titles = [NSMutableArray arrayWithObjects:NSLocalizedString(@"Pull to refresh...",), 203 | NSLocalizedString(@"Release to refresh...",), 204 | NSLocalizedString(@"Loading...",), 205 | nil]; 206 | 207 | self.subtitles = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 208 | self.viewForState = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 209 | self.wasTriggeredByUser = YES; 210 | } 211 | 212 | return self; 213 | } 214 | 215 | + (CGRect)rectForRefreshViewAtPosition:(SVPullToRefreshPosition)position scrollView:(UIScrollView*)scrollView { 216 | CGFloat yOrigin = 0; 217 | CGFloat xOrigin = 0; 218 | CGFloat viewWidth = scrollView.bounds.size.width; 219 | CGFloat viewHeight = SVPullToRefreshViewHeight; 220 | switch (position) { 221 | case SVPullToRefreshPositionTop: 222 | yOrigin = -SVPullToRefreshViewHeight; 223 | break; 224 | case SVPullToRefreshPositionBottom: 225 | yOrigin = MAX(scrollView.contentSize.height, scrollView.bounds.size.height); 226 | break; 227 | case SVPullToRefreshPositionLeft: 228 | xOrigin = -SVPullToRefreshViewWidth; 229 | viewWidth = SVPullToRefreshViewWidth; 230 | viewHeight = scrollView.bounds.size.height; 231 | break; 232 | case SVPullToRefreshPositionRight: 233 | xOrigin = MAX(scrollView.contentSize.width, scrollView.bounds.size.width); 234 | viewWidth = SVPullToRefreshViewWidth; 235 | viewHeight = scrollView.bounds.size.height; 236 | break; 237 | 238 | default: 239 | break; 240 | } 241 | return CGRectMake(xOrigin, yOrigin, viewWidth, viewHeight); 242 | } 243 | 244 | - (void)willMoveToSuperview:(UIView *)newSuperview { 245 | if (self.superview && newSuperview == nil) { 246 | //use self.superview, not self.scrollView. Why self.scrollView == nil here? 247 | UIScrollView *scrollView = (UIScrollView *)self.superview; 248 | if (scrollView.showsPullToRefresh) { 249 | if (self.isObserving) { 250 | //If enter this branch, it is the moment just before "SVPullToRefreshView's dealloc", so remove observer here 251 | [scrollView removeObserver:self forKeyPath:@"contentOffset"]; 252 | [scrollView removeObserver:self forKeyPath:@"contentSize"]; 253 | [scrollView removeObserver:self forKeyPath:@"frame"]; 254 | self.isObserving = NO; 255 | } 256 | } 257 | } 258 | } 259 | 260 | - (void)layoutSubviews { 261 | 262 | for(id otherView in self.viewForState) { 263 | if([otherView isKindOfClass:[UIView class]]) 264 | [otherView removeFromSuperview]; 265 | } 266 | 267 | id customView = [self.viewForState objectAtIndex:self.state]; 268 | BOOL hasCustomView = [customView isKindOfClass:[UIView class]]; 269 | 270 | self.titleLabel.hidden = hasCustomView; 271 | self.subtitleLabel.hidden = hasCustomView; 272 | self.arrow.hidden = hasCustomView; 273 | 274 | if(hasCustomView) { 275 | [self addSubview:customView]; 276 | CGRect viewBounds = [customView bounds]; 277 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 278 | [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 279 | } 280 | else { 281 | switch (self.state) { 282 | case SVPullToRefreshStateAll: 283 | case SVPullToRefreshStateStopped: 284 | self.arrow.alpha = 1; 285 | [self.activityIndicatorView stopAnimating]; 286 | switch (self.position) { 287 | case SVPullToRefreshPositionTop: 288 | [self rotateArrow:0 hide:NO]; 289 | break; 290 | case SVPullToRefreshPositionBottom: 291 | [self rotateArrow:(float)M_PI hide:NO]; 292 | break; 293 | case SVPullToRefreshPositionLeft: 294 | [self rotateArrow:(float)-M_PI_2 hide:NO]; 295 | break; 296 | case SVPullToRefreshPositionRight: 297 | [self rotateArrow:(float)M_PI_2 hide:NO]; 298 | break; 299 | } 300 | break; 301 | 302 | case SVPullToRefreshStateTriggered: 303 | switch (self.position) { 304 | case SVPullToRefreshPositionTop: 305 | [self rotateArrow:(float)M_PI hide:NO]; 306 | break; 307 | case SVPullToRefreshPositionBottom: 308 | [self rotateArrow:0 hide:NO]; 309 | break; 310 | case SVPullToRefreshPositionLeft: 311 | [self rotateArrow:(float)M_PI_2 hide:NO]; 312 | break; 313 | case SVPullToRefreshPositionRight: 314 | [self rotateArrow:(float)-M_PI_2 hide:NO]; 315 | break; 316 | } 317 | break; 318 | 319 | case SVPullToRefreshStateLoading: 320 | [self.activityIndicatorView startAnimating]; 321 | switch (self.position) { 322 | case SVPullToRefreshPositionTop: 323 | [self rotateArrow:0 hide:YES]; 324 | break; 325 | case SVPullToRefreshPositionBottom: 326 | [self rotateArrow:(float)M_PI hide:YES]; 327 | break; 328 | case SVPullToRefreshPositionLeft: 329 | [self rotateArrow:(float)-M_PI_2 hide:YES]; 330 | break; 331 | case SVPullToRefreshPositionRight: 332 | [self rotateArrow:(float)M_PI_2 hide:YES]; 333 | break; 334 | } 335 | break; 336 | } 337 | if(self.position == SVPullToRefreshPositionTop || self.position == SVPullToRefreshPositionBottom) { 338 | [self layoutForVertical]; 339 | } else { 340 | [self layoutForHorizontal]; 341 | } 342 | } 343 | } 344 | 345 | - (void)layoutForVertical { 346 | CGFloat leftViewWidth = MAX(self.arrow.bounds.size.width,self.activityIndicatorView.bounds.size.width); 347 | 348 | CGFloat margin = 10; 349 | CGFloat marginY = 2; 350 | CGFloat labelMaxWidth = self.bounds.size.width - margin - leftViewWidth; 351 | 352 | self.titleLabel.text = [self.titles objectAtIndex:self.state]; 353 | 354 | NSString *subtitle = [self.subtitles objectAtIndex:self.state]; 355 | self.subtitleLabel.text = subtitle.length > 0 ? subtitle : nil; 356 | 357 | CGSize titleSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font constraintToSize:CGSizeMake(labelMaxWidth,self.titleLabel.font.lineHeight)]; 358 | 359 | CGSize subtitleSize = [self.subtitleLabel.text sizeWithFont:self.subtitleLabel.font constraintToSize:CGSizeMake(labelMaxWidth,self.subtitleLabel.font.lineHeight)]; 360 | 361 | CGFloat maxLabelWidth = MAX(titleSize.width,subtitleSize.width); 362 | 363 | CGFloat totalMaxWidth; 364 | if (maxLabelWidth) { 365 | totalMaxWidth = leftViewWidth + margin + maxLabelWidth; 366 | } else { 367 | totalMaxWidth = leftViewWidth + maxLabelWidth; 368 | } 369 | 370 | CGFloat labelX = (self.bounds.size.width / 2) - (totalMaxWidth / 2) + leftViewWidth + margin; 371 | 372 | if(subtitleSize.height > 0){ 373 | CGFloat totalHeight = titleSize.height + subtitleSize.height + marginY; 374 | CGFloat minY = (self.bounds.size.height / 2) - (totalHeight / 2); 375 | 376 | CGFloat titleY = minY; 377 | self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY, titleSize.width, titleSize.height)); 378 | self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY + titleSize.height + marginY, subtitleSize.width, subtitleSize.height)); 379 | }else{ 380 | CGFloat totalHeight = titleSize.height; 381 | CGFloat minY = (self.bounds.size.height / 2) - (totalHeight / 2); 382 | 383 | CGFloat titleY = minY; 384 | self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY, titleSize.width, titleSize.height)); 385 | self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY + titleSize.height + marginY, subtitleSize.width, subtitleSize.height)); 386 | } 387 | 388 | CGFloat arrowX = (self.bounds.size.width / 2) - (totalMaxWidth / 2) + (leftViewWidth - self.arrow.bounds.size.width) / 2; 389 | self.arrow.frame = CGRectMake(arrowX, 390 | (self.bounds.size.height / 2) - (self.arrow.bounds.size.height / 2), 391 | self.arrow.bounds.size.width, 392 | self.arrow.bounds.size.height); 393 | self.activityIndicatorView.center = self.arrow.center; 394 | } 395 | 396 | - (void)layoutForHorizontal { 397 | CGFloat imageHeight = MAX(self.arrow.bounds.size.width,self.activityIndicatorView.bounds.size.width); 398 | 399 | CGFloat distanceBetweenImageAndLabel = 10; 400 | CGFloat marginY = 2; 401 | CGFloat labelMaxWidth = self.bounds.size.width - 6; 402 | 403 | self.titleLabel.text = [self.titles objectAtIndex:self.state]; 404 | 405 | NSString *subtitle = [self.subtitles objectAtIndex:self.state]; 406 | self.subtitleLabel.text = subtitle.length > 0 ? subtitle : nil; 407 | 408 | CGSize titleSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font constraintToSize:CGSizeMake(labelMaxWidth,CGFLOAT_MAX)]; 409 | 410 | CGSize subtitleSize = [self.subtitleLabel.text sizeWithFont:self.subtitleLabel.font constraintToSize:CGSizeMake(labelMaxWidth,CGFLOAT_MAX)]; 411 | 412 | CGFloat arrowY = self.bounds.size.height / 2 - imageHeight - distanceBetweenImageAndLabel; 413 | self.arrow.frame = CGRectMake((self.bounds.size.width / 2) - (self.arrow.bounds.size.height / 2), 414 | arrowY, 415 | self.arrow.bounds.size.height, 416 | self.arrow.bounds.size.width); 417 | 418 | CGFloat labelY = CGRectGetMaxY(self.arrow.frame) + distanceBetweenImageAndLabel; 419 | CGFloat labelX = (self.frame.size.width - titleSize.width) / 2; 420 | if(subtitleSize.height > 0){ 421 | self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, labelY, labelMaxWidth, titleSize.height)); 422 | self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, labelY + titleSize.height + marginY, labelMaxWidth, subtitleSize.height)); 423 | }else{ 424 | 425 | self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, labelY, labelMaxWidth, titleSize.height)); 426 | self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, labelY + titleSize.height + marginY, labelMaxWidth, subtitleSize.height)); 427 | } 428 | 429 | self.activityIndicatorView.center = self.arrow.center; 430 | } 431 | 432 | #pragma mark - Scroll View 433 | 434 | - (void)resetScrollViewContentInset { 435 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 436 | switch (self.position) { 437 | case SVPullToRefreshPositionTop: 438 | currentInsets.top = self.originalTopInset; 439 | break; 440 | case SVPullToRefreshPositionBottom: 441 | currentInsets.bottom = self.originalBottomInset; 442 | currentInsets.top = self.originalTopInset; 443 | break; 444 | case SVPullToRefreshPositionLeft: 445 | currentInsets.left = self.originalLeftInset; 446 | break; 447 | case SVPullToRefreshPositionRight: 448 | currentInsets.right = self.originalRightInset; 449 | break; 450 | } 451 | [self setScrollViewContentInset:currentInsets]; 452 | } 453 | 454 | - (void)setScrollViewContentInsetForLoading { 455 | CGFloat offsetY = MAX(self.scrollView.contentOffset.y * -1, 0); 456 | CGFloat offsetX = MAX(self.scrollView.contentOffset.x * -1, 0); 457 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 458 | switch (self.position) { 459 | case SVPullToRefreshPositionTop: 460 | currentInsets.top = MIN(offsetY, self.originalTopInset + self.bounds.size.height); 461 | break; 462 | case SVPullToRefreshPositionBottom: 463 | currentInsets.bottom = MIN(offsetY, self.originalBottomInset + self.bounds.size.height); 464 | break; 465 | case SVPullToRefreshPositionLeft: 466 | currentInsets.left = MIN(offsetX, self.originalLeftInset + self.bounds.size.width); 467 | break; 468 | case SVPullToRefreshPositionRight: 469 | currentInsets.right = MIN(offsetX, self.originalRightInset + self.bounds.size.width); 470 | break; 471 | } 472 | [self setScrollViewContentInset:currentInsets]; 473 | } 474 | 475 | - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { 476 | [UIView animateWithDuration:0.3 477 | delay:0 478 | options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState 479 | animations:^{ 480 | self.scrollView.contentInset = contentInset; 481 | } 482 | completion:NULL]; 483 | } 484 | 485 | #pragma mark - Observing 486 | 487 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 488 | if([keyPath isEqualToString:@"contentOffset"]) 489 | [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]]; 490 | else if([keyPath isEqualToString:@"contentSize"]) { 491 | [self layoutSubviews]; 492 | 493 | self.frame = [SVPullToRefreshView rectForRefreshViewAtPosition:self.position scrollView:self.scrollView]; 494 | } 495 | else if([keyPath isEqualToString:@"frame"]) 496 | [self layoutSubviews]; 497 | 498 | } 499 | 500 | - (void)scrollViewDidScroll:(CGPoint)contentOffset { 501 | if(self.state != SVPullToRefreshStateLoading) { 502 | CGFloat scrollOffsetThreshold = 0; 503 | switch (self.position) { 504 | case SVPullToRefreshPositionTop: 505 | scrollOffsetThreshold = self.frame.origin.y - self.originalTopInset; 506 | break; 507 | case SVPullToRefreshPositionBottom: 508 | scrollOffsetThreshold = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height, 0.0f) + self.bounds.size.height + self.originalBottomInset; 509 | break; 510 | case SVPullToRefreshPositionLeft: 511 | scrollOffsetThreshold = self.frame.origin.x - self.originalLeftInset; 512 | break; 513 | case SVPullToRefreshPositionRight: 514 | scrollOffsetThreshold = MAX(self.scrollView.contentSize.width - self.scrollView.bounds.size.width, 0) + self.bounds.size.width + self.originalRightInset; 515 | } 516 | 517 | if(!self.scrollView.isDragging && self.state == SVPullToRefreshStateTriggered) 518 | self.state = SVPullToRefreshStateLoading; 519 | else if(contentOffset.y < scrollOffsetThreshold && self.scrollView.isDragging && self.state == SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionTop) 520 | self.state = SVPullToRefreshStateTriggered; 521 | else if(contentOffset.y >= scrollOffsetThreshold && self.state != SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionTop) 522 | self.state = SVPullToRefreshStateStopped; 523 | else if(contentOffset.y > scrollOffsetThreshold && self.scrollView.isDragging && self.state == SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionBottom) 524 | self.state = SVPullToRefreshStateTriggered; 525 | else if(contentOffset.y <= scrollOffsetThreshold && self.state != SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionBottom) 526 | self.state = SVPullToRefreshStateStopped; 527 | else if(contentOffset.x < scrollOffsetThreshold && self.scrollView.isDragging && self.state == SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionLeft) 528 | self.state = SVPullToRefreshStateTriggered; 529 | else if(contentOffset.x >= scrollOffsetThreshold && self.state != SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionLeft) 530 | self.state = SVPullToRefreshStateStopped; 531 | else if(contentOffset.x > scrollOffsetThreshold && self.scrollView.isDragging && self.state == SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionRight) 532 | self.state = SVPullToRefreshStateTriggered; 533 | else if(contentOffset.x <= scrollOffsetThreshold && self.state != SVPullToRefreshStateStopped && self.position == SVPullToRefreshPositionRight) 534 | self.state = SVPullToRefreshStateStopped; 535 | } else { 536 | CGFloat offset; 537 | UIEdgeInsets contentInset; 538 | switch (self.position) { 539 | case SVPullToRefreshPositionTop: 540 | offset = MAX(self.scrollView.contentOffset.y * -1, 0.0f); 541 | offset = MIN(offset, self.originalTopInset + self.bounds.size.height); 542 | contentInset = self.scrollView.contentInset; 543 | self.scrollView.contentInset = UIEdgeInsetsMake(offset, contentInset.left, contentInset.bottom, contentInset.right); 544 | break; 545 | case SVPullToRefreshPositionBottom: 546 | if (self.scrollView.contentSize.height >= self.scrollView.bounds.size.height) { 547 | offset = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.bounds.size.height, 0.0f); 548 | offset = MIN(offset, self.originalBottomInset + self.bounds.size.height); 549 | contentInset = self.scrollView.contentInset; 550 | self.scrollView.contentInset = UIEdgeInsetsMake(contentInset.top, contentInset.left, offset, contentInset.right); 551 | } else if (self.wasTriggeredByUser) { 552 | offset = MIN(self.bounds.size.height, self.originalBottomInset + self.bounds.size.height); 553 | contentInset = self.scrollView.contentInset; 554 | self.scrollView.contentInset = UIEdgeInsetsMake(-offset, contentInset.left, contentInset.bottom, contentInset.right); 555 | } 556 | break; 557 | case SVPullToRefreshPositionLeft: 558 | offset = MAX(self.scrollView.contentOffset.x * -1, 0); 559 | offset = MIN(offset, self.originalLeftInset + self.bounds.size.width); 560 | contentInset = self.scrollView.contentInset; 561 | self.scrollView.contentInset = UIEdgeInsetsMake(contentInset.top, offset, contentInset.bottom, contentInset.right); 562 | break; 563 | 564 | case SVPullToRefreshPositionRight: 565 | if(self.scrollView.contentSize.width >= self.scrollView.bounds.size.width) { 566 | offset = MAX(self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.bounds.size.width, 0.0f); 567 | offset = MIN(offset, self.originalRightInset + self.bounds.size.width); 568 | contentInset = self.scrollView.contentInset; 569 | self.scrollView.contentInset = UIEdgeInsetsMake(contentInset.top, contentInset.left, contentInset.bottom, offset); 570 | } else { 571 | offset = MIN(self.bounds.size.width, self.originalRightInset + self.bounds.size.width); 572 | contentInset = self.scrollView.contentInset; 573 | self.scrollView.contentInset = UIEdgeInsetsMake(contentInset.top, -offset, contentInset.bottom, contentInset.right); 574 | } 575 | break; 576 | } 577 | } 578 | } 579 | 580 | #pragma mark - Getters 581 | 582 | - (SVPullToRefreshArrow *)arrow { 583 | if(!_arrow) { 584 | _arrow = [[SVPullToRefreshArrow alloc]initWithFrame:CGRectMake(0, self.bounds.size.height-54, 22, 48)]; 585 | _arrow.backgroundColor = [UIColor clearColor]; 586 | [self addSubview:_arrow]; 587 | } 588 | return _arrow; 589 | } 590 | 591 | - (UIActivityIndicatorView *)activityIndicatorView { 592 | if(!_activityIndicatorView) { 593 | _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 594 | _activityIndicatorView.hidesWhenStopped = YES; 595 | [self addSubview:_activityIndicatorView]; 596 | } 597 | return _activityIndicatorView; 598 | } 599 | 600 | - (UILabel *)titleLabel { 601 | if(!_titleLabel) { 602 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 210, 20)]; 603 | _titleLabel.text = NSLocalizedString(@"Pull to refresh...",); 604 | _titleLabel.font = [UIFont boldSystemFontOfSize:14]; 605 | _titleLabel.backgroundColor = [UIColor clearColor]; 606 | _titleLabel.textColor = textColor; 607 | [self addSubview:_titleLabel]; 608 | } 609 | return _titleLabel; 610 | } 611 | 612 | - (UILabel *)subtitleLabel { 613 | if(!_subtitleLabel) { 614 | _subtitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 210, 20)]; 615 | _subtitleLabel.font = [UIFont systemFontOfSize:12]; 616 | _subtitleLabel.backgroundColor = [UIColor clearColor]; 617 | _subtitleLabel.textColor = textColor; 618 | [self addSubview:_subtitleLabel]; 619 | } 620 | return _subtitleLabel; 621 | } 622 | 623 | - (UILabel *)dateLabel { 624 | return self.showsDateLabel ? self.subtitleLabel : nil; 625 | } 626 | 627 | - (NSDateFormatter *)dateFormatter { 628 | if(!dateFormatter) { 629 | dateFormatter = [[NSDateFormatter alloc] init]; 630 | [dateFormatter setDateStyle:NSDateFormatterShortStyle]; 631 | [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; 632 | dateFormatter.locale = [NSLocale currentLocale]; 633 | } 634 | return dateFormatter; 635 | } 636 | 637 | - (UIColor *)arrowColor { 638 | return self.arrow.arrowColor; // pass through 639 | } 640 | 641 | - (UIColor *)textColor { 642 | return self.titleLabel.textColor; 643 | } 644 | 645 | - (UIColor *)activityIndicatorViewColor { 646 | return self.activityIndicatorView.color; 647 | } 648 | 649 | - (UIActivityIndicatorViewStyle)activityIndicatorViewStyle { 650 | return self.activityIndicatorView.activityIndicatorViewStyle; 651 | } 652 | 653 | #pragma mark - Setters 654 | 655 | - (void)setArrowColor:(UIColor *)newArrowColor { 656 | self.arrow.arrowColor = newArrowColor; // pass through 657 | [self.arrow setNeedsDisplay]; 658 | } 659 | 660 | - (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state { 661 | if(!title) 662 | title = @""; 663 | 664 | if(state == SVPullToRefreshStateAll) 665 | [self.titles replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[title, title, title]]; 666 | else 667 | [self.titles replaceObjectAtIndex:state withObject:title]; 668 | 669 | [self setNeedsLayout]; 670 | } 671 | 672 | - (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state { 673 | if(!subtitle) 674 | subtitle = @""; 675 | 676 | if(state == SVPullToRefreshStateAll) 677 | [self.subtitles replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[subtitle, subtitle, subtitle]]; 678 | else 679 | [self.subtitles replaceObjectAtIndex:state withObject:subtitle]; 680 | 681 | [self setNeedsLayout]; 682 | } 683 | 684 | - (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state { 685 | id viewPlaceholder = view; 686 | 687 | if(!viewPlaceholder) 688 | viewPlaceholder = @""; 689 | 690 | if(state == SVPullToRefreshStateAll) 691 | [self.viewForState replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[viewPlaceholder, viewPlaceholder, viewPlaceholder]]; 692 | else 693 | [self.viewForState replaceObjectAtIndex:state withObject:viewPlaceholder]; 694 | 695 | [self setNeedsLayout]; 696 | } 697 | 698 | - (void)setTextColor:(UIColor *)newTextColor { 699 | textColor = newTextColor; 700 | self.titleLabel.textColor = newTextColor; 701 | self.subtitleLabel.textColor = newTextColor; 702 | } 703 | 704 | - (void)setActivityIndicatorViewColor:(UIColor *)color { 705 | self.activityIndicatorView.color = color; 706 | } 707 | 708 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)viewStyle { 709 | self.activityIndicatorView.activityIndicatorViewStyle = viewStyle; 710 | } 711 | 712 | - (void)setLastUpdatedDate:(NSDate *)newLastUpdatedDate { 713 | self.showsDateLabel = YES; 714 | self.dateLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Last Updated: %@",), newLastUpdatedDate?[self.dateFormatter stringFromDate:newLastUpdatedDate]:NSLocalizedString(@"Never",)]; 715 | } 716 | 717 | - (void)setDateFormatter:(NSDateFormatter *)newDateFormatter { 718 | dateFormatter = newDateFormatter; 719 | self.dateLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Last Updated: %@",), self.lastUpdatedDate?[newDateFormatter stringFromDate:self.lastUpdatedDate]:NSLocalizedString(@"Never",)]; 720 | } 721 | 722 | #pragma mark - 723 | 724 | - (void)triggerRefresh { 725 | [self.scrollView triggerPullToRefreshAtPosition:SVPullToRefreshPositionTop]; 726 | } 727 | 728 | - (void)startAnimating{ 729 | switch (self.position) { 730 | case SVPullToRefreshPositionTop: 731 | 732 | if(fequalzero(self.scrollView.contentOffset.y)) { 733 | [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.frame.size.height) animated:YES]; 734 | self.wasTriggeredByUser = NO; 735 | } 736 | else 737 | self.wasTriggeredByUser = YES; 738 | 739 | break; 740 | case SVPullToRefreshPositionBottom: 741 | 742 | if((fequalzero(self.scrollView.contentOffset.y) && self.scrollView.contentSize.height < self.scrollView.bounds.size.height) 743 | || fequal(self.scrollView.contentOffset.y, self.scrollView.contentSize.height - self.scrollView.bounds.size.height)) { 744 | [self.scrollView setContentOffset:(CGPoint){.y = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height, 0.0f) + self.frame.size.height} animated:YES]; 745 | self.wasTriggeredByUser = NO; 746 | } 747 | else 748 | self.wasTriggeredByUser = YES; 749 | 750 | break; 751 | case SVPullToRefreshPositionLeft: 752 | if(fequalzero(self.scrollView.contentOffset.x)) { 753 | [self.scrollView setContentOffset:CGPointMake(-self.frame.size.width, self.scrollView.contentOffset.x) animated:YES]; 754 | self.wasTriggeredByUser = NO; 755 | } 756 | else 757 | self.wasTriggeredByUser = YES; 758 | break; 759 | case SVPullToRefreshPositionRight: 760 | if((fequalzero(self.scrollView.contentOffset.x) && self.scrollView.contentSize.width < self.scrollView.bounds.size.width) 761 | || fequal(self.scrollView.contentOffset.x, self.scrollView.contentSize.width - self.scrollView.bounds.size.width)) { 762 | [self.scrollView setContentOffset:(CGPoint){.x = MAX(self.scrollView.contentSize.width - self.scrollView.bounds.size.width, 0.0f) + self.frame.size.width} animated:YES]; 763 | self.wasTriggeredByUser = NO; 764 | } 765 | else 766 | self.wasTriggeredByUser = YES; 767 | break; 768 | } 769 | 770 | self.state = SVPullToRefreshStateLoading; 771 | } 772 | 773 | - (void)stopAnimating { 774 | self.state = SVPullToRefreshStateStopped; 775 | 776 | switch (self.position) { 777 | case SVPullToRefreshPositionTop: 778 | if(!self.wasTriggeredByUser) 779 | [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.originalTopInset) animated:YES]; 780 | break; 781 | case SVPullToRefreshPositionBottom: 782 | if(!self.wasTriggeredByUser) 783 | [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.originalBottomInset) animated:YES]; 784 | break; 785 | case SVPullToRefreshPositionLeft: 786 | if(!self.wasTriggeredByUser) 787 | [self.scrollView setContentOffset:CGPointMake(-self.originalLeftInset, self.scrollView.contentOffset.y) animated:YES]; 788 | break; 789 | case SVPullToRefreshPositionRight: 790 | if(!self.wasTriggeredByUser) 791 | [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.originalRightInset, self.scrollView.contentOffset.y) animated:YES]; 792 | break; 793 | } 794 | } 795 | 796 | - (void)setState:(SVPullToRefreshState)newState { 797 | 798 | if(_state == newState) 799 | return; 800 | 801 | SVPullToRefreshState previousState = _state; 802 | _state = newState; 803 | 804 | [self setNeedsLayout]; 805 | [self layoutIfNeeded]; 806 | 807 | switch (newState) { 808 | case SVPullToRefreshStateAll: 809 | case SVPullToRefreshStateStopped: 810 | [self resetScrollViewContentInset]; 811 | break; 812 | 813 | case SVPullToRefreshStateTriggered: 814 | break; 815 | 816 | case SVPullToRefreshStateLoading: 817 | [self setScrollViewContentInsetForLoading]; 818 | 819 | if(previousState == SVPullToRefreshStateTriggered && pullToRefreshActionHandler) { 820 | pullToRefreshActionHandler(); 821 | } 822 | 823 | break; 824 | } 825 | } 826 | 827 | - (void)rotateArrow:(float)degrees hide:(BOOL)hide { 828 | [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ 829 | self.arrow.layer.transform = CATransform3DMakeRotation(degrees, 0, 0, 1); 830 | self.arrow.layer.opacity = !hide; 831 | //[self.arrow setNeedsDisplay];//ios 4 832 | } completion:NULL]; 833 | } 834 | 835 | @end 836 | 837 | 838 | #pragma mark - SVPullToRefreshArrow 839 | 840 | @implementation SVPullToRefreshArrow 841 | @synthesize arrowColor; 842 | 843 | - (UIColor *)arrowColor { 844 | if (arrowColor) return arrowColor; 845 | return [UIColor grayColor]; // default Color 846 | } 847 | 848 | - (void)drawRect:(CGRect)rect { 849 | CGContextRef c = UIGraphicsGetCurrentContext(); 850 | 851 | // the rects above the arrow 852 | CGContextAddRect(c, CGRectMake(5, 0, 12, 4)); // to-do: use dynamic points 853 | CGContextAddRect(c, CGRectMake(5, 6, 12, 4)); // currently fixed size: 22 x 48pt 854 | CGContextAddRect(c, CGRectMake(5, 12, 12, 4)); 855 | CGContextAddRect(c, CGRectMake(5, 18, 12, 4)); 856 | CGContextAddRect(c, CGRectMake(5, 24, 12, 4)); 857 | CGContextAddRect(c, CGRectMake(5, 30, 12, 4)); 858 | 859 | // the arrow 860 | CGContextMoveToPoint(c, 0, 34); 861 | CGContextAddLineToPoint(c, 11, 48); 862 | CGContextAddLineToPoint(c, 22, 34); 863 | CGContextAddLineToPoint(c, 0, 34); 864 | CGContextClosePath(c); 865 | 866 | CGContextSaveGState(c); 867 | CGContextClip(c); 868 | 869 | // Gradient Declaration 870 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 871 | CGFloat alphaGradientLocations[] = {0, 0.8f}; 872 | 873 | CGGradientRef alphaGradient = nil; 874 | if([[[UIDevice currentDevice] systemVersion]floatValue] >= 5){ 875 | NSArray* alphaGradientColors = [NSArray arrayWithObjects: 876 | (id)[self.arrowColor colorWithAlphaComponent:0].CGColor, 877 | (id)[self.arrowColor colorWithAlphaComponent:1].CGColor, 878 | nil]; 879 | alphaGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)alphaGradientColors, alphaGradientLocations); 880 | }else{ 881 | const CGFloat * components = CGColorGetComponents([self.arrowColor CGColor]); 882 | size_t numComponents = CGColorGetNumberOfComponents([self.arrowColor CGColor]); 883 | CGFloat colors[8]; 884 | switch(numComponents){ 885 | case 2:{ 886 | colors[0] = colors[4] = components[0]; 887 | colors[1] = colors[5] = components[0]; 888 | colors[2] = colors[6] = components[0]; 889 | break; 890 | } 891 | case 4:{ 892 | colors[0] = colors[4] = components[0]; 893 | colors[1] = colors[5] = components[1]; 894 | colors[2] = colors[6] = components[2]; 895 | break; 896 | } 897 | } 898 | colors[3] = 0; 899 | colors[7] = 1; 900 | alphaGradient = CGGradientCreateWithColorComponents(colorSpace,colors,alphaGradientLocations,2); 901 | } 902 | 903 | 904 | CGContextDrawLinearGradient(c, alphaGradient, CGPointZero, CGPointMake(0, rect.size.height), 0); 905 | 906 | CGContextRestoreGState(c); 907 | 908 | CGGradientRelease(alphaGradient); 909 | CGColorSpaceRelease(colorSpace); 910 | } 911 | @end 912 | --------------------------------------------------------------------------------