├── README.md ├── TableRefreshAnimation1.gif ├── TableRefreshAnimation2.gif ├── TableRefreshDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── yzy.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── TableRefreshDemo.xcscheme │ └── xcschememanagement.plist ├── TableRefreshDemo ├── ViewController.m ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── TableRefreshView │ ├── RefreshView │ │ ├── RefreshLabelView.h │ │ ├── RefreshLabelView.m │ │ ├── RefreshLayer.h │ │ ├── RefreshLayer.m │ │ ├── RefreshView.h │ │ └── RefreshView.m │ ├── TableFooterRefreshView.h │ ├── TableFooterRefreshView.m │ ├── TableHeaderRefreshView.h │ ├── TableHeaderRefreshView.m │ └── icon.jpg ├── ViewController.h └── main.m ├── TableRefreshDemoTests ├── Info.plist └── TableRefreshDemoTests.m └── TableRefreshDemoUITests ├── Info.plist └── TableRefreshDemoUITests.m /README.md: -------------------------------------------------------------------------------- 1 | # TableRefreshAnimation 2 | 3 | ## Introduction 4 | TableView pull down or pull up refresh animation 5 | 6 | ## How to use 7 | add header view,you can set if there is a navigation bar 8 | ```obejct-c 9 | self.refreshHeaderView = [[TableHeaderRefreshView alloc] initWithScrollView:self.refreshTableView hasNavigationBar:YES]; 10 | 11 | [self.refreshHeaderView doRefresh]; 12 | 13 | __weak __typeof(&*self)weakSelf = self; 14 | [self.refreshHeaderView addRefreshingBlock:^{ 15 | // you can do some net request or other refresh operation 16 | // ... 17 | 18 | // here simulate do some refresh operation,and after 3s refresh complate 19 | double delayTime = 3.0; 20 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delayTime * NSEC_PER_SEC); 21 | dispatch_after(time, dispatch_get_main_queue(), ^{ 22 | [weakSelf.refreshHeaderView stopRefresh]; 23 | }); 24 | }]; 25 | ``` 26 | 27 | add footer view,you can set if there is a navigation bar 28 | ```object-c 29 | self.refreshFooterView = [[TableFooterRefreshView alloc] initWithScrollView:self.refreshTableView hasNavigationBar:YES]; 30 | 31 | [self.refreshFooterView addRefreshingBlock:^{ 32 | // you can do some net request or other refresh operation 33 | // ... 34 | 35 | // here simulate do some refresh operation,and after 3s refresh complate 36 | double delayTime = 3.0; 37 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delayTime * NSEC_PER_SEC); 38 | dispatch_after(time, dispatch_get_main_queue(), ^{ 39 | [weakSelf.refreshFooterView stopRefresh]; 40 | }); 41 | }]; 42 | ``` 43 | 44 | ## Gif 45 | ![](https://github.com/Yuzeyang/TableRefreshAnimation/raw/master/TableRefreshAnimation1.gif) 46 | ![](https://github.com/Yuzeyang/TableRefreshAnimation/raw/master/TableRefreshAnimation2.gif) 47 | 48 | ## Related articles 49 | 50 | [iOS TableView上拉下拉刷新控件(一)](http://zeeyang.com/2016/01/05/iOS_TableView_refresh_controller_one/) 51 | 52 | [iOS TableView上拉下拉刷新控件(二)](http://zeeyang.com/2016/01/06/iOS_TableView_refresh_controller-two/) -------------------------------------------------------------------------------- /TableRefreshAnimation1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuzeyang/TableRefreshAnimation/aa31d628f245f8eecf0276a24160af8fc9051f40/TableRefreshAnimation1.gif -------------------------------------------------------------------------------- /TableRefreshAnimation2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuzeyang/TableRefreshAnimation/aa31d628f245f8eecf0276a24160af8fc9051f40/TableRefreshAnimation2.gif -------------------------------------------------------------------------------- /TableRefreshDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5BBF35611C34E906008B6622 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35601C34E906008B6622 /* main.m */; }; 11 | 5BBF35641C34E906008B6622 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35631C34E906008B6622 /* AppDelegate.m */; }; 12 | 5BBF35671C34E906008B6622 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35661C34E906008B6622 /* ViewController.m */; }; 13 | 5BBF356A1C34E906008B6622 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBF35681C34E906008B6622 /* Main.storyboard */; }; 14 | 5BBF356C1C34E906008B6622 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BBF356B1C34E906008B6622 /* Assets.xcassets */; }; 15 | 5BBF356F1C34E906008B6622 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBF356D1C34E906008B6622 /* LaunchScreen.storyboard */; }; 16 | 5BBF357A1C34E906008B6622 /* TableRefreshDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35791C34E906008B6622 /* TableRefreshDemoTests.m */; }; 17 | 5BBF35851C34E906008B6622 /* TableRefreshDemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35841C34E906008B6622 /* TableRefreshDemoUITests.m */; }; 18 | 5BBF35A81C36CA97008B6622 /* RefreshLabelView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35A31C36CA97008B6622 /* RefreshLabelView.m */; }; 19 | 5BBF35A91C36CA97008B6622 /* RefreshLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35A51C36CA97008B6622 /* RefreshLayer.m */; }; 20 | 5BBF35AA1C36CA97008B6622 /* RefreshView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35A71C36CA97008B6622 /* RefreshView.m */; }; 21 | 5BBF35AD1C36CAAF008B6622 /* TableHeaderRefreshView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35AC1C36CAAF008B6622 /* TableHeaderRefreshView.m */; }; 22 | 5BBF35B01C37C641008B6622 /* TableFooterRefreshView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BBF35AF1C37C641008B6622 /* TableFooterRefreshView.m */; }; 23 | 5BBF35B21C38EB7B008B6622 /* icon.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5BBF35B11C38EB7B008B6622 /* icon.jpg */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 5BBF35761C34E906008B6622 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 5BBF35541C34E906008B6622 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 5BBF355B1C34E906008B6622; 32 | remoteInfo = TableRefreshDemo; 33 | }; 34 | 5BBF35811C34E906008B6622 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 5BBF35541C34E906008B6622 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 5BBF355B1C34E906008B6622; 39 | remoteInfo = TableRefreshDemo; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 5BBF355C1C34E906008B6622 /* TableRefreshDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableRefreshDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 5BBF35601C34E906008B6622 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 46 | 5BBF35621C34E906008B6622 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 47 | 5BBF35631C34E906008B6622 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 48 | 5BBF35651C34E906008B6622 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 49 | 5BBF35661C34E906008B6622 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = " ViewController.m"; sourceTree = ""; }; 50 | 5BBF35691C34E906008B6622 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 5BBF356B1C34E906008B6622 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 5BBF356E1C34E906008B6622 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 5BBF35701C34E906008B6622 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 5BBF35751C34E906008B6622 /* TableRefreshDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableRefreshDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 5BBF35791C34E906008B6622 /* TableRefreshDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TableRefreshDemoTests.m; sourceTree = ""; }; 56 | 5BBF357B1C34E906008B6622 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 5BBF35801C34E906008B6622 /* TableRefreshDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableRefreshDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 5BBF35841C34E906008B6622 /* TableRefreshDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TableRefreshDemoUITests.m; sourceTree = ""; }; 59 | 5BBF35861C34E906008B6622 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | 5BBF35A21C36CA97008B6622 /* RefreshLabelView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefreshLabelView.h; sourceTree = ""; }; 61 | 5BBF35A31C36CA97008B6622 /* RefreshLabelView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RefreshLabelView.m; sourceTree = ""; }; 62 | 5BBF35A41C36CA97008B6622 /* RefreshLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefreshLayer.h; sourceTree = ""; }; 63 | 5BBF35A51C36CA97008B6622 /* RefreshLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RefreshLayer.m; sourceTree = ""; }; 64 | 5BBF35A61C36CA97008B6622 /* RefreshView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefreshView.h; sourceTree = ""; }; 65 | 5BBF35A71C36CA97008B6622 /* RefreshView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RefreshView.m; sourceTree = ""; }; 66 | 5BBF35AB1C36CAAF008B6622 /* TableHeaderRefreshView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableHeaderRefreshView.h; sourceTree = ""; }; 67 | 5BBF35AC1C36CAAF008B6622 /* TableHeaderRefreshView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableHeaderRefreshView.m; sourceTree = ""; }; 68 | 5BBF35AE1C37C641008B6622 /* TableFooterRefreshView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableFooterRefreshView.h; sourceTree = ""; }; 69 | 5BBF35AF1C37C641008B6622 /* TableFooterRefreshView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableFooterRefreshView.m; sourceTree = ""; }; 70 | 5BBF35B11C38EB7B008B6622 /* icon.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = icon.jpg; path = TableRefreshView/icon.jpg; sourceTree = ""; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | 5BBF35591C34E906008B6622 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 5BBF35721C34E906008B6622 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 5BBF357D1C34E906008B6622 /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 5BBF35531C34E906008B6622 = { 99 | isa = PBXGroup; 100 | children = ( 101 | 5BBF355E1C34E906008B6622 /* TableRefreshDemo */, 102 | 5BBF35781C34E906008B6622 /* TableRefreshDemoTests */, 103 | 5BBF35831C34E906008B6622 /* TableRefreshDemoUITests */, 104 | 5BBF355D1C34E906008B6622 /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 5BBF355D1C34E906008B6622 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 5BBF355C1C34E906008B6622 /* TableRefreshDemo.app */, 112 | 5BBF35751C34E906008B6622 /* TableRefreshDemoTests.xctest */, 113 | 5BBF35801C34E906008B6622 /* TableRefreshDemoUITests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 5BBF355E1C34E906008B6622 /* TableRefreshDemo */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 5BBF35621C34E906008B6622 /* AppDelegate.h */, 122 | 5BBF35631C34E906008B6622 /* AppDelegate.m */, 123 | 5BBF35651C34E906008B6622 /* ViewController.h */, 124 | 5BBF35661C34E906008B6622 /* ViewController.m */, 125 | 5BBF35A01C36CA97008B6622 /* TableRefreshView */, 126 | 5BBF35681C34E906008B6622 /* Main.storyboard */, 127 | 5BBF35B11C38EB7B008B6622 /* icon.jpg */, 128 | 5BBF356B1C34E906008B6622 /* Assets.xcassets */, 129 | 5BBF356D1C34E906008B6622 /* LaunchScreen.storyboard */, 130 | 5BBF35701C34E906008B6622 /* Info.plist */, 131 | 5BBF355F1C34E906008B6622 /* Supporting Files */, 132 | ); 133 | path = TableRefreshDemo; 134 | sourceTree = ""; 135 | }; 136 | 5BBF355F1C34E906008B6622 /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 5BBF35601C34E906008B6622 /* main.m */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | 5BBF35781C34E906008B6622 /* TableRefreshDemoTests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 5BBF35791C34E906008B6622 /* TableRefreshDemoTests.m */, 148 | 5BBF357B1C34E906008B6622 /* Info.plist */, 149 | ); 150 | path = TableRefreshDemoTests; 151 | sourceTree = ""; 152 | }; 153 | 5BBF35831C34E906008B6622 /* TableRefreshDemoUITests */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 5BBF35841C34E906008B6622 /* TableRefreshDemoUITests.m */, 157 | 5BBF35861C34E906008B6622 /* Info.plist */, 158 | ); 159 | path = TableRefreshDemoUITests; 160 | sourceTree = ""; 161 | }; 162 | 5BBF35A01C36CA97008B6622 /* TableRefreshView */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 5BBF35AB1C36CAAF008B6622 /* TableHeaderRefreshView.h */, 166 | 5BBF35AC1C36CAAF008B6622 /* TableHeaderRefreshView.m */, 167 | 5BBF35AE1C37C641008B6622 /* TableFooterRefreshView.h */, 168 | 5BBF35AF1C37C641008B6622 /* TableFooterRefreshView.m */, 169 | 5BBF35A11C36CA97008B6622 /* RefreshView */, 170 | ); 171 | path = TableRefreshView; 172 | sourceTree = ""; 173 | }; 174 | 5BBF35A11C36CA97008B6622 /* RefreshView */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 5BBF35A21C36CA97008B6622 /* RefreshLabelView.h */, 178 | 5BBF35A31C36CA97008B6622 /* RefreshLabelView.m */, 179 | 5BBF35A41C36CA97008B6622 /* RefreshLayer.h */, 180 | 5BBF35A51C36CA97008B6622 /* RefreshLayer.m */, 181 | 5BBF35A61C36CA97008B6622 /* RefreshView.h */, 182 | 5BBF35A71C36CA97008B6622 /* RefreshView.m */, 183 | ); 184 | path = RefreshView; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | 5BBF355B1C34E906008B6622 /* TableRefreshDemo */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 5BBF35891C34E906008B6622 /* Build configuration list for PBXNativeTarget "TableRefreshDemo" */; 193 | buildPhases = ( 194 | 5BBF35581C34E906008B6622 /* Sources */, 195 | 5BBF35591C34E906008B6622 /* Frameworks */, 196 | 5BBF355A1C34E906008B6622 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | ); 202 | name = TableRefreshDemo; 203 | productName = TableRefreshDemo; 204 | productReference = 5BBF355C1C34E906008B6622 /* TableRefreshDemo.app */; 205 | productType = "com.apple.product-type.application"; 206 | }; 207 | 5BBF35741C34E906008B6622 /* TableRefreshDemoTests */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = 5BBF358C1C34E906008B6622 /* Build configuration list for PBXNativeTarget "TableRefreshDemoTests" */; 210 | buildPhases = ( 211 | 5BBF35711C34E906008B6622 /* Sources */, 212 | 5BBF35721C34E906008B6622 /* Frameworks */, 213 | 5BBF35731C34E906008B6622 /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | 5BBF35771C34E906008B6622 /* PBXTargetDependency */, 219 | ); 220 | name = TableRefreshDemoTests; 221 | productName = TableRefreshDemoTests; 222 | productReference = 5BBF35751C34E906008B6622 /* TableRefreshDemoTests.xctest */; 223 | productType = "com.apple.product-type.bundle.unit-test"; 224 | }; 225 | 5BBF357F1C34E906008B6622 /* TableRefreshDemoUITests */ = { 226 | isa = PBXNativeTarget; 227 | buildConfigurationList = 5BBF358F1C34E906008B6622 /* Build configuration list for PBXNativeTarget "TableRefreshDemoUITests" */; 228 | buildPhases = ( 229 | 5BBF357C1C34E906008B6622 /* Sources */, 230 | 5BBF357D1C34E906008B6622 /* Frameworks */, 231 | 5BBF357E1C34E906008B6622 /* Resources */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | 5BBF35821C34E906008B6622 /* PBXTargetDependency */, 237 | ); 238 | name = TableRefreshDemoUITests; 239 | productName = TableRefreshDemoUITests; 240 | productReference = 5BBF35801C34E906008B6622 /* TableRefreshDemoUITests.xctest */; 241 | productType = "com.apple.product-type.bundle.ui-testing"; 242 | }; 243 | /* End PBXNativeTarget section */ 244 | 245 | /* Begin PBXProject section */ 246 | 5BBF35541C34E906008B6622 /* Project object */ = { 247 | isa = PBXProject; 248 | attributes = { 249 | LastUpgradeCheck = 0720; 250 | ORGANIZATIONNAME = "宫城"; 251 | TargetAttributes = { 252 | 5BBF355B1C34E906008B6622 = { 253 | CreatedOnToolsVersion = 7.2; 254 | }; 255 | 5BBF35741C34E906008B6622 = { 256 | CreatedOnToolsVersion = 7.2; 257 | TestTargetID = 5BBF355B1C34E906008B6622; 258 | }; 259 | 5BBF357F1C34E906008B6622 = { 260 | CreatedOnToolsVersion = 7.2; 261 | TestTargetID = 5BBF355B1C34E906008B6622; 262 | }; 263 | }; 264 | }; 265 | buildConfigurationList = 5BBF35571C34E906008B6622 /* Build configuration list for PBXProject "TableRefreshDemo" */; 266 | compatibilityVersion = "Xcode 3.2"; 267 | developmentRegion = English; 268 | hasScannedForEncodings = 0; 269 | knownRegions = ( 270 | en, 271 | Base, 272 | ); 273 | mainGroup = 5BBF35531C34E906008B6622; 274 | productRefGroup = 5BBF355D1C34E906008B6622 /* Products */; 275 | projectDirPath = ""; 276 | projectRoot = ""; 277 | targets = ( 278 | 5BBF355B1C34E906008B6622 /* TableRefreshDemo */, 279 | 5BBF35741C34E906008B6622 /* TableRefreshDemoTests */, 280 | 5BBF357F1C34E906008B6622 /* TableRefreshDemoUITests */, 281 | ); 282 | }; 283 | /* End PBXProject section */ 284 | 285 | /* Begin PBXResourcesBuildPhase section */ 286 | 5BBF355A1C34E906008B6622 /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 5BBF35B21C38EB7B008B6622 /* icon.jpg in Resources */, 291 | 5BBF356F1C34E906008B6622 /* LaunchScreen.storyboard in Resources */, 292 | 5BBF356C1C34E906008B6622 /* Assets.xcassets in Resources */, 293 | 5BBF356A1C34E906008B6622 /* Main.storyboard in Resources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | 5BBF35731C34E906008B6622 /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 5BBF357E1C34E906008B6622 /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXResourcesBuildPhase section */ 312 | 313 | /* Begin PBXSourcesBuildPhase section */ 314 | 5BBF35581C34E906008B6622 /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | 5BBF35671C34E906008B6622 /* ViewController.m in Sources */, 319 | 5BBF35A91C36CA97008B6622 /* RefreshLayer.m in Sources */, 320 | 5BBF35641C34E906008B6622 /* AppDelegate.m in Sources */, 321 | 5BBF35611C34E906008B6622 /* main.m in Sources */, 322 | 5BBF35B01C37C641008B6622 /* TableFooterRefreshView.m in Sources */, 323 | 5BBF35AA1C36CA97008B6622 /* RefreshView.m in Sources */, 324 | 5BBF35AD1C36CAAF008B6622 /* TableHeaderRefreshView.m in Sources */, 325 | 5BBF35A81C36CA97008B6622 /* RefreshLabelView.m in Sources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | 5BBF35711C34E906008B6622 /* Sources */ = { 330 | isa = PBXSourcesBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | 5BBF357A1C34E906008B6622 /* TableRefreshDemoTests.m in Sources */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | 5BBF357C1C34E906008B6622 /* Sources */ = { 338 | isa = PBXSourcesBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | 5BBF35851C34E906008B6622 /* TableRefreshDemoUITests.m in Sources */, 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | /* End PBXSourcesBuildPhase section */ 346 | 347 | /* Begin PBXTargetDependency section */ 348 | 5BBF35771C34E906008B6622 /* PBXTargetDependency */ = { 349 | isa = PBXTargetDependency; 350 | target = 5BBF355B1C34E906008B6622 /* TableRefreshDemo */; 351 | targetProxy = 5BBF35761C34E906008B6622 /* PBXContainerItemProxy */; 352 | }; 353 | 5BBF35821C34E906008B6622 /* PBXTargetDependency */ = { 354 | isa = PBXTargetDependency; 355 | target = 5BBF355B1C34E906008B6622 /* TableRefreshDemo */; 356 | targetProxy = 5BBF35811C34E906008B6622 /* PBXContainerItemProxy */; 357 | }; 358 | /* End PBXTargetDependency section */ 359 | 360 | /* Begin PBXVariantGroup section */ 361 | 5BBF35681C34E906008B6622 /* Main.storyboard */ = { 362 | isa = PBXVariantGroup; 363 | children = ( 364 | 5BBF35691C34E906008B6622 /* Base */, 365 | ); 366 | name = Main.storyboard; 367 | sourceTree = ""; 368 | }; 369 | 5BBF356D1C34E906008B6622 /* LaunchScreen.storyboard */ = { 370 | isa = PBXVariantGroup; 371 | children = ( 372 | 5BBF356E1C34E906008B6622 /* Base */, 373 | ); 374 | name = LaunchScreen.storyboard; 375 | sourceTree = ""; 376 | }; 377 | /* End PBXVariantGroup section */ 378 | 379 | /* Begin XCBuildConfiguration section */ 380 | 5BBF35871C34E906008B6622 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 385 | CLANG_CXX_LIBRARY = "libc++"; 386 | CLANG_ENABLE_MODULES = YES; 387 | CLANG_ENABLE_OBJC_ARC = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INT_CONVERSION = YES; 394 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 395 | CLANG_WARN_UNREACHABLE_CODE = YES; 396 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 397 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 398 | COPY_PHASE_STRIP = NO; 399 | DEBUG_INFORMATION_FORMAT = dwarf; 400 | ENABLE_STRICT_OBJC_MSGSEND = YES; 401 | ENABLE_TESTABILITY = YES; 402 | GCC_C_LANGUAGE_STANDARD = gnu99; 403 | GCC_DYNAMIC_NO_PIC = NO; 404 | GCC_NO_COMMON_BLOCKS = YES; 405 | GCC_OPTIMIZATION_LEVEL = 0; 406 | GCC_PREPROCESSOR_DEFINITIONS = ( 407 | "DEBUG=1", 408 | "$(inherited)", 409 | ); 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 417 | MTL_ENABLE_DEBUG_INFO = YES; 418 | ONLY_ACTIVE_ARCH = YES; 419 | SDKROOT = iphoneos; 420 | TARGETED_DEVICE_FAMILY = "1,2"; 421 | }; 422 | name = Debug; 423 | }; 424 | 5BBF35881C34E906008B6622 /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ALWAYS_SEARCH_USER_PATHS = NO; 428 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 429 | CLANG_CXX_LIBRARY = "libc++"; 430 | CLANG_ENABLE_MODULES = YES; 431 | CLANG_ENABLE_OBJC_ARC = YES; 432 | CLANG_WARN_BOOL_CONVERSION = YES; 433 | CLANG_WARN_CONSTANT_CONVERSION = YES; 434 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 435 | CLANG_WARN_EMPTY_BODY = YES; 436 | CLANG_WARN_ENUM_CONVERSION = YES; 437 | CLANG_WARN_INT_CONVERSION = YES; 438 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 439 | CLANG_WARN_UNREACHABLE_CODE = YES; 440 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 442 | COPY_PHASE_STRIP = NO; 443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 444 | ENABLE_NS_ASSERTIONS = NO; 445 | ENABLE_STRICT_OBJC_MSGSEND = YES; 446 | GCC_C_LANGUAGE_STANDARD = gnu99; 447 | GCC_NO_COMMON_BLOCKS = YES; 448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 450 | GCC_WARN_UNDECLARED_SELECTOR = YES; 451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 452 | GCC_WARN_UNUSED_FUNCTION = YES; 453 | GCC_WARN_UNUSED_VARIABLE = YES; 454 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 455 | MTL_ENABLE_DEBUG_INFO = NO; 456 | SDKROOT = iphoneos; 457 | TARGETED_DEVICE_FAMILY = "1,2"; 458 | VALIDATE_PRODUCT = YES; 459 | }; 460 | name = Release; 461 | }; 462 | 5BBF358A1C34E906008B6622 /* Debug */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 466 | INFOPLIST_FILE = TableRefreshDemo/Info.plist; 467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 468 | PRODUCT_BUNDLE_IDENTIFIER = com.gongcheng.TableRefreshDemo; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | }; 471 | name = Debug; 472 | }; 473 | 5BBF358B1C34E906008B6622 /* Release */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 477 | INFOPLIST_FILE = TableRefreshDemo/Info.plist; 478 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 479 | PRODUCT_BUNDLE_IDENTIFIER = com.gongcheng.TableRefreshDemo; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | }; 482 | name = Release; 483 | }; 484 | 5BBF358D1C34E906008B6622 /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | BUNDLE_LOADER = "$(TEST_HOST)"; 488 | INFOPLIST_FILE = TableRefreshDemoTests/Info.plist; 489 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 490 | PRODUCT_BUNDLE_IDENTIFIER = com.gongcheng.TableRefreshDemoTests; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TableRefreshDemo.app/TableRefreshDemo"; 493 | }; 494 | name = Debug; 495 | }; 496 | 5BBF358E1C34E906008B6622 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | BUNDLE_LOADER = "$(TEST_HOST)"; 500 | INFOPLIST_FILE = TableRefreshDemoTests/Info.plist; 501 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 502 | PRODUCT_BUNDLE_IDENTIFIER = com.gongcheng.TableRefreshDemoTests; 503 | PRODUCT_NAME = "$(TARGET_NAME)"; 504 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TableRefreshDemo.app/TableRefreshDemo"; 505 | }; 506 | name = Release; 507 | }; 508 | 5BBF35901C34E906008B6622 /* Debug */ = { 509 | isa = XCBuildConfiguration; 510 | buildSettings = { 511 | INFOPLIST_FILE = TableRefreshDemoUITests/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 513 | PRODUCT_BUNDLE_IDENTIFIER = com.gongcheng.TableRefreshDemoUITests; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | TEST_TARGET_NAME = TableRefreshDemo; 516 | USES_XCTRUNNER = YES; 517 | }; 518 | name = Debug; 519 | }; 520 | 5BBF35911C34E906008B6622 /* Release */ = { 521 | isa = XCBuildConfiguration; 522 | buildSettings = { 523 | INFOPLIST_FILE = TableRefreshDemoUITests/Info.plist; 524 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 525 | PRODUCT_BUNDLE_IDENTIFIER = com.gongcheng.TableRefreshDemoUITests; 526 | PRODUCT_NAME = "$(TARGET_NAME)"; 527 | TEST_TARGET_NAME = TableRefreshDemo; 528 | USES_XCTRUNNER = YES; 529 | }; 530 | name = Release; 531 | }; 532 | /* End XCBuildConfiguration section */ 533 | 534 | /* Begin XCConfigurationList section */ 535 | 5BBF35571C34E906008B6622 /* Build configuration list for PBXProject "TableRefreshDemo" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | 5BBF35871C34E906008B6622 /* Debug */, 539 | 5BBF35881C34E906008B6622 /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | 5BBF35891C34E906008B6622 /* Build configuration list for PBXNativeTarget "TableRefreshDemo" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 5BBF358A1C34E906008B6622 /* Debug */, 548 | 5BBF358B1C34E906008B6622 /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | }; 552 | 5BBF358C1C34E906008B6622 /* Build configuration list for PBXNativeTarget "TableRefreshDemoTests" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | 5BBF358D1C34E906008B6622 /* Debug */, 556 | 5BBF358E1C34E906008B6622 /* Release */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | }; 560 | 5BBF358F1C34E906008B6622 /* Build configuration list for PBXNativeTarget "TableRefreshDemoUITests" */ = { 561 | isa = XCConfigurationList; 562 | buildConfigurations = ( 563 | 5BBF35901C34E906008B6622 /* Debug */, 564 | 5BBF35911C34E906008B6622 /* Release */, 565 | ); 566 | defaultConfigurationIsVisible = 0; 567 | }; 568 | /* End XCConfigurationList section */ 569 | }; 570 | rootObject = 5BBF35541C34E906008B6622 /* Project object */; 571 | } 572 | -------------------------------------------------------------------------------- /TableRefreshDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TableRefreshDemo.xcodeproj/xcuserdata/yzy.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /TableRefreshDemo.xcodeproj/xcuserdata/yzy.xcuserdatad/xcschemes/TableRefreshDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /TableRefreshDemo.xcodeproj/xcuserdata/yzy.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TableRefreshDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 5BBF355B1C34E906008B6622 16 | 17 | primary 18 | 19 | 20 | 5BBF35741C34E906008B6622 21 | 22 | primary 23 | 24 | 25 | 5BBF357F1C34E906008B6622 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /TableRefreshDemo/ ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TableHeaderRefreshView.h" 11 | #import "TableFooterRefreshView.h" 12 | 13 | #define kMaxDataSourceCount 20 14 | 15 | @interface ViewController () 16 | 17 | @property (weak, nonatomic) IBOutlet UITableView *refreshTableView; 18 | @property (nonatomic, strong) NSMutableArray *dataSource; 19 | @property (nonatomic, strong) TableHeaderRefreshView *refreshHeaderView; 20 | @property (nonatomic, strong) TableFooterRefreshView *refreshFooterView; 21 | 22 | @end 23 | 24 | @implementation ViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | self.dataSource = [NSMutableArray array]; 30 | 31 | for (NSUInteger i = 0; i < 10; i++) { 32 | NSString *data = [NSString stringWithFormat:@"This is title %lu",i]; 33 | [self.dataSource addObject:data]; 34 | } 35 | 36 | self.refreshTableView.delegate = self; 37 | self.refreshTableView.dataSource = self; 38 | self.refreshTableView.tableFooterView = [UIView new]; 39 | [self.refreshTableView layoutIfNeeded]; 40 | // Do any additional setup after loading the view, typically from a nib. 41 | } 42 | 43 | - (void)didReceiveMemoryWarning { 44 | [super didReceiveMemoryWarning]; 45 | // Dispose of any resources that can be recreated. 46 | } 47 | 48 | - (void)viewWillAppear:(BOOL)animated { 49 | [super viewWillAppear:animated]; 50 | 51 | self.refreshHeaderView = [[TableHeaderRefreshView alloc] initWithScrollView:self.refreshTableView hasNavigationBar:YES]; 52 | 53 | [self.refreshHeaderView doRefresh]; 54 | 55 | __weak __typeof(&*self)weakSelf = self; 56 | [self.refreshHeaderView addRefreshingBlock:^{ 57 | // you can do some net request or other refresh operation 58 | // ... 59 | 60 | // here simulate do some refresh operation,and after 3s refresh complate 61 | double delayTime = 3.0; 62 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delayTime * NSEC_PER_SEC); 63 | dispatch_after(time, dispatch_get_main_queue(), ^{ 64 | [weakSelf.refreshHeaderView stopRefresh]; 65 | }); 66 | }]; 67 | 68 | self.refreshFooterView = [[TableFooterRefreshView alloc] initWithScrollView:self.refreshTableView hasNavigationBar:YES]; 69 | 70 | [self.refreshFooterView addRefreshingBlock:^{ 71 | // you can do some net request or other refresh operation 72 | // ... 73 | 74 | // here simulate do some refresh operation,and after 3s refresh complate 75 | NSUInteger count = weakSelf.dataSource.count; 76 | if (count <= kMaxDataSourceCount) { 77 | for (NSUInteger i = count; i < count + 10; i++) { 78 | NSString *data = [NSString stringWithFormat:@"This is title %lu",i]; 79 | [weakSelf.dataSource addObject:data]; 80 | } 81 | } 82 | 83 | double delayTime = 3.0; 84 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delayTime * NSEC_PER_SEC); 85 | dispatch_after(time, dispatch_get_main_queue(), ^{ 86 | [weakSelf.refreshTableView reloadData]; 87 | [weakSelf.refreshFooterView stopRefresh]; 88 | }); 89 | }]; 90 | } 91 | 92 | #pragma mark - delegate 93 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 94 | return self.dataSource.count; 95 | } 96 | 97 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 98 | UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 99 | cell.textLabel.text = self.dataSource[indexPath.row]; 100 | cell.imageView.image = [UIImage imageNamed:@"icon.jpg"]; 101 | 102 | return cell; 103 | } 104 | 105 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 106 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 107 | } 108 | 109 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 110 | return 70.0; 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /TableRefreshDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TableRefreshDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | ViewController *view = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"main"]; 22 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view]; 23 | self.window.rootViewController = nav; 24 | [self.window makeKeyAndVisible]; 25 | 26 | return YES; 27 | } 28 | 29 | - (void)applicationWillResignActive:(UIApplication *)application { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 32 | } 33 | 34 | - (void)applicationDidEnterBackground:(UIApplication *)application { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | - (void)applicationWillEnterForeground:(UIApplication *)application { 40 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | - (void)applicationDidBecomeActive:(UIApplication *)application { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 45 | } 46 | 47 | - (void)applicationWillTerminate:(UIApplication *)application { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TableRefreshDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /TableRefreshDemo/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 | -------------------------------------------------------------------------------- /TableRefreshDemo/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 | -------------------------------------------------------------------------------- /TableRefreshDemo/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 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/RefreshView/RefreshLabelView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshLabelView.h 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/1. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, DIRECTION) { 12 | UP, 13 | DOWN 14 | }; 15 | 16 | @interface RefreshLabelView : UIView 17 | 18 | /** 19 | * Rate of progress,from 0 to 1 20 | */ 21 | @property (nonatomic, assign) CGFloat progress; 22 | 23 | /** 24 | * The direction of the pull 25 | */ 26 | @property (nonatomic, assign) DIRECTION direction; 27 | 28 | /** 29 | * Whether is loading 30 | */ 31 | @property (nonatomic, assign) BOOL isLoading; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/RefreshView/RefreshLabelView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshLabelView.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/1. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import "RefreshLabelView.h" 10 | 11 | #define kLabelHeight 40 12 | 13 | #define PULLDOWN_REFRESH @"下拉即可刷新..." 14 | #define PULLUP_RERESH @"上拉即可刷新..." 15 | #define RELEASE_REFRESH @"松开即可刷新..." 16 | #define IS_REFRESH @"正在刷新列表..." 17 | 18 | @interface RefreshLabelView () 19 | 20 | @property (nonatomic, strong) UILabel *noticeLB; 21 | 22 | @end 23 | 24 | @implementation RefreshLabelView 25 | 26 | - (instancetype)initWithFrame:(CGRect)frame { 27 | self = [super initWithFrame:frame]; 28 | if (self) { 29 | [self setUI]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)setUI { 35 | self.noticeLB = [[UILabel alloc] initWithFrame:CGRectMake(0, self.center.y - kLabelHeight/2, CGRectGetWidth(self.frame), kLabelHeight)]; 36 | self.noticeLB.adjustsFontSizeToFitWidth = YES; 37 | self.noticeLB.text = PULLDOWN_REFRESH; 38 | self.noticeLB.textColor = [UIColor blackColor]; 39 | [self addSubview:self.noticeLB]; 40 | self.direction = DOWN; 41 | } 42 | 43 | - (void)setProgress:(CGFloat)progress { 44 | self.noticeLB.alpha = progress; 45 | 46 | if (self.isLoading) { 47 | self.noticeLB.text = IS_REFRESH; 48 | }else { 49 | if (progress < 1.0) { 50 | self.noticeLB.text = self.direction == UP?PULLUP_RERESH:PULLDOWN_REFRESH; 51 | }else { 52 | self.noticeLB.text = RELEASE_REFRESH; 53 | } 54 | } 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/RefreshView/RefreshLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshLayer.h 3 | // RefreshAnimation 4 | // 5 | // Created by 宫城 on 15/12/29. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RefreshLayer : CALayer 12 | 13 | /** 14 | * Rate of progress,from 0 to 1 15 | */ 16 | @property (nonatomic, assign) CGFloat progress; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/RefreshView/RefreshLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshLayer.m 3 | // RefreshAnimation 4 | // 5 | // Created by 宫城 on 15/12/29. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import "RefreshLayer.h" 10 | 11 | #define kLineLength 10 12 | #define kArrowLength 5 13 | #define kArrowAngle M_PI/6 14 | 15 | #define kCenterX CGRectGetWidth(self.frame)/2 16 | #define kCenterY CGRectGetHeight(self.frame)/2 17 | 18 | @implementation RefreshLayer 19 | 20 | - (void)drawInContext:(CGContextRef)ctx { 21 | [super drawInContext:ctx]; 22 | 23 | UIGraphicsPushContext(ctx); 24 | CGContextRef context = UIGraphicsGetCurrentContext(); 25 | 26 | // left line 27 | UIBezierPath *leftPath = [UIBezierPath bezierPath]; 28 | leftPath.lineWidth = 2.0; 29 | leftPath.lineCapStyle = kCGLineCapRound; 30 | leftPath.lineJoinStyle = kCGLineJoinRound; 31 | 32 | UIBezierPath *arrowPath = [UIBezierPath bezierPath]; 33 | 34 | if (self.progress <= 0.5) { 35 | CGPoint leftA = CGPointMake(kCenterX - kLineLength, kCenterY + 2*kLineLength - self.progress/0.5*kLineLength); 36 | CGPoint leftB = CGPointMake(leftA.x, leftA.y - kLineLength); 37 | 38 | [leftPath moveToPoint:leftA]; 39 | [leftPath addLineToPoint:leftB]; 40 | 41 | [arrowPath moveToPoint:leftB]; 42 | [arrowPath addLineToPoint:CGPointMake(leftB.x - kArrowLength*sin(kArrowAngle), leftB.y + kArrowLength*cos(kArrowAngle))]; 43 | [leftPath appendPath:arrowPath]; 44 | }else if (self.progress > 0.5) { 45 | CGPoint leftA = CGPointMake(kCenterX - kLineLength, kCenterY + kLineLength - (self.progress - 0.5)/0.5*kLineLength); 46 | CGPoint leftB = CGPointMake(kCenterX - kLineLength, kCenterY); 47 | 48 | [leftPath moveToPoint:leftA]; 49 | [leftPath addLineToPoint:leftB]; 50 | [leftPath addArcWithCenter:CGPointMake(kCenterX, kCenterY) radius:kLineLength startAngle:M_PI endAngle:M_PI+M_PI*(self.progress - 0.5)/0.5*9/10 clockwise:YES]; 51 | 52 | [arrowPath moveToPoint:leftPath.currentPoint]; 53 | [arrowPath addLineToPoint:CGPointMake(leftPath.currentPoint.x - kArrowLength*sin(kArrowAngle+M_PI*(self.progress - 0.5)/0.5*9/10), leftPath.currentPoint.y + kArrowLength*cos(kArrowAngle+M_PI*(self.progress - 0.5)/0.5*9/10))]; 54 | [leftPath appendPath:arrowPath]; 55 | } 56 | 57 | // right line 58 | UIBezierPath *rightPath = [UIBezierPath bezierPath]; 59 | rightPath.lineWidth = 2.0; 60 | rightPath.lineCapStyle = kCGLineCapRound; 61 | rightPath.lineJoinStyle = kCGLineCapRound; 62 | 63 | if (self.progress <= 0.5) { 64 | CGPoint rightA = CGPointMake(kCenterX + kLineLength, kCenterY - 2*kLineLength + self.progress/0.5*kLineLength); 65 | CGPoint rightB = CGPointMake(rightA.x, rightA.y + kLineLength); 66 | 67 | [rightPath moveToPoint:rightA]; 68 | [rightPath addLineToPoint:rightB]; 69 | 70 | [arrowPath moveToPoint:rightB]; 71 | [arrowPath addLineToPoint:CGPointMake(rightB.x + kArrowLength*sin(kArrowAngle), rightB.y - kArrowLength*cos(kArrowAngle))]; 72 | [rightPath appendPath:arrowPath]; 73 | }else if (self.progress > 0.5) { 74 | CGPoint rightA = CGPointMake(kCenterX + kLineLength, kCenterY - kLineLength + (self.progress - 0.5)/0.5*kLineLength); 75 | CGPoint rightB = CGPointMake(kCenterX + kLineLength, kCenterY); 76 | 77 | [rightPath moveToPoint:rightA]; 78 | [rightPath addLineToPoint:rightB]; 79 | [rightPath addArcWithCenter:CGPointMake(kCenterX, kCenterY) radius:kLineLength startAngle:0 endAngle:M_PI*(self.progress - 0.5)/0.5*9/10 clockwise:YES]; 80 | 81 | [arrowPath moveToPoint:rightPath.currentPoint]; 82 | [arrowPath addLineToPoint:CGPointMake(rightPath.currentPoint.x + kArrowLength*sin(kArrowAngle+M_PI*(self.progress - 0.5)/0.5*9/10), rightPath.currentPoint.y - kArrowLength*cos(kArrowAngle+M_PI*(self.progress - 0.5)/0.5*9/10))]; 83 | [rightPath appendPath:arrowPath]; 84 | } 85 | 86 | CGContextSaveGState(context); 87 | CGContextRestoreGState(context); 88 | 89 | [[UIColor blackColor] setStroke]; 90 | [leftPath stroke]; 91 | [rightPath stroke]; 92 | [arrowPath stroke]; 93 | 94 | UIGraphicsPopContext(); 95 | } 96 | 97 | - (void)setProgress:(CGFloat)progress { 98 | _progress = progress; 99 | [self setNeedsDisplay]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/RefreshView/RefreshView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshView.h 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/1. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RefreshView : UIView 12 | 13 | /** 14 | * Rate of progress,from 0 to 1 15 | */ 16 | @property (nonatomic, assign) CGFloat progress; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/RefreshView/RefreshView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshView.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/1. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import "RefreshView.h" 10 | #import "RefreshLayer.h" 11 | 12 | @interface RefreshView () 13 | 14 | @property (nonatomic, strong) RefreshLayer *refreshLayer; 15 | 16 | @end 17 | 18 | @implementation RefreshView 19 | 20 | - (instancetype)initWithFrame:(CGRect)frame { 21 | self = [super initWithFrame:frame]; 22 | if (self) { 23 | 24 | } 25 | return self; 26 | } 27 | 28 | - (void)setProgress:(CGFloat)progress { 29 | self.refreshLayer.progress = progress; 30 | [self.refreshLayer setNeedsDisplay]; 31 | } 32 | 33 | - (void)willMoveToSuperview:(UIView *)newSuperview { 34 | self.refreshLayer = [RefreshLayer layer]; 35 | self.refreshLayer.frame = self.bounds; 36 | self.refreshLayer.contentsScale = [UIScreen mainScreen].scale; 37 | self.refreshLayer.progress = 0.0; 38 | [self.refreshLayer setNeedsDisplay]; 39 | [self.layer addSublayer:self.refreshLayer]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/TableFooterRefreshView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableFooterRefreshView.h 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/2. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableFooterRefreshView : UIView 12 | 13 | /** 14 | * Init method 15 | * 16 | * @param scrollView The associated scorll view 17 | * @param hasNavigationBar If there is a navigation bar 18 | * 19 | * @return self 20 | */ 21 | - (instancetype)initWithScrollView:(UIScrollView *)scrollView hasNavigationBar:(BOOL)hasNavigationBar; 22 | 23 | /** 24 | * Add refresh block to handle something when refreshing 25 | * 26 | * @param block Refresh block 27 | */ 28 | - (void)addRefreshingBlock:(void(^)())block; 29 | 30 | /** 31 | * Stop Refresh 32 | */ 33 | - (void)stopRefresh; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/TableFooterRefreshView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableFooterRefreshView.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/2. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import "TableFooterRefreshView.h" 10 | #import "RefreshView.h" 11 | #import "RefreshLabelView.h" 12 | 13 | #define kRefreshViewWidth 200 14 | #define kRefreshViewHeight 80 15 | 16 | #define kMaxPullUpDistance 84 17 | 18 | typedef void(^RefreshingBlock)(void); 19 | 20 | @interface TableFooterRefreshView () 21 | 22 | @property (nonatomic, strong) UIScrollView *scrollView; 23 | @property (nonatomic, strong) RefreshView *refreshView; 24 | @property (nonatomic, strong) RefreshLabelView *refreshLBView; 25 | 26 | @property (nonatomic, assign) CGFloat originOffset; 27 | @property (nonatomic, assign) CGFloat progress; 28 | @property (nonatomic, assign) BOOL isLoading; 29 | @property (nonatomic, assign) BOOL notTracking; 30 | @property (nonatomic, assign) CGSize contentSize; 31 | @property (nonatomic, copy) RefreshingBlock refreshingBlock; 32 | 33 | @end 34 | 35 | @implementation TableFooterRefreshView 36 | 37 | - (instancetype)initWithScrollView:(UIScrollView *)scrollView hasNavigationBar:(BOOL)hasNavigationBar { 38 | self = [super initWithFrame:CGRectMake((CGRectGetWidth(scrollView.frame) - kRefreshViewWidth)/2, CGRectGetHeight(scrollView.frame), kRefreshViewWidth, kRefreshViewHeight)]; 39 | if (self) { 40 | if (hasNavigationBar) { 41 | self.originOffset = 64.0; 42 | }else { 43 | self.originOffset = 0.0; 44 | } 45 | [self setUI]; 46 | self.scrollView = scrollView; 47 | [self.scrollView insertSubview:self atIndex:0]; 48 | [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 49 | [self.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 50 | self.hidden = YES; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)setUI { 56 | self.refreshView = [[RefreshView alloc] initWithFrame:CGRectMake(20, 0, 40, CGRectGetHeight(self.frame))]; 57 | [self insertSubview:self.refreshView atIndex:0]; 58 | 59 | CGFloat lbX = self.refreshView.frame.origin.x + CGRectGetWidth(self.refreshView.frame) + 10; 60 | CGFloat lbWidth = kRefreshViewWidth - lbX - 30; 61 | self.refreshLBView = [[RefreshLabelView alloc] initWithFrame:CGRectMake(lbX , 0, lbWidth, CGRectGetHeight(self.frame))]; 62 | self.refreshLBView.direction = UP; 63 | [self insertSubview:self.refreshLBView aboveSubview:self.refreshView]; 64 | } 65 | 66 | - (void)setProgress:(CGFloat)progress { 67 | if (CGRectGetHeight(self.scrollView.frame) > self.contentSize.height) { 68 | return; 69 | } 70 | 71 | if (!self.scrollView.tracking) { 72 | self.refreshLBView.isLoading = YES; 73 | } 74 | 75 | if (!self.isLoading) { 76 | self.refreshView.progress = progress; 77 | self.refreshLBView.progress = progress; 78 | } 79 | 80 | CGFloat overDistance = self.scrollView.contentOffset.y - (self.scrollView.contentSize.height - CGRectGetHeight(self.scrollView.frame)) - kMaxPullUpDistance + 10; 81 | if (overDistance > 0) { 82 | if (!self.scrollView.tracking && self.hidden == NO) { 83 | if (!self.notTracking) { 84 | self.notTracking = YES; 85 | self.isLoading = YES; 86 | 87 | [self startLoading:self.refreshView]; 88 | 89 | [UIView animateWithDuration:0.3 animations:^{ 90 | self.scrollView.contentInset = UIEdgeInsetsMake(self.originOffset, 0, kMaxPullUpDistance, 0); 91 | } completion:^(BOOL finished) { 92 | if (self.refreshingBlock) { 93 | self.refreshingBlock(); 94 | } 95 | }]; 96 | } 97 | } 98 | 99 | if (!self.isLoading) { 100 | self.refreshView.transform = CGAffineTransformMakeRotation(overDistance/40); 101 | } 102 | }else { 103 | self.refreshLBView.isLoading = NO; 104 | self.refreshView.transform = CGAffineTransformIdentity; 105 | } 106 | } 107 | 108 | - (void)startLoading:(UIView *)view { 109 | CABasicAnimation *rotationZ = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 110 | rotationZ.fromValue = @(0); 111 | rotationZ.toValue = @(M_PI*2); 112 | rotationZ.repeatCount = HUGE_VAL; 113 | rotationZ.duration = 1.0; 114 | rotationZ.cumulative = YES; 115 | [view.layer addAnimation:rotationZ forKey:@"footerRotationZ"]; 116 | } 117 | 118 | - (void)stopLoading:(UIView *)view { 119 | [view.layer removeAllAnimations]; 120 | } 121 | 122 | - (void)stopRefresh { 123 | self.progress = 1.0; 124 | [UIView animateWithDuration:0.1 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 125 | self.alpha = 0.0; 126 | self.scrollView.contentInset = UIEdgeInsetsMake(self.originOffset + 0.1, 0, 0, 0); 127 | } completion:^(BOOL finished) { 128 | self.alpha = 1.0; 129 | self.notTracking = NO; 130 | self.isLoading = NO; 131 | self.refreshLBView.isLoading = NO; 132 | [self stopLoading:self.refreshView]; 133 | }]; 134 | } 135 | 136 | - (void)addRefreshingBlock:(void(^)())block { 137 | self.refreshingBlock = block; 138 | } 139 | 140 | #pragma mark - KVO 141 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 142 | if ([keyPath isEqualToString:@"contentSize"]) { 143 | self.contentSize = [[change valueForKey:NSKeyValueChangeNewKey] CGSizeValue]; 144 | if (self.contentSize.height >= CGRectGetHeight(self.scrollView.frame)) { 145 | self.hidden = NO; 146 | } 147 | } 148 | 149 | if ([keyPath isEqualToString:@"contentOffset"]) { 150 | CGPoint contentOffset = [[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]; 151 | if (contentOffset.y >= (self.contentSize.height - CGRectGetHeight(self.scrollView.frame))) { 152 | self.progress = MAX(0.0, MIN((contentOffset.y - (self.contentSize.height - CGRectGetHeight(self.scrollView.frame)))/kMaxPullUpDistance, 1.0)); 153 | self.center = CGPointMake(self.center.x, self.contentSize.height + kRefreshViewHeight/2); 154 | } 155 | } 156 | } 157 | 158 | #pragma mark - dealloc 159 | - (void)dealloc { 160 | [self.scrollView removeObserver:self forKeyPath:@"contentOffset"]; 161 | [self.scrollView removeObserver:self forKeyPath:@"contentSize"]; 162 | } 163 | 164 | @end 165 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/TableHeaderRefreshView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableHeaderRefreshView.h 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/1. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableHeaderRefreshView : UIView 12 | 13 | /** 14 | * Init method 15 | * 16 | * @param scrollView The associated scorll view 17 | * @param hasNavigationBar If there is a navigation bar 18 | * 19 | * @return self 20 | */ 21 | - (instancetype)initWithScrollView:(UIScrollView *)scrollView hasNavigationBar:(BOOL)hasNavigationBar; 22 | 23 | /** 24 | * Add refresh block to handle something when refreshing 25 | * 26 | * @param block Refresh block 27 | */ 28 | - (void)addRefreshingBlock:(void(^)())block; 29 | 30 | /** 31 | * Do refresh 32 | */ 33 | - (void)doRefresh; 34 | 35 | /** 36 | * Stop refresh 37 | */ 38 | - (void)stopRefresh; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/TableHeaderRefreshView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableHeaderRefreshView.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 16/1/1. 6 | // Copyright © 2016年 宫城. All rights reserved. 7 | // 8 | 9 | #import "TableHeaderRefreshView.h" 10 | #import "RefreshView.h" 11 | #import "RefreshLabelView.h" 12 | 13 | #define kRefreshViewWidth 200 14 | #define kRefreshViewHeight 80 15 | 16 | #define kMaxPullDownDistance 84 17 | 18 | typedef void(^RefreshingBlock)(void); 19 | 20 | @interface TableHeaderRefreshView () 21 | 22 | @property (nonatomic, strong) UIScrollView *scrollView; 23 | @property (nonatomic, strong) RefreshView *refreshView; 24 | @property (nonatomic, strong) RefreshLabelView *refreshLBView; 25 | 26 | @property (nonatomic, assign) CGFloat originOffset; 27 | @property (nonatomic, assign) CGFloat progress; 28 | @property (nonatomic, assign) BOOL isLoading; 29 | @property (nonatomic, assign) BOOL notTracking; 30 | @property (nonatomic, copy) RefreshingBlock refreshingBlock; 31 | 32 | @end 33 | 34 | @implementation TableHeaderRefreshView 35 | 36 | - (instancetype)initWithScrollView:(UIScrollView *)scrollView hasNavigationBar:(BOOL)hasNavigationBar { 37 | self = [super initWithFrame:CGRectMake((CGRectGetWidth(scrollView.frame) - kRefreshViewWidth)/2, -kRefreshViewHeight, kRefreshViewWidth, kRefreshViewHeight)]; 38 | if (self) { 39 | if (hasNavigationBar) { 40 | self.originOffset = 64.0; 41 | }else { 42 | self.originOffset = 0.0; 43 | } 44 | [self setUI]; 45 | self.scrollView = scrollView; 46 | [self.scrollView insertSubview:self atIndex:0]; 47 | [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 48 | } 49 | return self; 50 | } 51 | 52 | - (void)setUI { 53 | self.refreshView = [[RefreshView alloc] initWithFrame:CGRectMake(20, 0, 40, CGRectGetHeight(self.frame))]; 54 | [self insertSubview:self.refreshView atIndex:0]; 55 | 56 | CGFloat lbX = self.refreshView.frame.origin.x + CGRectGetWidth(self.refreshView.frame) + 10; 57 | CGFloat lbWidth = kRefreshViewWidth - lbX - 30; 58 | self.refreshLBView = [[RefreshLabelView alloc] initWithFrame:CGRectMake(lbX, 0, lbWidth, CGRectGetHeight(self.frame))]; 59 | [self insertSubview:self.refreshLBView aboveSubview:self.refreshView]; 60 | } 61 | 62 | - (void)setProgress:(CGFloat)progress { 63 | if (!self.scrollView.tracking) { 64 | self.refreshLBView.isLoading = YES; 65 | } 66 | 67 | if (!self.isLoading) { 68 | self.refreshView.progress = progress; 69 | self.refreshLBView.progress = progress; 70 | } 71 | 72 | CGFloat overDistance = fabs(self.originOffset + self.scrollView.contentOffset.y) - kMaxPullDownDistance + 10; 73 | if (overDistance > 0) { 74 | if (!self.scrollView.tracking) { 75 | if (!self.notTracking) { 76 | self.notTracking = YES; 77 | self.isLoading = YES; 78 | 79 | [self startLoading:self.refreshView]; 80 | 81 | // 0.3s animation time is the best experience 82 | [UIView animateWithDuration:0.3 animations:^{ 83 | self.scrollView.contentInset = UIEdgeInsetsMake(kMaxPullDownDistance + self.originOffset, 0, 0, 0); 84 | } completion:^(BOOL finished) { 85 | if (self.refreshingBlock) { 86 | self.refreshingBlock(); 87 | } 88 | }]; 89 | } 90 | } 91 | 92 | if (!self.isLoading) { 93 | self.refreshView.transform = CGAffineTransformMakeRotation(overDistance/40); 94 | } 95 | }else { 96 | self.refreshLBView.isLoading = NO; 97 | self.refreshView.transform = CGAffineTransformIdentity; 98 | } 99 | } 100 | 101 | - (void)startLoading:(UIView *)view { 102 | CABasicAnimation *rotationZ = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 103 | rotationZ.fromValue = @(0); 104 | rotationZ.toValue = @(M_PI*2); 105 | rotationZ.repeatDuration = HUGE_VAL; 106 | rotationZ.duration = 1.0; 107 | rotationZ.cumulative = YES; 108 | [view.layer addAnimation:rotationZ forKey:@"headerRotationZ"]; 109 | } 110 | 111 | - (void)stopLoading:(UIView *)view { 112 | [view.layer removeAllAnimations]; 113 | } 114 | 115 | - (void)doRefresh { 116 | [self.scrollView setContentOffset:CGPointMake(0, - kMaxPullDownDistance) animated:YES]; 117 | } 118 | 119 | - (void)stopRefresh { 120 | self.progress = 1.0; 121 | [UIView animateWithDuration:0.1 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 122 | self.alpha = 0.0; 123 | self.scrollView.contentInset = UIEdgeInsetsMake(self.originOffset + 0.1, 0, 0, 0); 124 | } completion:^(BOOL finished) { 125 | self.alpha = 1.0; 126 | self.isLoading = NO; 127 | self.notTracking = NO; 128 | self.refreshLBView.isLoading = NO; 129 | [self stopLoading:self.refreshView]; 130 | }]; 131 | } 132 | 133 | - (void)addRefreshingBlock:(void (^)())block { 134 | self.refreshingBlock = block; 135 | } 136 | 137 | #pragma mark - KVO 138 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 139 | if ([keyPath isEqualToString:@"contentOffset"]) { 140 | CGPoint contentOffset = [[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]; 141 | if (self.originOffset + contentOffset.y <= 0) { 142 | self.progress = MAX(0.0, MIN(fabs((self.originOffset + contentOffset.y)/kMaxPullDownDistance), 1.0)); 143 | } 144 | } 145 | } 146 | 147 | #pragma mark - dealloc 148 | - (void)dealloc { 149 | [self.scrollView removeObserver:self forKeyPath:@"contentOffset"]; 150 | } 151 | @end 152 | -------------------------------------------------------------------------------- /TableRefreshDemo/TableRefreshView/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuzeyang/TableRefreshAnimation/aa31d628f245f8eecf0276a24160af8fc9051f40/TableRefreshDemo/TableRefreshView/icon.jpg -------------------------------------------------------------------------------- /TableRefreshDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TableRefreshDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TableRefreshDemo 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. 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 | -------------------------------------------------------------------------------- /TableRefreshDemoTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TableRefreshDemoTests/TableRefreshDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableRefreshDemoTests.m 3 | // TableRefreshDemoTests 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableRefreshDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TableRefreshDemoTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TableRefreshDemoUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TableRefreshDemoUITests/TableRefreshDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableRefreshDemoUITests.m 3 | // TableRefreshDemoUITests 4 | // 5 | // Created by 宫城 on 15/12/31. 6 | // Copyright © 2015年 宫城. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableRefreshDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TableRefreshDemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------