├── .gitattributes ├── CollectionViewSideRefresh.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── hcb.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── hcb.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── CollectionViewSideRefresh ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── loading_01.imageset │ │ ├── Contents.json │ │ └── loading_01.png │ ├── loading_02.imageset │ │ ├── Contents.json │ │ └── loading_02.png │ ├── loading_03.imageset │ │ ├── Contents.json │ │ └── loading_03.png │ ├── loading_04.imageset │ │ ├── Contents.json │ │ └── loading_04.png │ ├── loading_05.imageset │ │ ├── Contents.json │ │ └── loading_05.png │ ├── loading_06.imageset │ │ ├── Contents.json │ │ └── loading_06.png │ ├── loading_07.imageset │ │ ├── Contents.json │ │ └── loading_07.png │ ├── loading_08.imageset │ │ ├── Contents.json │ │ └── loading_08.png │ └── loading_09.imageset │ │ ├── Contents.json │ │ └── loading_09.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SideRefresh │ ├── SideRefresh.h │ ├── SideRefresh.m │ ├── SideRefreshEmptyFooter.h │ ├── SideRefreshEmptyFooter.m │ ├── SideRefreshFooter.h │ ├── SideRefreshFooter.m │ ├── SideRefreshHeader.h │ ├── SideRefreshHeader.m │ ├── UICollectionView+SideExtension.h │ ├── UICollectionView+SideExtension.m │ ├── UICollectionView+SideRefresh.h │ └── UICollectionView+SideRefresh.m ├── SideRefreshDemo │ ├── CustomViewController.h │ ├── CustomViewController.m │ ├── DefaultViewController.h │ ├── DefaultViewController.m │ ├── EmptyFooterViewController.h │ ├── EmptyFooterViewController.m │ ├── HideItemViewController.h │ ├── HideItemViewController.m │ ├── MyCollectionViewCell.h │ ├── MyCollectionViewCell.m │ └── MyCollectionViewCell.xib ├── ViewController.h ├── ViewController.m └── main.m ├── GIF ├── 没有更多数据提示.GIF ├── 自定义.png ├── 隐藏提示或动画.png └── 默认样式.png └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 532FF95F2101A78800CC3A80 /* SideRefreshEmptyFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 532FF95E2101A78800CC3A80 /* SideRefreshEmptyFooter.m */; }; 11 | 532FF9622103297E00CC3A80 /* EmptyFooterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 532FF9612103297E00CC3A80 /* EmptyFooterViewController.m */; }; 12 | 534195602148F7D1005EF096 /* UICollectionView+SideExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 5341955F2148F7D1005EF096 /* UICollectionView+SideExtension.m */; }; 13 | 53C040F520F60342005FAB25 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C040F420F60342005FAB25 /* AppDelegate.m */; }; 14 | 53C040F820F60342005FAB25 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C040F720F60342005FAB25 /* ViewController.m */; }; 15 | 53C040FB20F60342005FAB25 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53C040F920F60342005FAB25 /* Main.storyboard */; }; 16 | 53C040FD20F60343005FAB25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53C040FC20F60343005FAB25 /* Assets.xcassets */; }; 17 | 53C0410020F60343005FAB25 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53C040FE20F60343005FAB25 /* LaunchScreen.storyboard */; }; 18 | 53C0410320F60343005FAB25 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0410220F60343005FAB25 /* main.m */; }; 19 | 53C0410C20F60410005FAB25 /* SideRefreshHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0410B20F60410005FAB25 /* SideRefreshHeader.m */; }; 20 | 53C0410F20F6041F005FAB25 /* SideRefreshFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0410E20F6041F005FAB25 /* SideRefreshFooter.m */; }; 21 | 53C0411220F60435005FAB25 /* UICollectionView+SideRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0411120F60435005FAB25 /* UICollectionView+SideRefresh.m */; }; 22 | 53C0411520F6065B005FAB25 /* SideRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0411420F6065B005FAB25 /* SideRefresh.m */; }; 23 | 53C0412220F88B52005FAB25 /* DefaultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0412120F88B52005FAB25 /* DefaultViewController.m */; }; 24 | 53C0412520F88D13005FAB25 /* CustomViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0412420F88D13005FAB25 /* CustomViewController.m */; }; 25 | 53C0412920F88D3E005FAB25 /* MyCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0412720F88D3E005FAB25 /* MyCollectionViewCell.m */; }; 26 | 53C0412A20F88D3E005FAB25 /* MyCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53C0412820F88D3E005FAB25 /* MyCollectionViewCell.xib */; }; 27 | 53C0412D20F88E05005FAB25 /* HideItemViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53C0412C20F88E05005FAB25 /* HideItemViewController.m */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 532FF95D2101A78800CC3A80 /* SideRefreshEmptyFooter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SideRefreshEmptyFooter.h; sourceTree = ""; }; 32 | 532FF95E2101A78800CC3A80 /* SideRefreshEmptyFooter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SideRefreshEmptyFooter.m; sourceTree = ""; }; 33 | 532FF9602103297E00CC3A80 /* EmptyFooterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EmptyFooterViewController.h; sourceTree = ""; }; 34 | 532FF9612103297E00CC3A80 /* EmptyFooterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EmptyFooterViewController.m; sourceTree = ""; }; 35 | 5341955E2148F7D1005EF096 /* UICollectionView+SideExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+SideExtension.h"; sourceTree = ""; }; 36 | 5341955F2148F7D1005EF096 /* UICollectionView+SideExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+SideExtension.m"; sourceTree = ""; }; 37 | 53C040F020F60342005FAB25 /* CollectionViewSideRefresh.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CollectionViewSideRefresh.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 53C040F320F60342005FAB25 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 39 | 53C040F420F60342005FAB25 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 40 | 53C040F620F60342005FAB25 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 41 | 53C040F720F60342005FAB25 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 42 | 53C040FA20F60342005FAB25 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 53C040FC20F60343005FAB25 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 53C040FF20F60343005FAB25 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | 53C0410120F60343005FAB25 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 53C0410220F60343005FAB25 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | 53C0410A20F60410005FAB25 /* SideRefreshHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SideRefreshHeader.h; sourceTree = ""; }; 48 | 53C0410B20F60410005FAB25 /* SideRefreshHeader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SideRefreshHeader.m; sourceTree = ""; }; 49 | 53C0410D20F6041F005FAB25 /* SideRefreshFooter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SideRefreshFooter.h; sourceTree = ""; }; 50 | 53C0410E20F6041F005FAB25 /* SideRefreshFooter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SideRefreshFooter.m; sourceTree = ""; }; 51 | 53C0411020F60435005FAB25 /* UICollectionView+SideRefresh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+SideRefresh.h"; sourceTree = ""; }; 52 | 53C0411120F60435005FAB25 /* UICollectionView+SideRefresh.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+SideRefresh.m"; sourceTree = ""; }; 53 | 53C0411320F6065B005FAB25 /* SideRefresh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SideRefresh.h; sourceTree = ""; }; 54 | 53C0411420F6065B005FAB25 /* SideRefresh.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SideRefresh.m; sourceTree = ""; }; 55 | 53C0412020F88B52005FAB25 /* DefaultViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DefaultViewController.h; sourceTree = ""; }; 56 | 53C0412120F88B52005FAB25 /* DefaultViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DefaultViewController.m; sourceTree = ""; }; 57 | 53C0412320F88D13005FAB25 /* CustomViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomViewController.h; sourceTree = ""; }; 58 | 53C0412420F88D13005FAB25 /* CustomViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomViewController.m; sourceTree = ""; }; 59 | 53C0412620F88D3E005FAB25 /* MyCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyCollectionViewCell.h; sourceTree = ""; }; 60 | 53C0412720F88D3E005FAB25 /* MyCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MyCollectionViewCell.m; sourceTree = ""; }; 61 | 53C0412820F88D3E005FAB25 /* MyCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyCollectionViewCell.xib; sourceTree = ""; }; 62 | 53C0412B20F88E05005FAB25 /* HideItemViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HideItemViewController.h; sourceTree = ""; }; 63 | 53C0412C20F88E05005FAB25 /* HideItemViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HideItemViewController.m; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 53C040ED20F60342005FAB25 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 53C040E720F60342005FAB25 = { 78 | isa = PBXGroup; 79 | children = ( 80 | 53C040F220F60342005FAB25 /* CollectionViewSideRefresh */, 81 | 53C040F120F60342005FAB25 /* Products */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | 53C040F120F60342005FAB25 /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 53C040F020F60342005FAB25 /* CollectionViewSideRefresh.app */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 53C040F220F60342005FAB25 /* CollectionViewSideRefresh */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 53C0410920F60359005FAB25 /* SideRefresh */, 97 | 53C0411C20F88B32005FAB25 /* SideRefreshDemo */, 98 | 53C040F320F60342005FAB25 /* AppDelegate.h */, 99 | 53C040F420F60342005FAB25 /* AppDelegate.m */, 100 | 53C040F620F60342005FAB25 /* ViewController.h */, 101 | 53C040F720F60342005FAB25 /* ViewController.m */, 102 | 53C040F920F60342005FAB25 /* Main.storyboard */, 103 | 53C040FC20F60343005FAB25 /* Assets.xcassets */, 104 | 53C040FE20F60343005FAB25 /* LaunchScreen.storyboard */, 105 | 53C0410120F60343005FAB25 /* Info.plist */, 106 | 53C0410220F60343005FAB25 /* main.m */, 107 | ); 108 | path = CollectionViewSideRefresh; 109 | sourceTree = ""; 110 | }; 111 | 53C0410920F60359005FAB25 /* SideRefresh */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 53C0410A20F60410005FAB25 /* SideRefreshHeader.h */, 115 | 53C0410B20F60410005FAB25 /* SideRefreshHeader.m */, 116 | 53C0410D20F6041F005FAB25 /* SideRefreshFooter.h */, 117 | 53C0410E20F6041F005FAB25 /* SideRefreshFooter.m */, 118 | 532FF95D2101A78800CC3A80 /* SideRefreshEmptyFooter.h */, 119 | 532FF95E2101A78800CC3A80 /* SideRefreshEmptyFooter.m */, 120 | 53C0411020F60435005FAB25 /* UICollectionView+SideRefresh.h */, 121 | 53C0411120F60435005FAB25 /* UICollectionView+SideRefresh.m */, 122 | 5341955E2148F7D1005EF096 /* UICollectionView+SideExtension.h */, 123 | 5341955F2148F7D1005EF096 /* UICollectionView+SideExtension.m */, 124 | 53C0411320F6065B005FAB25 /* SideRefresh.h */, 125 | 53C0411420F6065B005FAB25 /* SideRefresh.m */, 126 | ); 127 | path = SideRefresh; 128 | sourceTree = ""; 129 | }; 130 | 53C0411C20F88B32005FAB25 /* SideRefreshDemo */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 53C0412020F88B52005FAB25 /* DefaultViewController.h */, 134 | 53C0412120F88B52005FAB25 /* DefaultViewController.m */, 135 | 53C0412320F88D13005FAB25 /* CustomViewController.h */, 136 | 53C0412420F88D13005FAB25 /* CustomViewController.m */, 137 | 53C0412B20F88E05005FAB25 /* HideItemViewController.h */, 138 | 53C0412C20F88E05005FAB25 /* HideItemViewController.m */, 139 | 532FF9602103297E00CC3A80 /* EmptyFooterViewController.h */, 140 | 532FF9612103297E00CC3A80 /* EmptyFooterViewController.m */, 141 | 53C0412620F88D3E005FAB25 /* MyCollectionViewCell.h */, 142 | 53C0412720F88D3E005FAB25 /* MyCollectionViewCell.m */, 143 | 53C0412820F88D3E005FAB25 /* MyCollectionViewCell.xib */, 144 | ); 145 | path = SideRefreshDemo; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | 53C040EF20F60342005FAB25 /* CollectionViewSideRefresh */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 53C0410620F60343005FAB25 /* Build configuration list for PBXNativeTarget "CollectionViewSideRefresh" */; 154 | buildPhases = ( 155 | 53C040EC20F60342005FAB25 /* Sources */, 156 | 53C040ED20F60342005FAB25 /* Frameworks */, 157 | 53C040EE20F60342005FAB25 /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = CollectionViewSideRefresh; 164 | productName = CollectionViewSideRefresh; 165 | productReference = 53C040F020F60342005FAB25 /* CollectionViewSideRefresh.app */; 166 | productType = "com.apple.product-type.application"; 167 | }; 168 | /* End PBXNativeTarget section */ 169 | 170 | /* Begin PBXProject section */ 171 | 53C040E820F60342005FAB25 /* Project object */ = { 172 | isa = PBXProject; 173 | attributes = { 174 | LastUpgradeCheck = 0930; 175 | ORGANIZATIONNAME = DandJ; 176 | TargetAttributes = { 177 | 53C040EF20F60342005FAB25 = { 178 | CreatedOnToolsVersion = 9.3; 179 | }; 180 | }; 181 | }; 182 | buildConfigurationList = 53C040EB20F60342005FAB25 /* Build configuration list for PBXProject "CollectionViewSideRefresh" */; 183 | compatibilityVersion = "Xcode 9.3"; 184 | developmentRegion = en; 185 | hasScannedForEncodings = 0; 186 | knownRegions = ( 187 | en, 188 | Base, 189 | ); 190 | mainGroup = 53C040E720F60342005FAB25; 191 | productRefGroup = 53C040F120F60342005FAB25 /* Products */; 192 | projectDirPath = ""; 193 | projectRoot = ""; 194 | targets = ( 195 | 53C040EF20F60342005FAB25 /* CollectionViewSideRefresh */, 196 | ); 197 | }; 198 | /* End PBXProject section */ 199 | 200 | /* Begin PBXResourcesBuildPhase section */ 201 | 53C040EE20F60342005FAB25 /* Resources */ = { 202 | isa = PBXResourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | 53C0410020F60343005FAB25 /* LaunchScreen.storyboard in Resources */, 206 | 53C040FD20F60343005FAB25 /* Assets.xcassets in Resources */, 207 | 53C0412A20F88D3E005FAB25 /* MyCollectionViewCell.xib in Resources */, 208 | 53C040FB20F60342005FAB25 /* Main.storyboard in Resources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXResourcesBuildPhase section */ 213 | 214 | /* Begin PBXSourcesBuildPhase section */ 215 | 53C040EC20F60342005FAB25 /* Sources */ = { 216 | isa = PBXSourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 53C040F820F60342005FAB25 /* ViewController.m in Sources */, 220 | 532FF9622103297E00CC3A80 /* EmptyFooterViewController.m in Sources */, 221 | 532FF95F2101A78800CC3A80 /* SideRefreshEmptyFooter.m in Sources */, 222 | 53C0412520F88D13005FAB25 /* CustomViewController.m in Sources */, 223 | 53C0410F20F6041F005FAB25 /* SideRefreshFooter.m in Sources */, 224 | 53C0410320F60343005FAB25 /* main.m in Sources */, 225 | 53C0412920F88D3E005FAB25 /* MyCollectionViewCell.m in Sources */, 226 | 53C0411520F6065B005FAB25 /* SideRefresh.m in Sources */, 227 | 53C0411220F60435005FAB25 /* UICollectionView+SideRefresh.m in Sources */, 228 | 53C040F520F60342005FAB25 /* AppDelegate.m in Sources */, 229 | 53C0412220F88B52005FAB25 /* DefaultViewController.m in Sources */, 230 | 53C0410C20F60410005FAB25 /* SideRefreshHeader.m in Sources */, 231 | 534195602148F7D1005EF096 /* UICollectionView+SideExtension.m in Sources */, 232 | 53C0412D20F88E05005FAB25 /* HideItemViewController.m in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXSourcesBuildPhase section */ 237 | 238 | /* Begin PBXVariantGroup section */ 239 | 53C040F920F60342005FAB25 /* Main.storyboard */ = { 240 | isa = PBXVariantGroup; 241 | children = ( 242 | 53C040FA20F60342005FAB25 /* Base */, 243 | ); 244 | name = Main.storyboard; 245 | sourceTree = ""; 246 | }; 247 | 53C040FE20F60343005FAB25 /* LaunchScreen.storyboard */ = { 248 | isa = PBXVariantGroup; 249 | children = ( 250 | 53C040FF20F60343005FAB25 /* Base */, 251 | ); 252 | name = LaunchScreen.storyboard; 253 | sourceTree = ""; 254 | }; 255 | /* End PBXVariantGroup section */ 256 | 257 | /* Begin XCBuildConfiguration section */ 258 | 53C0410420F60343005FAB25 /* Debug */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | ALWAYS_SEARCH_USER_PATHS = NO; 262 | CLANG_ANALYZER_NONNULL = YES; 263 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_ENABLE_OBJC_WEAK = YES; 269 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_COMMA = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 285 | CLANG_WARN_STRICT_PROTOTYPES = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 287 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 288 | CLANG_WARN_UNREACHABLE_CODE = YES; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | CODE_SIGN_IDENTITY = "iPhone Developer"; 291 | COPY_PHASE_STRIP = NO; 292 | DEBUG_INFORMATION_FORMAT = dwarf; 293 | ENABLE_STRICT_OBJC_MSGSEND = YES; 294 | ENABLE_TESTABILITY = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu11; 296 | GCC_DYNAMIC_NO_PIC = NO; 297 | GCC_NO_COMMON_BLOCKS = YES; 298 | GCC_OPTIMIZATION_LEVEL = 0; 299 | GCC_PREPROCESSOR_DEFINITIONS = ( 300 | "DEBUG=1", 301 | "$(inherited)", 302 | ); 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 310 | MTL_ENABLE_DEBUG_INFO = YES; 311 | ONLY_ACTIVE_ARCH = YES; 312 | SDKROOT = iphoneos; 313 | }; 314 | name = Debug; 315 | }; 316 | 53C0410520F60343005FAB25 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ALWAYS_SEARCH_USER_PATHS = NO; 320 | CLANG_ANALYZER_NONNULL = YES; 321 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_ENABLE_OBJC_WEAK = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INFINITE_RECURSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 343 | CLANG_WARN_STRICT_PROTOTYPES = YES; 344 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 345 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | CODE_SIGN_IDENTITY = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | SDKROOT = iphoneos; 364 | VALIDATE_PRODUCT = YES; 365 | }; 366 | name = Release; 367 | }; 368 | 53C0410720F60343005FAB25 /* Debug */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | CODE_SIGN_STYLE = Automatic; 373 | DEVELOPMENT_TEAM = 4QA4S2484R; 374 | INFOPLIST_FILE = CollectionViewSideRefresh/Info.plist; 375 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 376 | LD_RUNPATH_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "@executable_path/Frameworks", 379 | ); 380 | PRODUCT_BUNDLE_IDENTIFIER = com.DandJ.CollectionViewSideRefresh; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Debug; 385 | }; 386 | 53C0410820F60343005FAB25 /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 390 | CODE_SIGN_STYLE = Automatic; 391 | DEVELOPMENT_TEAM = 4QA4S2484R; 392 | INFOPLIST_FILE = CollectionViewSideRefresh/Info.plist; 393 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 394 | LD_RUNPATH_SEARCH_PATHS = ( 395 | "$(inherited)", 396 | "@executable_path/Frameworks", 397 | ); 398 | PRODUCT_BUNDLE_IDENTIFIER = com.DandJ.CollectionViewSideRefresh; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Release; 403 | }; 404 | /* End XCBuildConfiguration section */ 405 | 406 | /* Begin XCConfigurationList section */ 407 | 53C040EB20F60342005FAB25 /* Build configuration list for PBXProject "CollectionViewSideRefresh" */ = { 408 | isa = XCConfigurationList; 409 | buildConfigurations = ( 410 | 53C0410420F60343005FAB25 /* Debug */, 411 | 53C0410520F60343005FAB25 /* Release */, 412 | ); 413 | defaultConfigurationIsVisible = 0; 414 | defaultConfigurationName = Release; 415 | }; 416 | 53C0410620F60343005FAB25 /* Build configuration list for PBXNativeTarget "CollectionViewSideRefresh" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 53C0410720F60343005FAB25 /* Debug */, 420 | 53C0410820F60343005FAB25 /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | /* End XCConfigurationList section */ 426 | }; 427 | rootObject = 53C040E820F60342005FAB25 /* Project object */; 428 | } 429 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh.xcodeproj/project.xcworkspace/xcuserdata/hcb.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh.xcodeproj/project.xcworkspace/xcuserdata/hcb.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CollectionViewSideRefresh.xcodeproj/xcuserdata/hcb.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh.xcodeproj/xcuserdata/hcb.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CollectionViewSideRefresh.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. 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 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_01.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_01.imageset/loading_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_01.imageset/loading_01.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_02.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_02.imageset/loading_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_02.imageset/loading_02.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_03.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_03.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_03.imageset/loading_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_03.imageset/loading_03.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_04.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_04.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_04.imageset/loading_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_04.imageset/loading_04.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_05.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_05.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_05.imageset/loading_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_05.imageset/loading_05.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_06.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_06.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_06.imageset/loading_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_06.imageset/loading_06.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_07.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_07.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_07.imageset/loading_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_07.imageset/loading_07.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_08.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_08.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_08.imageset/loading_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_08.imageset/loading_08.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_09.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "loading_09.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Assets.xcassets/loading_09.imageset/loading_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/CollectionViewSideRefresh/Assets.xcassets/loading_09.imageset/loading_09.png -------------------------------------------------------------------------------- /CollectionViewSideRefresh/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 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/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 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 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 | 200 | 207 | 214 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefresh.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //刷新条宽度 12 | static const CGFloat SideRefreshWidth = 50.0; 13 | 14 | /** 15 | 刷新状态 16 | 17 | - SideRefreshStatusNormal: 常规状态 18 | - SideRefreshStatusPulling: 即将刷新 19 | - SideRefreshStatusLoading: 刷新中 20 | */ 21 | typedef NS_ENUM(NSUInteger, SideRefreshStatus) { 22 | SideRefreshStatusNormal, 23 | SideRefreshStatusPulling, 24 | SideRefreshStatusLoading, 25 | }; 26 | 27 | /** 28 | 刷新回调 29 | */ 30 | typedef void(^loadAction)(void); 31 | 32 | @interface SideRefresh : UIView 33 | 34 | /** 35 | 依附的collectionView 36 | */ 37 | @property (nonatomic, weak) UICollectionView *collectionView; 38 | 39 | /** 40 | 刷新状态 41 | */ 42 | @property (nonatomic, assign) SideRefreshStatus refreshStatus; 43 | 44 | /** 45 | 旧的刷新状态 46 | */ 47 | @property (nonatomic, assign) SideRefreshStatus oldRefreshStatus; 48 | 49 | /** 50 | 拉动提示 51 | */ 52 | @property (nonatomic, copy) NSString *normalMessage; 53 | 54 | /** 55 | 即将刷新的提示 56 | */ 57 | @property (nonatomic, copy) NSString *pullingMessage; 58 | 59 | /** 60 | 加载中提示 61 | */ 62 | @property (nonatomic, copy) NSString *loadingMessage; 63 | 64 | /** 65 | 自定义的加载动画图片组 66 | */ 67 | @property (nonatomic, strong) NSArray *loadingImages; 68 | 69 | /** 70 | 提示文本 71 | */ 72 | @property (nonatomic, strong) UILabel *messageLabel; 73 | 74 | /** 75 | 自定义的加载图片显示视图 76 | */ 77 | @property (nonatomic, strong) UIImageView *loadingImageView; 78 | 79 | /** 80 | 默认菊花 81 | */ 82 | @property (nonatomic, strong) UIActivityIndicatorView *defaultIndicator; 83 | 84 | /** 85 | 原始的ContentInset 86 | */ 87 | @property (nonatomic, assign) UIEdgeInsets originalContentInset; 88 | 89 | /** 90 | 是否支持page 91 | */ 92 | @property (nonatomic, assign) BOOL collectionViewPageEnabel; 93 | 94 | /** 95 | 隐藏文本 96 | */ 97 | @property (nonatomic, assign) BOOL hideMessage; 98 | 99 | /** 100 | 隐藏加载图片 101 | */ 102 | @property (nonatomic, assign) BOOL hideIndicator; 103 | 104 | /** 105 | 刷新执行回调 106 | */ 107 | @property (nonatomic, copy) loadAction loadActionBlock; 108 | 109 | /** 110 | 初始化 111 | 112 | @param loadActionBlock 刷新回调 113 | @return 实例 114 | */ 115 | + (instancetype)refreshWithLoadAction:(loadAction)loadActionBlock; 116 | 117 | /** 118 | 开始刷新 119 | */ 120 | - (void)beginLoading; 121 | 122 | /** 123 | 结束刷新 124 | */ 125 | - (void)endLoading; 126 | 127 | /** 128 | 执行刷新回调 129 | */ 130 | - (void)startLoadingAction; 131 | 132 | /** 133 | 设置常规状态ContentOffset 134 | */ 135 | - (void)updateContentOffsetToNormal; 136 | 137 | /** 138 | 设置刷新状态ContentOffset 139 | */ 140 | - (void)updateContentOffsetToLoading; 141 | 142 | /** 143 | 是否到达可刷新状态 144 | 145 | @return YES 是,NO否 146 | */ 147 | - (BOOL)pullToReadyRefresh; 148 | 149 | /** 150 | 重置刷新视图位置 151 | */ 152 | - (void)resetRefreshFrame; 153 | 154 | /** 155 | 保持刷新状态的offset 156 | */ 157 | - (void)keepRefreshingOffset; 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefresh.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "SideRefresh.h" 10 | #import "UICollectionView+SideExtension.h" 11 | #import "UICollectionView+SideRefresh.h" 12 | 13 | @implementation SideRefresh 14 | 15 | - (void)willMoveToSuperview:(UIView *)newSuperview { 16 | [super willMoveToSuperview:newSuperview]; 17 | [self removeObserver]; 18 | if ([newSuperview isKindOfClass:[UICollectionView class]]) { 19 | self.collectionView = (UICollectionView *)newSuperview; 20 | self.collectionView.alwaysBounceHorizontal = YES; 21 | self.originalContentInset = self.collectionView.side_inset; 22 | self.collectionViewPageEnabel = self.collectionView.pagingEnabled; 23 | [self addObserver]; 24 | } 25 | } 26 | 27 | - (void)startLoadingAction { 28 | if(self.loadActionBlock && self.superview) { 29 | self.loadActionBlock(); 30 | } 31 | } 32 | 33 | - (void)beginLoading { 34 | self.refreshStatus = SideRefreshStatusLoading; 35 | } 36 | 37 | - (void)endLoading { 38 | self.refreshStatus = SideRefreshStatusNormal; 39 | [self.collectionView hideEmptyFooter]; 40 | } 41 | 42 | - (void)removeObserver { 43 | [self.superview removeObserver:self forKeyPath:@"contentOffset"]; 44 | [self.superview removeObserver:self forKeyPath:@"contentSize"]; 45 | } 46 | 47 | - (void)addObserver { 48 | NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; 49 | [self.collectionView addObserver:self forKeyPath:@"contentOffset" options:options context:nil]; 50 | [self.collectionView addObserver:self forKeyPath:@"contentSize" options:options context:nil]; 51 | } 52 | 53 | #pragma mark - ContentOffset Observer 54 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 55 | if(![self.superview isEqual:self.collectionView]) { 56 | return; 57 | } 58 | if([keyPath isEqualToString:@"contentOffset"]) { 59 | [self handleContentOffsetChanged]; 60 | } 61 | else if([keyPath isEqualToString:@"contentSize"]) { 62 | [self handleContentSizeChanged]; 63 | } 64 | } 65 | 66 | - (void)handleContentOffsetChanged { 67 | if(self.collectionView.dragging) { 68 | if([self pullToReadyRefresh]) { 69 | if(self.refreshStatus == SideRefreshStatusNormal) { 70 | self.refreshStatus = SideRefreshStatusPulling; 71 | } 72 | } else { 73 | if(self.refreshStatus == SideRefreshStatusPulling) { 74 | self.refreshStatus = SideRefreshStatusNormal; 75 | } else if(self.refreshStatus == SideRefreshStatusLoading) { 76 | // self.collectionView.pagingEnabled = NO; 77 | self.refreshStatus = SideRefreshStatusNormal; 78 | } 79 | } 80 | } else { 81 | if(self.refreshStatus == SideRefreshStatusPulling) { 82 | self.refreshStatus = SideRefreshStatusLoading; 83 | } 84 | else if(self.refreshStatus == SideRefreshStatusLoading) { 85 | [self keepRefreshingOffset]; 86 | } 87 | } 88 | [self updateRefreshFrame]; 89 | } 90 | 91 | - (void)handleContentSizeChanged { 92 | [self resetRefreshFrame]; 93 | } 94 | 95 | - (void)updateRefreshFrame { 96 | if(!self.hidden) { 97 | return; 98 | } 99 | [self resetRefreshFrame]; 100 | [self updateRefreshContentFrame]; 101 | } 102 | 103 | - (void)updateRefreshContentFrame { 104 | CGFloat indicatorWidth = 20.0; 105 | self.backgroundColor = self.collectionView.backgroundColor; 106 | 107 | CGFloat imgFitY = (self.frame.size.height / 2) - (indicatorWidth / 2); 108 | if(self.hideMessage) { 109 | self.messageLabel.hidden = YES; 110 | } else { 111 | self.messageLabel.frame = CGRectMake(15, 0, 15, self.collectionView.frame.size.height); 112 | NSString *maxLengthMessage = self.normalMessage; 113 | if(self.pullingMessage.length > maxLengthMessage.length) { 114 | maxLengthMessage = self.pullingMessage; 115 | } 116 | if(self.loadingMessage.length > maxLengthMessage.length) { 117 | maxLengthMessage = self.loadingMessage; 118 | } 119 | CGSize messageSize = [maxLengthMessage boundingRectWithSize:CGSizeMake(15, CGFLOAT_MAX) 120 | options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin 121 | attributes:@{NSFontAttributeName:self.messageLabel.font} 122 | context:nil].size; 123 | imgFitY = (self.collectionView.frame.size.height - messageSize.height)/2 - indicatorWidth - 20; 124 | } 125 | 126 | if(self.hideIndicator) { 127 | self.loadingImageView.hidden = YES; 128 | [self.defaultIndicator removeFromSuperview]; 129 | } else { 130 | self.loadingImageView.hidden = NO; 131 | if([self.loadingImages count] > 0) { 132 | [self.defaultIndicator removeFromSuperview]; 133 | self.loadingImageView.frame = CGRectMake(12, imgFitY, indicatorWidth, indicatorWidth); 134 | self.loadingImageView.animationImages = self.loadingImages; 135 | self.loadingImageView.animationDuration = 0.5; 136 | self.loadingImageView.animationRepeatCount = NSIntegerMax; 137 | self.loadingImageView.image = self.loadingImages.firstObject; 138 | } else { 139 | self.loadingImageView.hidden = YES; 140 | self.defaultIndicator.frame = CGRectMake(12, imgFitY, indicatorWidth, indicatorWidth); 141 | self.defaultIndicator.hidesWhenStopped = NO; 142 | } 143 | } 144 | } 145 | 146 | 147 | #pragma mark - Child Recognize 148 | + (instancetype)refreshWithLoadAction:(loadAction)loadActionBlock { 149 | [self doesNotRecognizeSelector:_cmd]; 150 | return nil; 151 | } 152 | 153 | - (void)updateContentOffsetToNormal { 154 | [self doesNotRecognizeSelector:_cmd]; 155 | } 156 | 157 | 158 | - (void)updateContentOffsetToLoading { 159 | [self doesNotRecognizeSelector:_cmd]; 160 | } 161 | 162 | - (BOOL)pullToReadyRefresh { 163 | [self doesNotRecognizeSelector:_cmd]; 164 | return NO; 165 | } 166 | 167 | - (void)resetRefreshFrame { 168 | [self doesNotRecognizeSelector:_cmd]; 169 | } 170 | 171 | - (void)keepRefreshingOffset { 172 | [self doesNotRecognizeSelector:_cmd]; 173 | } 174 | 175 | #pragma mark - setter & getter 176 | - (void)setRefreshStatus:(SideRefreshStatus)refreshStatus { 177 | _refreshStatus = refreshStatus; 178 | switch (refreshStatus) { 179 | case SideRefreshStatusNormal: 180 | self.messageLabel.text = self.normalMessage; 181 | if(self.oldRefreshStatus == SideRefreshStatusLoading) { 182 | [self updateContentOffsetToNormal]; 183 | [self.loadingImageView stopAnimating]; 184 | [self.defaultIndicator stopAnimating]; 185 | } 186 | break; 187 | case SideRefreshStatusPulling: 188 | self.messageLabel.text = self.pullingMessage; 189 | break; 190 | case SideRefreshStatusLoading: 191 | self.messageLabel.text = self.loadingMessage; 192 | [self updateContentOffsetToLoading]; 193 | [self startLoadingAction]; 194 | [self.loadingImageView startAnimating]; 195 | [self.defaultIndicator startAnimating]; 196 | break; 197 | } 198 | self.oldRefreshStatus = refreshStatus; 199 | } 200 | 201 | - (UIImageView *)loadingImageView { 202 | if(!_loadingImageView) { 203 | _loadingImageView = [[UIImageView alloc] init]; 204 | [self addSubview:_loadingImageView]; 205 | } 206 | return _loadingImageView; 207 | } 208 | 209 | - (UILabel *)messageLabel { 210 | if(!_messageLabel) { 211 | _messageLabel = [[UILabel alloc] init]; 212 | _messageLabel.numberOfLines = 0; 213 | _messageLabel.font = [UIFont systemFontOfSize:12]; 214 | _messageLabel.textColor = [UIColor colorWithRed:102/255.0 green:102/255.0 blue:102/255.0 alpha:1/1.0]; 215 | _messageLabel.textAlignment = NSTextAlignmentCenter; 216 | _messageLabel.text = self.normalMessage; 217 | [self addSubview:_messageLabel]; 218 | } 219 | return _messageLabel; 220 | } 221 | 222 | - (UIActivityIndicatorView *)defaultIndicator { 223 | if(!_defaultIndicator) { 224 | _defaultIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 225 | [self addSubview:_defaultIndicator]; 226 | } 227 | return _defaultIndicator; 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefreshEmptyFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefreshEmptyFooter.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/20. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static const CGFloat SideRefreshEmptyFooterWidth = 50.0; 12 | 13 | @interface SideRefreshEmptyFooter : UIView 14 | 15 | @property (nonatomic, copy) NSString *message; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefreshEmptyFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefreshEmptyFooter.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/20. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "SideRefreshEmptyFooter.h" 10 | 11 | @interface SideRefreshEmptyFooter() 12 | 13 | @property (nonatomic, strong) UILabel *messageLabel; 14 | 15 | @property (nonatomic, weak) UICollectionView *collectionView; 16 | 17 | @end 18 | 19 | @implementation SideRefreshEmptyFooter 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame { 22 | self = [super initWithFrame:frame]; 23 | if(self) { 24 | [self setupMessageLabel]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)willMoveToSuperview:(UIView *)newSuperview { 30 | [super willMoveToSuperview:newSuperview]; 31 | [self.superview removeObserver:self forKeyPath:@"contentSize"]; 32 | if([newSuperview isKindOfClass:[UICollectionView class]]) { 33 | self.collectionView = (UICollectionView *)newSuperview; 34 | NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; 35 | [self.collectionView addObserver:self forKeyPath:@"contentSize" options:options context:nil]; 36 | self.backgroundColor = self.collectionView.backgroundColor; 37 | } 38 | } 39 | 40 | - (void)setMessage:(NSString *)message { 41 | _message = message; 42 | self.messageLabel.text = message; 43 | } 44 | 45 | - (void)setupMessageLabel { 46 | CGFloat labelWidth = 15; 47 | CGFloat labelX = (self.frame.size.width - labelWidth) * 0.5; 48 | _messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelX, 0, labelWidth, self.frame.size.height)]; 49 | _messageLabel.numberOfLines = 0; 50 | _messageLabel.font = [UIFont systemFontOfSize:12]; 51 | _messageLabel.textColor = [UIColor colorWithRed:102/255.0 green:102/255.0 blue:102/255.0 alpha:1/1.0]; 52 | _messageLabel.textAlignment = NSTextAlignmentCenter; 53 | [self addSubview:_messageLabel]; 54 | } 55 | 56 | #pragma mark - ContentOffset Observer 57 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 58 | if([keyPath isEqualToString:@"contentSize"]) { 59 | CGFloat frameX = self.collectionView.contentSize.width + self.collectionView.contentInset.right; 60 | self.frame = CGRectMake(frameX, self.frame.origin.y, self.frame.size.width, self.frame.size.height); 61 | } 62 | } 63 | @end 64 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefreshFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefreshFooter.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "SideRefresh.h" 10 | 11 | @interface SideRefreshFooter : SideRefresh 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefreshFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefreshFooter.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "SideRefreshFooter.h" 10 | #import "UICollectionView+SideRefresh.h" 11 | #import "UICollectionView+SideExtension.h" 12 | 13 | @implementation SideRefreshFooter 14 | 15 | + (instancetype)refreshWithLoadAction:(loadAction)loadActionBlock { 16 | SideRefreshFooter *freshFooter = [[SideRefreshFooter alloc] init]; 17 | freshFooter.loadActionBlock = loadActionBlock; 18 | return freshFooter; 19 | } 20 | 21 | - (instancetype)init { 22 | self = [super init]; 23 | if(self) { 24 | self.normalMessage = @"左拉加载更多..."; 25 | self.pullingMessage = @"松开加载..."; 26 | self.loadingMessage = @"加载中..."; 27 | self.hidden = YES; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)updateContentOffsetToNormal { 33 | __weak typeof(self) weakSelf = self; 34 | dispatch_async(dispatch_get_main_queue(), ^{ 35 | __strong __typeof(self) strongSelf = weakSelf; 36 | if(!strongSelf)return; 37 | [strongSelf.collectionView setSide_insetR:strongSelf.originalContentInset.right]; 38 | }); 39 | if(self.collectionViewPageEnabel) { 40 | [UIView animateWithDuration:0.3 animations:^{ 41 | [self.collectionView setSide_insetR:self.originalContentInset.right]; 42 | }]; 43 | } 44 | } 45 | 46 | - (void)updateContentOffsetToLoading { 47 | if(self.collectionView.isShowEmptyFooter) { 48 | [self resetRefreshFrame]; 49 | return; 50 | } 51 | CGFloat targetRight = self.originalContentInset.right + SideRefreshWidth; 52 | CGFloat showWidth = self.collectionView.frame.size.width - self.originalContentInset.right; 53 | CGFloat spaceWidth = showWidth - self.collectionView.contentSize.width; 54 | [UIView animateWithDuration:0.3 animations:^{ 55 | if(spaceWidth > 0) {//应当考虑content显示不完整个CollectionView的情况 56 | [self.collectionView setSide_insetR:(targetRight + spaceWidth)]; 57 | } else { 58 | [self.collectionView setSide_insetR:targetRight]; 59 | } 60 | }]; 61 | } 62 | 63 | - (BOOL)pullToReadyRefresh { 64 | CGFloat offsetX = self.collectionView.contentOffset.x; 65 | CGFloat mostRightOffset = self.collectionView.contentSize.width - self.collectionView.frame.size.width; 66 | if(mostRightOffset < 0) {//考虑Content显示不完整个CollectionView的情况 67 | mostRightOffset = 0; 68 | } 69 | CGFloat targetX = SideRefreshWidth + mostRightOffset; 70 | return offsetX > targetX; 71 | } 72 | 73 | - (void)resetRefreshFrame { 74 | CGFloat frameX = self.collectionView.contentSize.width + self.originalContentInset.right; 75 | self.frame = CGRectMake(frameX, 0, SideRefreshWidth, self.collectionView.frame.size.height); 76 | if(!(self.collectionView.contentSize.width > self.collectionView.frame.size.width)) { 77 | self.frame = CGRectMake(self.collectionView.frame.size.width, 0, SideRefreshWidth, self.collectionView.frame.size.height); 78 | } 79 | self.hidden = NO; 80 | } 81 | 82 | - (void)keepRefreshingOffset { 83 | if(!self.collectionView.pagingEnabled) { 84 | return; 85 | } 86 | CGFloat targetX = SideRefreshWidth + self.originalContentInset.right + self.collectionView.contentSize.width - self.collectionView.frame.size.width; 87 | [self.collectionView setContentOffset:CGPointMake(targetX, self.collectionView.contentOffset.y) animated:NO]; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefreshHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefreshHeader.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "SideRefresh.h" 10 | 11 | @interface SideRefreshHeader : SideRefresh 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/SideRefreshHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // SideRefreshHeader.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "SideRefreshHeader.h" 10 | #import "UICollectionView+SideExtension.h" 11 | 12 | @implementation SideRefreshHeader 13 | 14 | + (instancetype)refreshWithLoadAction:(loadAction)loadActionBlock { 15 | SideRefreshHeader *freshHeader = [[SideRefreshHeader alloc] init]; 16 | freshHeader.loadActionBlock = loadActionBlock; 17 | return freshHeader; 18 | } 19 | 20 | - (instancetype)init { 21 | self = [super init]; 22 | if(self) { 23 | self.normalMessage = @"右拉刷新..."; 24 | self.pullingMessage = @"松开刷新..."; 25 | self.loadingMessage = @"刷新中..."; 26 | self.hidden = YES; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)updateContentOffsetToNormal { 32 | self.collectionView.pagingEnabled = self.collectionViewPageEnabel; 33 | [UIView animateWithDuration:0.3 animations:^{ 34 | [self.collectionView setContentInset:self.originalContentInset]; 35 | }]; 36 | } 37 | 38 | - (void)updateContentOffsetToLoading { 39 | self.collectionView.pagingEnabled = NO; 40 | CGFloat targetLeft = (SideRefreshWidth + self.originalContentInset.left); 41 | [UIView animateWithDuration:0.3 animations:^{ 42 | [self.collectionView setSide_insetL:targetLeft]; 43 | }]; 44 | } 45 | 46 | - (BOOL)pullToReadyRefresh { 47 | CGFloat offsetX = self.collectionView.contentOffset.x * -1; 48 | offsetX = offsetX - self.collectionView.contentInset.left; 49 | return offsetX > SideRefreshWidth; 50 | } 51 | 52 | - (void)resetRefreshFrame { 53 | CGFloat frameX = (SideRefreshWidth + self.originalContentInset.left) * - 1; 54 | self.frame = CGRectMake(frameX, 0, SideRefreshWidth, self.collectionView.frame.size.height); 55 | self.hidden = NO; 56 | } 57 | 58 | - (void)keepRefreshingOffset { 59 | if(!self.collectionViewPageEnabel) { 60 | return; 61 | } 62 | CGFloat targetX = (SideRefreshWidth + self.originalContentInset.left) * - 1; 63 | [self.collectionView setContentOffset:CGPointMake(targetX, self.collectionView.contentOffset.y) animated:NO]; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/UICollectionView+SideExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+SideExtension.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by dangercheng on 2018/9/12. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UICollectionView (SideExtension) 12 | @property (nonatomic, assign, readonly) UIEdgeInsets side_inset; 13 | @property (nonatomic, assign) CGFloat side_insetL; 14 | @property (nonatomic, assign) CGFloat side_insetR; 15 | @end 16 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/UICollectionView+SideExtension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+SideExtension.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by dangercheng on 2018/9/12. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "UICollectionView+SideExtension.h" 10 | 11 | @implementation UICollectionView (SideExtension) 12 | static BOOL respondsToAdjustedContentInset_; 13 | 14 | + (void)initialize 15 | { 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | respondsToAdjustedContentInset_ = [self instancesRespondToSelector:@selector(adjustedContentInset)]; 19 | }); 20 | } 21 | 22 | - (UIEdgeInsets)side_inset 23 | { 24 | #ifdef __IPHONE_11_0 25 | if (respondsToAdjustedContentInset_) { 26 | return self.adjustedContentInset; 27 | } 28 | #endif 29 | return self.contentInset; 30 | } 31 | 32 | - (CGFloat)side_insetL 33 | { 34 | return self.side_inset.left; 35 | } 36 | 37 | - (void)setSide_insetL:(CGFloat)side_insetL 38 | { 39 | UIEdgeInsets inset = self.contentInset; 40 | inset.left = side_insetL; 41 | #ifdef __IPHONE_11_0 42 | if (respondsToAdjustedContentInset_) { 43 | inset.left -= (self.adjustedContentInset.left - self.contentInset.left); 44 | } 45 | #endif 46 | self.contentInset = inset; 47 | } 48 | 49 | - (CGFloat)side_insetR { 50 | return self.side_inset.right; 51 | } 52 | 53 | - (void)setSide_insetR:(CGFloat)side_insetR { 54 | UIEdgeInsets inset = self.contentInset; 55 | inset.right = side_insetR; 56 | #ifdef __IPHONE_11_0 57 | if (respondsToAdjustedContentInset_) { 58 | inset.right -= (self.adjustedContentInset.right - self.contentInset.right); 59 | } 60 | #endif 61 | self.contentInset = inset; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/UICollectionView+SideRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+SideRefresh.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SideRefreshHeader.h" 11 | #import "SideRefreshFooter.h" 12 | #import "SideRefreshEmptyFooter.h" 13 | 14 | @interface UICollectionView (SideRefresh) 15 | 16 | /** 17 | 刷新头 18 | */ 19 | @property (nonatomic, strong) SideRefreshFooter *sideRefreshFooter; 20 | 21 | /** 22 | 刷新脚 23 | */ 24 | @property (nonatomic, strong) SideRefreshHeader *sideRefreshHeader; 25 | 26 | /** 27 | 是否已显示空刷新脚 28 | */ 29 | @property (nonatomic, assign, readonly) BOOL isShowEmptyFooter; 30 | 31 | /** 32 | 显示默认空刷新脚,默认提示:"没有更多数据了" 33 | */ 34 | - (void)showEmptyFooter; 35 | 36 | /** 37 | 显示自定义提示内容的空刷新脚 38 | 39 | @param message 提示内容 40 | */ 41 | - (void)showEmptyFooterWithMessage:(NSString *)message; 42 | 43 | /** 44 | 隐藏空刷新脚 45 | */ 46 | - (void)hideEmptyFooter; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefresh/UICollectionView+SideRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+SideRefresh.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "UICollectionView+SideRefresh.h" 10 | #import 11 | 12 | @interface UICollectionView () 13 | @property (nonatomic, strong) SideRefreshEmptyFooter *sideRefreshEmptyFooter; 14 | @end 15 | 16 | @implementation UICollectionView (SideRefresh) 17 | 18 | static const NSString *SideRefreshHeaderKey = @"SideRefreshHeaderKey"; 19 | static const NSString *SideRefreshFooterKey = @"SideRefreshFooterKey"; 20 | static const NSString *SideRefreshEmptyFooterKey = @"SideRefreshEmptyFooterKey"; 21 | static const NSString *SideRefreshIsShowEmptyFooterKey = @"SideRefreshIsShowEmptyFooterKey"; 22 | 23 | - (void)setSideRefreshFooter:(SideRefreshFooter *)sideRefreshFooter { 24 | if(self.sideRefreshFooter != sideRefreshFooter) { 25 | [self.sideRefreshFooter removeFromSuperview]; 26 | [self willChangeValueForKey:@"sideRefreshFooter"]; 27 | objc_setAssociatedObject(self, &SideRefreshFooterKey, sideRefreshFooter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 28 | [self didChangeValueForKey:@"sideRefreshFooter"]; 29 | [self addSubview:sideRefreshFooter]; 30 | } 31 | if(self.sideRefreshEmptyFooter) { 32 | [self.sideRefreshEmptyFooter removeFromSuperview]; 33 | } 34 | self.isShowEmptyFooter = NO; 35 | } 36 | 37 | - (SideRefreshFooter *)sideRefreshFooter { 38 | return objc_getAssociatedObject(self, &SideRefreshFooterKey); 39 | } 40 | 41 | - (void)setSideRefreshHeader:(SideRefreshHeader *)sideRefreshHeader { 42 | if(self.sideRefreshHeader != sideRefreshHeader) { 43 | [self.sideRefreshHeader removeFromSuperview]; 44 | [self willChangeValueForKey:@"sideRefreshHeader"]; 45 | objc_setAssociatedObject(self, &SideRefreshHeaderKey, sideRefreshHeader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 46 | [self didChangeValueForKey:@"sideRefreshHeader"]; 47 | [self insertSubview:sideRefreshHeader atIndex:0]; 48 | } 49 | } 50 | 51 | - (SideRefreshHeader *)sideRefreshHeader { 52 | return objc_getAssociatedObject(self, &SideRefreshHeaderKey); 53 | } 54 | 55 | - (void)setSideRefreshEmptyFooter:(SideRefreshEmptyFooter *)sideRefreshEmptyFooter { 56 | if(self.sideRefreshEmptyFooter != sideRefreshEmptyFooter) { 57 | [self.sideRefreshEmptyFooter removeFromSuperview]; 58 | [self willChangeValueForKey:@"sideRefreshEmptyFooter"]; 59 | objc_setAssociatedObject(self, &SideRefreshEmptyFooterKey, sideRefreshEmptyFooter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 60 | [self didChangeValueForKey:@"sideRefreshEmptyFooter"]; 61 | [self addSubview:sideRefreshEmptyFooter]; 62 | } 63 | } 64 | 65 | - (SideRefreshEmptyFooter *)sideRefreshEmptyFooter { 66 | return objc_getAssociatedObject(self, &SideRefreshEmptyFooterKey); 67 | } 68 | 69 | - (void)setIsShowEmptyFooter:(BOOL)isShowEmptyFooter { 70 | if(self.isShowEmptyFooter != isShowEmptyFooter) { 71 | objc_setAssociatedObject(self, &SideRefreshIsShowEmptyFooterKey, [NSNumber numberWithBool:isShowEmptyFooter], OBJC_ASSOCIATION_ASSIGN); 72 | } 73 | } 74 | 75 | - (BOOL)isShowEmptyFooter { 76 | return [objc_getAssociatedObject(self, &SideRefreshIsShowEmptyFooterKey) boolValue]; 77 | } 78 | 79 | - (void)showEmptyFooter { 80 | [self showEmptyFooterWithMessage:nil]; 81 | } 82 | 83 | - (void)showEmptyFooterWithMessage:(NSString *)message { 84 | if(!message) { 85 | message = @"没有更多数据了"; 86 | } 87 | [self.sideRefreshFooter removeFromSuperview]; 88 | CGFloat frameX = self.contentSize.width + self.contentInset.right; 89 | self.sideRefreshEmptyFooter = [[SideRefreshEmptyFooter alloc] initWithFrame:CGRectMake(frameX, 0, SideRefreshEmptyFooterWidth, self.frame.size.height)]; 90 | self.sideRefreshEmptyFooter.message = message; 91 | self.isShowEmptyFooter = YES; 92 | } 93 | 94 | - (void)hideEmptyFooter { 95 | [self.sideRefreshEmptyFooter removeFromSuperview]; 96 | self.isShowEmptyFooter = NO; 97 | if(self.sideRefreshFooter) { 98 | [self addSubview:self.sideRefreshFooter]; 99 | } 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/CustomViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomViewController.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CustomViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/CustomViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomViewController.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "CustomViewController.h" 10 | #import "UICollectionView+SideRefresh.h" 11 | #import "MyCollectionViewCell.h" 12 | 13 | @interface CustomViewController () 14 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 15 | @property (nonatomic, assign) CGFloat itemCount; 16 | @end 17 | 18 | @implementation CustomViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | // Do any additional setup after loading the view, typically from a nib. 23 | self.title = @"自定义"; 24 | self.itemCount = 0; 25 | 26 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 27 | flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 28 | flowLayout.itemSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300.0); 29 | flowLayout.minimumLineSpacing = 0.0; 30 | 31 | self.collectionView.collectionViewLayout = flowLayout; 32 | self.collectionView.dataSource = self; 33 | [self.collectionView registerNib:[UINib nibWithNibName:@"MyCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"MyCollectionViewCell"]; 34 | 35 | [self setupRefreshHeaderFoorer]; 36 | } 37 | - (void)viewDidAppear:(BOOL)animated { 38 | [super viewDidAppear:animated]; 39 | [self.collectionView.sideRefreshHeader beginLoading]; 40 | } 41 | 42 | - (void)setupRefreshHeaderFoorer { 43 | __weak typeof(self) weakSelf = self; 44 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 45 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 46 | __strong __typeof(self) strongSelf = weakSelf; 47 | strongSelf.itemCount = 3; 48 | [strongSelf.collectionView.sideRefreshHeader endLoading]; 49 | [strongSelf.collectionView reloadData]; 50 | }); 51 | }]; 52 | NSMutableArray *loadingImages = [NSMutableArray array]; 53 | for(int i = 1; i <= 9; i++) { 54 | UIImage *loadingImg = [UIImage imageNamed:[NSString stringWithFormat:@"loading_0%d", i]]; 55 | if(loadingImg) { 56 | [loadingImages addObject:loadingImg]; 57 | } 58 | } 59 | refreshHeader.loadingImages = loadingImages;//自定义图片动画 60 | //自定义提示文案 61 | refreshHeader.normalMessage = @"再拉我试试!"; 62 | refreshHeader.pullingMessage = @"松开,松开"; 63 | refreshHeader.loadingMessage = @"奔跑..."; 64 | self.collectionView.sideRefreshHeader = refreshHeader; 65 | 66 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 67 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 68 | __strong __typeof(self) strongSelf = weakSelf; 69 | if(!(strongSelf.itemCount > 20)) { 70 | strongSelf.itemCount += 3; 71 | [strongSelf.collectionView.sideRefreshFooter endLoading]; 72 | } else { 73 | [strongSelf.collectionView.sideRefreshFooter endLoading]; 74 | } 75 | [strongSelf.collectionView reloadData]; 76 | }); 77 | }]; 78 | refreshFooter.loadingImages = [loadingImages mutableCopy];//自定义图片动画 79 | //自定义提示文案 80 | refreshFooter.normalMessage = @"再拉我试试!"; 81 | refreshFooter.pullingMessage = @"松开,松开"; 82 | refreshFooter.loadingMessage = @"奔跑..."; 83 | self.collectionView.sideRefreshFooter = refreshFooter; 84 | } 85 | 86 | 87 | - (void)didReceiveMemoryWarning { 88 | [super didReceiveMemoryWarning]; 89 | // Dispose of any resources that can be recreated. 90 | } 91 | 92 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 93 | return self.itemCount; 94 | } 95 | 96 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 97 | MyCollectionViewCell *myCollectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCollectionViewCell" forIndexPath:indexPath]; 98 | myCollectionCell.titleLabel.text = [NSString stringWithFormat:@"NO.%ld", (indexPath.row + 1)]; 99 | return myCollectionCell; 100 | } 101 | 102 | - (void)dealloc { 103 | NSLog(@"CustomViewController delloc"); 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/DefaultViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultViewController.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DefaultViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/DefaultViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultViewController.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "DefaultViewController.h" 10 | #import "UICollectionView+SideRefresh.h" 11 | #import "MyCollectionViewCell.h" 12 | 13 | @interface DefaultViewController () 14 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 15 | @property (nonatomic, assign) CGFloat itemCount; 16 | @end 17 | 18 | @implementation DefaultViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | // Do any additional setup after loading the view, typically from a nib. 23 | self.title = @"默认刷新样式"; 24 | self.itemCount = 0; 25 | 26 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 27 | flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 28 | flowLayout.itemSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300.0); 29 | // flowLayout.itemSize = CGSizeMake(50.0, 300.0); 30 | flowLayout.minimumLineSpacing = 0.0; 31 | 32 | self.collectionView.collectionViewLayout = flowLayout; 33 | self.collectionView.dataSource = self; 34 | self.collectionView.pagingEnabled = YES; 35 | [self.collectionView registerNib:[UINib nibWithNibName:@"MyCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"MyCollectionViewCell"]; 36 | [self setupRefreshHeaderFoorer]; 37 | } 38 | 39 | - (void)viewDidAppear:(BOOL)animated { 40 | [super viewDidAppear:animated]; 41 | [self.collectionView.sideRefreshHeader beginLoading]; 42 | } 43 | 44 | - (void)setupRefreshHeaderFoorer { 45 | __weak typeof(self) weakSelf = self; 46 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 47 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 48 | __strong __typeof(self) strongSelf = weakSelf; 49 | strongSelf.itemCount = 3; 50 | [strongSelf.collectionView.sideRefreshHeader endLoading]; 51 | [strongSelf.collectionView reloadData]; 52 | }); 53 | }]; 54 | self.collectionView.sideRefreshHeader = refreshHeader; 55 | 56 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 57 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 58 | __strong __typeof(self) strongSelf = weakSelf; 59 | strongSelf.itemCount += 3; 60 | [strongSelf.collectionView.sideRefreshFooter endLoading]; 61 | [strongSelf.collectionView reloadData]; 62 | }); 63 | }]; 64 | self.collectionView.sideRefreshFooter = refreshFooter; 65 | } 66 | 67 | 68 | - (void)didReceiveMemoryWarning { 69 | [super didReceiveMemoryWarning]; 70 | // Dispose of any resources that can be recreated. 71 | } 72 | 73 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 74 | return self.itemCount; 75 | } 76 | 77 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 78 | MyCollectionViewCell *myCollectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCollectionViewCell" forIndexPath:indexPath]; 79 | myCollectionCell.titleLabel.text = [NSString stringWithFormat:@"NO.%d", (indexPath.row + 1)]; 80 | return myCollectionCell; 81 | } 82 | 83 | - (void)dealloc { 84 | NSLog(@"DefaultViewController delloc"); 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/EmptyFooterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyFooterViewController.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/21. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface EmptyFooterViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/EmptyFooterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyFooterViewController.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/21. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "EmptyFooterViewController.h" 10 | #import "UICollectionView+SideRefresh.h" 11 | #import "MyCollectionViewCell.h" 12 | 13 | @interface EmptyFooterViewController () 14 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 15 | 16 | @property (nonatomic, assign) CGFloat itemCount; 17 | @end 18 | 19 | @implementation EmptyFooterViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | self.title = @"没有更多数据-提示"; 25 | self.itemCount = 0; 26 | 27 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 28 | flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 29 | flowLayout.itemSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300.0); 30 | flowLayout.minimumLineSpacing = 0.0; 31 | 32 | self.collectionView.collectionViewLayout = flowLayout; 33 | self.collectionView.dataSource = self; 34 | self.collectionView.pagingEnabled = YES; 35 | [self.collectionView registerNib:[UINib nibWithNibName:@"MyCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"MyCollectionViewCell"]; 36 | 37 | [self setupRefreshHeaderFoorer]; 38 | } 39 | 40 | - (void)viewDidAppear:(BOOL)animated { 41 | [super viewDidAppear:animated]; 42 | [self.collectionView.sideRefreshHeader beginLoading]; 43 | } 44 | 45 | - (void)setupRefreshHeaderFoorer { 46 | __weak typeof(self) weakSelf = self; 47 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 48 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 49 | __strong __typeof(self) strongSelf = weakSelf; 50 | strongSelf.itemCount = 3; 51 | [strongSelf.collectionView.sideRefreshHeader endLoading]; 52 | [strongSelf.collectionView reloadData]; 53 | }); 54 | }]; 55 | refreshHeader.hideMessage = YES;//隐藏提示 56 | NSMutableArray *loadingImages = [NSMutableArray array]; 57 | for(int i = 1; i <= 9; i++) { 58 | UIImage *loadingImg = [UIImage imageNamed:[NSString stringWithFormat:@"loading_0%d", i]]; 59 | if(loadingImg) { 60 | [loadingImages addObject:loadingImg]; 61 | } 62 | } 63 | refreshHeader.loadingImages = loadingImages; 64 | self.collectionView.sideRefreshHeader = refreshHeader; 65 | 66 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 67 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 68 | __strong __typeof(self) strongSelf = weakSelf; 69 | strongSelf.itemCount += 3; 70 | [strongSelf.collectionView.sideRefreshFooter endLoading]; 71 | [strongSelf.collectionView reloadData]; 72 | if(strongSelf.itemCount == 6) { 73 | [strongSelf.collectionView showEmptyFooter]; 74 | // [strongSelf.collectionView showEmptyFooterWithMessage:@"不要看了,没有了。"]; 75 | } 76 | }); 77 | }]; 78 | refreshFooter.hideIndicator = YES;//隐藏加载动画 79 | refreshFooter.loadingImages = [loadingImages mutableCopy]; 80 | self.collectionView.sideRefreshFooter = refreshFooter; 81 | } 82 | 83 | 84 | - (void)didReceiveMemoryWarning { 85 | [super didReceiveMemoryWarning]; 86 | // Dispose of any resources that can be recreated. 87 | } 88 | 89 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 90 | return self.itemCount; 91 | } 92 | 93 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 94 | MyCollectionViewCell *myCollectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCollectionViewCell" forIndexPath:indexPath]; 95 | myCollectionCell.titleLabel.text = [NSString stringWithFormat:@"NO.%ld", (indexPath.row + 1)]; 96 | return myCollectionCell; 97 | } 98 | 99 | - (void)dealloc { 100 | NSLog(@"EmptyFooterViewController delloc"); 101 | } 102 | 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/HideItemViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HideItemViewController.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HideItemViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/HideItemViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HideItemViewController.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "HideItemViewController.h" 10 | #import "UICollectionView+SideRefresh.h" 11 | #import "MyCollectionViewCell.h" 12 | 13 | @interface HideItemViewController () 14 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 15 | @property (nonatomic, assign) CGFloat itemCount; 16 | 17 | @end 18 | 19 | @implementation HideItemViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | self.title = @"隐藏提示或动画"; 25 | self.itemCount = 0; 26 | 27 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 28 | flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 29 | flowLayout.itemSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300.0); 30 | flowLayout.minimumLineSpacing = 0.0; 31 | 32 | self.collectionView.collectionViewLayout = flowLayout; 33 | self.collectionView.dataSource = self; 34 | 35 | [self.collectionView registerNib:[UINib nibWithNibName:@"MyCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"MyCollectionViewCell"]; 36 | 37 | [self setupRefreshHeaderFoorer]; 38 | } 39 | 40 | - (void)viewDidAppear:(BOOL)animated { 41 | [super viewDidAppear:animated]; 42 | [self.collectionView.sideRefreshHeader beginLoading]; 43 | } 44 | 45 | - (void)setupRefreshHeaderFoorer { 46 | __weak typeof(self) weakSelf = self; 47 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 48 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 49 | __strong __typeof(self) strongSelf = weakSelf; 50 | strongSelf.itemCount = 3; 51 | [strongSelf.collectionView.sideRefreshHeader endLoading]; 52 | [strongSelf.collectionView reloadData]; 53 | }); 54 | }]; 55 | refreshHeader.hideMessage = YES;//隐藏提示 56 | NSMutableArray *loadingImages = [NSMutableArray array]; 57 | for(int i = 1; i <= 9; i++) { 58 | UIImage *loadingImg = [UIImage imageNamed:[NSString stringWithFormat:@"loading_0%d", i]]; 59 | if(loadingImg) { 60 | [loadingImages addObject:loadingImg]; 61 | } 62 | } 63 | refreshHeader.loadingImages = loadingImages; 64 | self.collectionView.sideRefreshHeader = refreshHeader; 65 | 66 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 67 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 68 | __strong __typeof(self) strongSelf = weakSelf; 69 | strongSelf.itemCount += 3; 70 | [strongSelf.collectionView.sideRefreshFooter endLoading]; 71 | [strongSelf.collectionView reloadData]; 72 | }); 73 | }]; 74 | refreshFooter.hideIndicator = YES;//隐藏加载动画 75 | refreshFooter.loadingImages = [loadingImages mutableCopy]; 76 | self.collectionView.sideRefreshFooter = refreshFooter; 77 | } 78 | 79 | 80 | - (void)didReceiveMemoryWarning { 81 | [super didReceiveMemoryWarning]; 82 | // Dispose of any resources that can be recreated. 83 | } 84 | 85 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 86 | return self.itemCount; 87 | } 88 | 89 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 90 | MyCollectionViewCell *myCollectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCollectionViewCell" forIndexPath:indexPath]; 91 | myCollectionCell.titleLabel.text = [NSString stringWithFormat:@"NO.%ld", (indexPath.row + 1)]; 92 | return myCollectionCell; 93 | } 94 | 95 | - (void)dealloc { 96 | NSLog(@"HideItemViewController delloc"); 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/MyCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // MyCollectionViewCell.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MyCollectionViewCell : UICollectionViewCell 12 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/MyCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // MyCollectionViewCell.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/13. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "MyCollectionViewCell.h" 10 | 11 | @implementation MyCollectionViewCell 12 | 13 | - (void)awakeFromNib { 14 | [super awakeFromNib]; 15 | // Initialization code 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/SideRefreshDemo/MyCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 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 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "DefaultViewController.h" 11 | #import "CustomViewController.h" 12 | #import "HideItemViewController.h" 13 | #import "EmptyFooterViewController.h" 14 | 15 | @interface ViewController () 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (IBAction)clickDefault:(id)sender { 22 | DefaultViewController *defaultController = [self.storyboard instantiateViewControllerWithIdentifier:@"DefaultViewController"]; 23 | [self.navigationController pushViewController:defaultController animated:YES]; 24 | } 25 | 26 | - (IBAction)clickCustom:(id)sender { 27 | CustomViewController *customController = [self.storyboard instantiateViewControllerWithIdentifier:@"CustomViewController"]; 28 | [self.navigationController pushViewController:customController animated:YES]; 29 | } 30 | 31 | - (IBAction)clickShowHide:(id)sender { 32 | HideItemViewController *hideItemController = [self.storyboard instantiateViewControllerWithIdentifier:@"HideItemViewController"]; 33 | [self.navigationController pushViewController:hideItemController animated:YES]; 34 | } 35 | - (IBAction)clickEmptyFooter:(id)sender { 36 | EmptyFooterViewController *emptyFooterViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"EmptyFooterViewController"]; 37 | [self.navigationController pushViewController:emptyFooterViewController animated:YES]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /CollectionViewSideRefresh/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CollectionViewSideRefresh 4 | // 5 | // Created by DandJ on 2018/7/11. 6 | // Copyright © 2018年 DandJ. 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 | -------------------------------------------------------------------------------- /GIF/没有更多数据提示.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/GIF/没有更多数据提示.GIF -------------------------------------------------------------------------------- /GIF/自定义.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/GIF/自定义.png -------------------------------------------------------------------------------- /GIF/隐藏提示或动画.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/GIF/隐藏提示或动画.png -------------------------------------------------------------------------------- /GIF/默认样式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/CollectionViewSideRefresh/e01df891f72cc3da14effb01ea0a2dc7e6e84a91/GIF/默认样式.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 一款简单好用的UICollectionView横向滚动刷新控件 2 | 3 | [源码地址:(GitHub)](https://github.com/dangercheng/CollectionViewSideRefresh) 4 | 5 | # 实现功能: 6 | 7 | - **接入简单,不侵入代码** 8 | - **右拉刷新,左拉加载更多** 9 | - **自定义提示文案和加载动画** 10 | - **方便隐藏和显示加载提示文本和动画** 11 | - **支持 ‘没有更多数据’ 提示** 12 | 13 | # 接入方式 14 | 1. 下载源码,将文件夹 "SideRefresh"添加进工程 15 | 2. 引入头文件 #import "UICollectionView+SideRefresh.h" 16 | 3. 创建SideRefreshHeader和SideRefreshFooter并添加给UICollectionView即可 17 | 18 | ## 默认刷新样式 19 | ``` 20 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 21 | //执行刷新 22 | }]; 23 | self.collectionView.sideRefreshHeader = refreshHeader; 24 | 25 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 26 | //执行加载更多 27 | }]; 28 | self.collectionView.sideRefreshFooter = refreshFooter; 29 | ``` 30 | ![默认样式](https://sfault-image.b0.upaiyun.com/304/614/3046140025-5b4876a3a0bb3_articlex) 31 | 32 | ## 自定义刷新提示和图片动画 33 | ``` 34 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 35 | //执行刷新 36 | }]; 37 | NSMutableArray *loadingImages = [NSMutableArray array]; 38 | for(int i = 1; i <= 9; i++) { 39 | UIImage *loadingImg = [UIImage imageNamed:[NSString stringWithFormat:@"loading_0%d", i]]; 40 | if(loadingImg) { 41 | [loadingImages addObject:loadingImg]; 42 | } 43 | } 44 | refreshHeader.loadingImages = loadingImages;//自定义图片动画 45 | //自定义提示文案 46 | refreshHeader.normalMessage = @"再拉我试试!"; 47 | refreshHeader.pullingMessage = @"松开,松开"; 48 | refreshHeader.loadingMessage = @"奔跑..."; 49 | self.collectionView.sideRefreshHeader = refreshHeader; 50 | 51 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 52 | //执行加载更多 53 | }]; 54 | self.collectionView.sideRefreshFooter = refreshFooter; 55 | ``` 56 | ![自定义](https://sfault-image.b0.upaiyun.com/376/316/3763166665-5b4876ff3f97f_articlex) 57 | ## 隐藏刷新提示或动画 58 | ``` 59 | SideRefreshHeader *refreshHeader = [SideRefreshHeader refreshWithLoadAction:^{ 60 | //执行刷新 61 | }]; 62 | refreshHeader.hideMessage = YES;//隐藏提示 63 | self.collectionView.sideRefreshHeader = refreshHeader; 64 | 65 | SideRefreshFooter *refreshFooter = [SideRefreshFooter refreshWithLoadAction:^{ 66 | //执行加载更多 67 | }]; 68 | refreshFooter.hideIndicator = YES;//隐藏加载动画 69 | self.collectionView.sideRefreshFooter = refreshFooter; 70 | ``` 71 | ![隐藏提示或动画](https://sfault-image.b0.upaiyun.com/365/863/3658631273-5b487722e5f30_articlex) 72 | ## ‘没有更多数据’ 提示 73 | 显示默认提示 74 | ``` 75 | [self.collectionView showEmptyFooter] 76 | ``` 77 | 显示自定义提示 78 | ``` 79 | [self.collectionView showEmptyFooterWithMessage:@"不要看了,没有了。"]; 80 | ``` 81 | ![没有更多数据](https://sfault-image.b0.upaiyun.com/213/366/2133667725-5b52f8f714335_articlex) 82 | 83 | **PS:隐藏需要手动调用** 84 | ``` 85 | [self.collectionView hideEmptyFooter]; 86 | ``` 87 | 88 | # 实现方式 89 | 90 | - **通过Runtime为UICollectionView添加SideRefreshHeader和SideRefreshFooter属性** 91 | - **观察UICollectionView的contentOffset实现拖动相关逻辑** 92 | - **观察UICollectionView的contentSize来重置刷新视图** 93 | - 更多细节参见源码Demo 94 | 95 | 如果觉得好用欢迎Star [github](https://github.com/dangercheng/CollectionViewSideRefresh), 使用问题请Issue。 96 | 97 | 98 | 99 | --------------------------------------------------------------------------------