├── EmptyDataHandle.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── apple.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── apple.xcuserdatad │ └── xcschemes │ ├── EmptyDataHandle.xcscheme │ └── xcschememanagement.plist └── EmptyDataHandle ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── BaseImages │ ├── Contents.json │ ├── NoData.imageset │ │ ├── Contents.json │ │ └── NoData@2x.png │ └── NoOrderData.imageset │ │ ├── Contents.json │ │ └── NoOrderData@2x.png └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Base ├── UIScrollView+EmptyDataSet.h ├── UIScrollView+EmptyDataSet.m ├── XLBaseTableController.h └── XLBaseTableController.m ├── CustomOneVC.h ├── CustomOneVC.m ├── CustomThreeVC.h ├── CustomThreeVC.m ├── CustomTwoVC.h ├── CustomTwoVC.m ├── DefaultEmptyVC.h ├── DefaultEmptyVC.m ├── EmptyDataHandle.xcdatamodeld ├── .xccurrentversion └── EmptyDataHandle.xcdatamodel │ └── contents ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m /EmptyDataHandle.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1888E49D1E35DA1E00C4C925 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E49C1E35DA1E00C4C925 /* main.m */; }; 11 | 1888E4A01E35DA1E00C4C925 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E49F1E35DA1E00C4C925 /* AppDelegate.m */; }; 12 | 1888E4A31E35DA1E00C4C925 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4A21E35DA1E00C4C925 /* ViewController.m */; }; 13 | 1888E4A61E35DA1E00C4C925 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1888E4A41E35DA1E00C4C925 /* Main.storyboard */; }; 14 | 1888E4A91E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4A71E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodeld */; }; 15 | 1888E4AB1E35DA1E00C4C925 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1888E4AA1E35DA1E00C4C925 /* Assets.xcassets */; }; 16 | 1888E4AE1E35DA1E00C4C925 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1888E4AC1E35DA1E00C4C925 /* LaunchScreen.storyboard */; }; 17 | 1888E4B81E35DB1B00C4C925 /* XLBaseTableController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4B71E35DB1B00C4C925 /* XLBaseTableController.m */; }; 18 | 1888E4BB1E35DB5900C4C925 /* UIScrollView+EmptyDataSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4BA1E35DB5900C4C925 /* UIScrollView+EmptyDataSet.m */; }; 19 | 1888E4BE1E35DF9F00C4C925 /* DefaultEmptyVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4BD1E35DF9F00C4C925 /* DefaultEmptyVC.m */; }; 20 | 1888E4C11E35DFE000C4C925 /* CustomOneVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4C01E35DFE000C4C925 /* CustomOneVC.m */; }; 21 | 1888E4C41E35E00300C4C925 /* CustomTwoVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4C31E35E00300C4C925 /* CustomTwoVC.m */; }; 22 | 1888E4C71E35E01300C4C925 /* CustomThreeVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1888E4C61E35E01300C4C925 /* CustomThreeVC.m */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 1888E4981E35DA1E00C4C925 /* EmptyDataHandle.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EmptyDataHandle.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 1888E49C1E35DA1E00C4C925 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 28 | 1888E49E1E35DA1E00C4C925 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 29 | 1888E49F1E35DA1E00C4C925 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 30 | 1888E4A11E35DA1E00C4C925 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 31 | 1888E4A21E35DA1E00C4C925 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 32 | 1888E4A51E35DA1E00C4C925 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | 1888E4A81E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = EmptyDataHandle.xcdatamodel; sourceTree = ""; }; 34 | 1888E4AA1E35DA1E00C4C925 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | 1888E4AD1E35DA1E00C4C925 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 36 | 1888E4AF1E35DA1E00C4C925 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 1888E4B61E35DB1B00C4C925 /* XLBaseTableController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XLBaseTableController.h; sourceTree = ""; }; 38 | 1888E4B71E35DB1B00C4C925 /* XLBaseTableController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XLBaseTableController.m; sourceTree = ""; }; 39 | 1888E4B91E35DB5900C4C925 /* UIScrollView+EmptyDataSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+EmptyDataSet.h"; sourceTree = ""; }; 40 | 1888E4BA1E35DB5900C4C925 /* UIScrollView+EmptyDataSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+EmptyDataSet.m"; sourceTree = ""; }; 41 | 1888E4BC1E35DF9F00C4C925 /* DefaultEmptyVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DefaultEmptyVC.h; sourceTree = ""; }; 42 | 1888E4BD1E35DF9F00C4C925 /* DefaultEmptyVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DefaultEmptyVC.m; sourceTree = ""; }; 43 | 1888E4BF1E35DFE000C4C925 /* CustomOneVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomOneVC.h; sourceTree = ""; }; 44 | 1888E4C01E35DFE000C4C925 /* CustomOneVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomOneVC.m; sourceTree = ""; }; 45 | 1888E4C21E35E00300C4C925 /* CustomTwoVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTwoVC.h; sourceTree = ""; }; 46 | 1888E4C31E35E00300C4C925 /* CustomTwoVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomTwoVC.m; sourceTree = ""; }; 47 | 1888E4C51E35E01300C4C925 /* CustomThreeVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomThreeVC.h; sourceTree = ""; }; 48 | 1888E4C61E35E01300C4C925 /* CustomThreeVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomThreeVC.m; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 1888E4951E35DA1E00C4C925 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 1888E48F1E35DA1E00C4C925 = { 63 | isa = PBXGroup; 64 | children = ( 65 | 1888E49A1E35DA1E00C4C925 /* EmptyDataHandle */, 66 | 1888E4991E35DA1E00C4C925 /* Products */, 67 | ); 68 | sourceTree = ""; 69 | }; 70 | 1888E4991E35DA1E00C4C925 /* Products */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 1888E4981E35DA1E00C4C925 /* EmptyDataHandle.app */, 74 | ); 75 | name = Products; 76 | sourceTree = ""; 77 | }; 78 | 1888E49A1E35DA1E00C4C925 /* EmptyDataHandle */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 1888E4B51E35DAF100C4C925 /* Base */, 82 | 1888E49E1E35DA1E00C4C925 /* AppDelegate.h */, 83 | 1888E49F1E35DA1E00C4C925 /* AppDelegate.m */, 84 | 1888E4A11E35DA1E00C4C925 /* ViewController.h */, 85 | 1888E4A21E35DA1E00C4C925 /* ViewController.m */, 86 | 1888E4BC1E35DF9F00C4C925 /* DefaultEmptyVC.h */, 87 | 1888E4BD1E35DF9F00C4C925 /* DefaultEmptyVC.m */, 88 | 1888E4BF1E35DFE000C4C925 /* CustomOneVC.h */, 89 | 1888E4C01E35DFE000C4C925 /* CustomOneVC.m */, 90 | 1888E4C21E35E00300C4C925 /* CustomTwoVC.h */, 91 | 1888E4C31E35E00300C4C925 /* CustomTwoVC.m */, 92 | 1888E4C51E35E01300C4C925 /* CustomThreeVC.h */, 93 | 1888E4C61E35E01300C4C925 /* CustomThreeVC.m */, 94 | 1888E4A41E35DA1E00C4C925 /* Main.storyboard */, 95 | 1888E4AA1E35DA1E00C4C925 /* Assets.xcassets */, 96 | 1888E4AC1E35DA1E00C4C925 /* LaunchScreen.storyboard */, 97 | 1888E4AF1E35DA1E00C4C925 /* Info.plist */, 98 | 1888E4A71E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodeld */, 99 | 1888E49B1E35DA1E00C4C925 /* Supporting Files */, 100 | ); 101 | path = EmptyDataHandle; 102 | sourceTree = ""; 103 | }; 104 | 1888E49B1E35DA1E00C4C925 /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 1888E49C1E35DA1E00C4C925 /* main.m */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | 1888E4B51E35DAF100C4C925 /* Base */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 1888E4B91E35DB5900C4C925 /* UIScrollView+EmptyDataSet.h */, 116 | 1888E4BA1E35DB5900C4C925 /* UIScrollView+EmptyDataSet.m */, 117 | 1888E4B61E35DB1B00C4C925 /* XLBaseTableController.h */, 118 | 1888E4B71E35DB1B00C4C925 /* XLBaseTableController.m */, 119 | ); 120 | path = Base; 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | 1888E4971E35DA1E00C4C925 /* EmptyDataHandle */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = 1888E4B21E35DA1E00C4C925 /* Build configuration list for PBXNativeTarget "EmptyDataHandle" */; 129 | buildPhases = ( 130 | 1888E4941E35DA1E00C4C925 /* Sources */, 131 | 1888E4951E35DA1E00C4C925 /* Frameworks */, 132 | 1888E4961E35DA1E00C4C925 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = EmptyDataHandle; 139 | productName = EmptyDataHandle; 140 | productReference = 1888E4981E35DA1E00C4C925 /* EmptyDataHandle.app */; 141 | productType = "com.apple.product-type.application"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | 1888E4901E35DA1E00C4C925 /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastUpgradeCheck = 0820; 150 | ORGANIZATIONNAME = ZXL; 151 | TargetAttributes = { 152 | 1888E4971E35DA1E00C4C925 = { 153 | CreatedOnToolsVersion = 8.2.1; 154 | DevelopmentTeam = ZV9RARUJHT; 155 | ProvisioningStyle = Automatic; 156 | }; 157 | }; 158 | }; 159 | buildConfigurationList = 1888E4931E35DA1E00C4C925 /* Build configuration list for PBXProject "EmptyDataHandle" */; 160 | compatibilityVersion = "Xcode 3.2"; 161 | developmentRegion = English; 162 | hasScannedForEncodings = 0; 163 | knownRegions = ( 164 | en, 165 | Base, 166 | ); 167 | mainGroup = 1888E48F1E35DA1E00C4C925; 168 | productRefGroup = 1888E4991E35DA1E00C4C925 /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | 1888E4971E35DA1E00C4C925 /* EmptyDataHandle */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | 1888E4961E35DA1E00C4C925 /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 1888E4AE1E35DA1E00C4C925 /* LaunchScreen.storyboard in Resources */, 183 | 1888E4AB1E35DA1E00C4C925 /* Assets.xcassets in Resources */, 184 | 1888E4A61E35DA1E00C4C925 /* Main.storyboard in Resources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXResourcesBuildPhase section */ 189 | 190 | /* Begin PBXSourcesBuildPhase section */ 191 | 1888E4941E35DA1E00C4C925 /* Sources */ = { 192 | isa = PBXSourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | 1888E4A31E35DA1E00C4C925 /* ViewController.m in Sources */, 196 | 1888E4A91E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodeld in Sources */, 197 | 1888E4A01E35DA1E00C4C925 /* AppDelegate.m in Sources */, 198 | 1888E4BE1E35DF9F00C4C925 /* DefaultEmptyVC.m in Sources */, 199 | 1888E4C71E35E01300C4C925 /* CustomThreeVC.m in Sources */, 200 | 1888E49D1E35DA1E00C4C925 /* main.m in Sources */, 201 | 1888E4C41E35E00300C4C925 /* CustomTwoVC.m in Sources */, 202 | 1888E4BB1E35DB5900C4C925 /* UIScrollView+EmptyDataSet.m in Sources */, 203 | 1888E4C11E35DFE000C4C925 /* CustomOneVC.m in Sources */, 204 | 1888E4B81E35DB1B00C4C925 /* XLBaseTableController.m in Sources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXSourcesBuildPhase section */ 209 | 210 | /* Begin PBXVariantGroup section */ 211 | 1888E4A41E35DA1E00C4C925 /* Main.storyboard */ = { 212 | isa = PBXVariantGroup; 213 | children = ( 214 | 1888E4A51E35DA1E00C4C925 /* Base */, 215 | ); 216 | name = Main.storyboard; 217 | sourceTree = ""; 218 | }; 219 | 1888E4AC1E35DA1E00C4C925 /* LaunchScreen.storyboard */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | 1888E4AD1E35DA1E00C4C925 /* Base */, 223 | ); 224 | name = LaunchScreen.storyboard; 225 | sourceTree = ""; 226 | }; 227 | /* End PBXVariantGroup section */ 228 | 229 | /* Begin XCBuildConfiguration section */ 230 | 1888E4B01E35DA1E00C4C925 /* Debug */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNREACHABLE_CODE = YES; 250 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 251 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = dwarf; 254 | ENABLE_STRICT_OBJC_MSGSEND = YES; 255 | ENABLE_TESTABILITY = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu99; 257 | GCC_DYNAMIC_NO_PIC = NO; 258 | GCC_NO_COMMON_BLOCKS = YES; 259 | GCC_OPTIMIZATION_LEVEL = 0; 260 | GCC_PREPROCESSOR_DEFINITIONS = ( 261 | "DEBUG=1", 262 | "$(inherited)", 263 | ); 264 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 265 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 266 | GCC_WARN_UNDECLARED_SELECTOR = YES; 267 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 268 | GCC_WARN_UNUSED_FUNCTION = YES; 269 | GCC_WARN_UNUSED_VARIABLE = YES; 270 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 271 | MTL_ENABLE_DEBUG_INFO = YES; 272 | ONLY_ACTIVE_ARCH = YES; 273 | SDKROOT = iphoneos; 274 | }; 275 | name = Debug; 276 | }; 277 | 1888E4B11E35DA1E00C4C925 /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_ANALYZER_NONNULL = YES; 282 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 283 | CLANG_CXX_LIBRARY = "libc++"; 284 | CLANG_ENABLE_MODULES = YES; 285 | CLANG_ENABLE_OBJC_ARC = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 289 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INFINITE_RECURSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 296 | CLANG_WARN_UNREACHABLE_CODE = YES; 297 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 298 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 299 | COPY_PHASE_STRIP = NO; 300 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 301 | ENABLE_NS_ASSERTIONS = NO; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | GCC_C_LANGUAGE_STANDARD = gnu99; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 312 | MTL_ENABLE_DEBUG_INFO = NO; 313 | SDKROOT = iphoneos; 314 | VALIDATE_PRODUCT = YES; 315 | }; 316 | name = Release; 317 | }; 318 | 1888E4B31E35DA1E00C4C925 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 322 | DEVELOPMENT_TEAM = ZV9RARUJHT; 323 | INFOPLIST_FILE = EmptyDataHandle/Info.plist; 324 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 325 | PRODUCT_BUNDLE_IDENTIFIER = ZXL.EmptyDataHandle; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | }; 328 | name = Debug; 329 | }; 330 | 1888E4B41E35DA1E00C4C925 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | DEVELOPMENT_TEAM = ZV9RARUJHT; 335 | INFOPLIST_FILE = EmptyDataHandle/Info.plist; 336 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 337 | PRODUCT_BUNDLE_IDENTIFIER = ZXL.EmptyDataHandle; 338 | PRODUCT_NAME = "$(TARGET_NAME)"; 339 | }; 340 | name = Release; 341 | }; 342 | /* End XCBuildConfiguration section */ 343 | 344 | /* Begin XCConfigurationList section */ 345 | 1888E4931E35DA1E00C4C925 /* Build configuration list for PBXProject "EmptyDataHandle" */ = { 346 | isa = XCConfigurationList; 347 | buildConfigurations = ( 348 | 1888E4B01E35DA1E00C4C925 /* Debug */, 349 | 1888E4B11E35DA1E00C4C925 /* Release */, 350 | ); 351 | defaultConfigurationIsVisible = 0; 352 | defaultConfigurationName = Release; 353 | }; 354 | 1888E4B21E35DA1E00C4C925 /* Build configuration list for PBXNativeTarget "EmptyDataHandle" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | 1888E4B31E35DA1E00C4C925 /* Debug */, 358 | 1888E4B41E35DA1E00C4C925 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | /* End XCConfigurationList section */ 364 | 365 | /* Begin XCVersionGroup section */ 366 | 1888E4A71E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodeld */ = { 367 | isa = XCVersionGroup; 368 | children = ( 369 | 1888E4A81E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodel */, 370 | ); 371 | currentVersion = 1888E4A81E35DA1E00C4C925 /* EmptyDataHandle.xcdatamodel */; 372 | path = EmptyDataHandle.xcdatamodeld; 373 | sourceTree = ""; 374 | versionGroupType = wrapper.xcdatamodel; 375 | }; 376 | /* End XCVersionGroup section */ 377 | }; 378 | rootObject = 1888E4901E35DA1E00C4C925 /* Project object */; 379 | } 380 | -------------------------------------------------------------------------------- /EmptyDataHandle.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EmptyDataHandle.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaa/EmptyDataHandle/26ad5c8ff26eaa1eed3ca9d5b07661d0481ff088/EmptyDataHandle.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /EmptyDataHandle.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/EmptyDataHandle.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /EmptyDataHandle.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | EmptyDataHandle.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 1888E4971E35DA1E00C4C925 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /EmptyDataHandle/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | 16 | @property (readonly, strong) NSPersistentContainer *persistentContainer; 17 | 18 | - (void)saveContext; 19 | 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /EmptyDataHandle/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | ViewController *view = [[ViewController alloc] init]; 22 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view]; 23 | self.window.rootViewController = nav; 24 | 25 | return YES; 26 | } 27 | 28 | 29 | - (void)applicationWillResignActive:(UIApplication *)application { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 32 | } 33 | 34 | 35 | - (void)applicationDidEnterBackground:(UIApplication *)application { 36 | // 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. 37 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 38 | } 39 | 40 | 41 | - (void)applicationWillEnterForeground:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationDidBecomeActive:(UIApplication *)application { 47 | // 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. 48 | } 49 | 50 | 51 | - (void)applicationWillTerminate:(UIApplication *)application { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | // Saves changes in the application's managed object context before the application terminates. 54 | [self saveContext]; 55 | } 56 | 57 | 58 | #pragma mark - Core Data stack 59 | 60 | @synthesize persistentContainer = _persistentContainer; 61 | 62 | - (NSPersistentContainer *)persistentContainer { 63 | // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it. 64 | @synchronized (self) { 65 | if (_persistentContainer == nil) { 66 | _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"EmptyDataHandle"]; 67 | [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) { 68 | if (error != nil) { 69 | // Replace this implementation with code to handle the error appropriately. 70 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 71 | 72 | /* 73 | Typical reasons for an error here include: 74 | * The parent directory does not exist, cannot be created, or disallows writing. 75 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 76 | * The device is out of space. 77 | * The store could not be migrated to the current model version. 78 | Check the error message to determine what the actual problem was. 79 | */ 80 | NSLog(@"Unresolved error %@, %@", error, error.userInfo); 81 | abort(); 82 | } 83 | }]; 84 | } 85 | } 86 | 87 | return _persistentContainer; 88 | } 89 | 90 | #pragma mark - Core Data Saving support 91 | 92 | - (void)saveContext { 93 | NSManagedObjectContext *context = self.persistentContainer.viewContext; 94 | NSError *error = nil; 95 | if ([context hasChanges] && ![context save:&error]) { 96 | // Replace this implementation with code to handle the error appropriately. 97 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 98 | NSLog(@"Unresolved error %@, %@", error, error.userInfo); 99 | abort(); 100 | } 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /EmptyDataHandle/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 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /EmptyDataHandle/Assets.xcassets/BaseImages/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /EmptyDataHandle/Assets.xcassets/BaseImages/NoData.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "NoData@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /EmptyDataHandle/Assets.xcassets/BaseImages/NoData.imageset/NoData@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaa/EmptyDataHandle/26ad5c8ff26eaa1eed3ca9d5b07661d0481ff088/EmptyDataHandle/Assets.xcassets/BaseImages/NoData.imageset/NoData@2x.png -------------------------------------------------------------------------------- /EmptyDataHandle/Assets.xcassets/BaseImages/NoOrderData.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "NoOrderData@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /EmptyDataHandle/Assets.xcassets/BaseImages/NoOrderData.imageset/NoOrderData@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaa/EmptyDataHandle/26ad5c8ff26eaa1eed3ca9d5b07661d0481ff088/EmptyDataHandle/Assets.xcassets/BaseImages/NoOrderData.imageset/NoOrderData@2x.png -------------------------------------------------------------------------------- /EmptyDataHandle/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /EmptyDataHandle/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /EmptyDataHandle/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 | -------------------------------------------------------------------------------- /EmptyDataHandle/Base/UIScrollView+EmptyDataSet.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+EmptyDataSet.h 3 | // DZNEmptyDataSet 4 | // https://github.com/dzenbot/DZNEmptyDataSet 5 | // 6 | // Created by Ignacio Romero Zurbuchen on 6/20/14. 7 | // Copyright (c) 2016 DZN Labs. All rights reserved. 8 | // Licence: MIT-Licence 9 | // 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @protocol DZNEmptyDataSetSource; 16 | @protocol DZNEmptyDataSetDelegate; 17 | 18 | #define DZNEmptyDataSetDeprecated(instead) DEPRECATED_MSG_ATTRIBUTE(" Use " # instead " instead") 19 | 20 | /** 21 | A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display. 22 | @discussion It will work automatically, by just conforming to DZNEmptyDataSetSource, and returning the data you want to show. 23 | */ 24 | @interface UIScrollView (EmptyDataSet) 25 | 26 | /** The empty datasets data source. */ 27 | @property (nonatomic, weak, nullable) IBOutlet id emptyDataSetSource; 28 | /** The empty datasets delegate. */ 29 | @property (nonatomic, weak, nullable) IBOutlet id emptyDataSetDelegate; 30 | /** YES if any empty dataset is visible. */ 31 | @property (nonatomic, readonly, getter = isEmptyDataSetVisible) BOOL emptyDataSetVisible; 32 | 33 | /** 34 | Reloads the empty dataset content receiver. 35 | @discussion Call this method to force all the data to refresh. Calling -reloadData is similar, but this forces only the empty dataset to reload, not the entire table view or collection view. 36 | */ 37 | - (void)reloadEmptyDataSet; 38 | 39 | @end 40 | 41 | 42 | /** 43 | The object that acts as the data source of the empty datasets. 44 | @discussion The data source must adopt the DZNEmptyDataSetSource protocol. The data source is not retained. All data source methods are optional. 45 | */ 46 | @protocol DZNEmptyDataSetSource 47 | @optional 48 | 49 | /** 50 | Asks the data source for the title of the dataset. 51 | The dataset uses a fixed font style by default, if no attributes are set. If you want a different font style, return a attributed string. 52 | 53 | @param scrollView A scrollView subclass informing the data source. 54 | @return An attributed string for the dataset title, combining font, text color, text pararaph style, etc. 55 | */ 56 | - (nullable NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView; 57 | 58 | /** 59 | Asks the data source for the description of the dataset. 60 | The dataset uses a fixed font style by default, if no attributes are set. If you want a different font style, return a attributed string. 61 | 62 | @param scrollView A scrollView subclass informing the data source. 63 | @return An attributed string for the dataset description text, combining font, text color, text pararaph style, etc. 64 | */ 65 | - (nullable NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView; 66 | 67 | /** 68 | Asks the data source for the image of the dataset. 69 | 70 | @param scrollView A scrollView subclass informing the data source. 71 | @return An image for the dataset. 72 | */ 73 | - (nullable UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView; 74 | 75 | 76 | /** 77 | Asks the data source for a tint color of the image dataset. Default is nil. 78 | 79 | @param scrollView A scrollView subclass object informing the data source. 80 | @return A color to tint the image of the dataset. 81 | */ 82 | - (nullable UIColor *)imageTintColorForEmptyDataSet:(UIScrollView *)scrollView; 83 | 84 | /** 85 | * Asks the data source for the image animation of the dataset. 86 | * 87 | * @param scrollView A scrollView subclass object informing the delegate. 88 | * 89 | * @return image animation 90 | */ 91 | - (nullable CAAnimation *)imageAnimationForEmptyDataSet:(UIScrollView *)scrollView; 92 | 93 | /** 94 | Asks the data source for the title to be used for the specified button state. 95 | The dataset uses a fixed font style by default, if no attributes are set. If you want a different font style, return a attributed string. 96 | 97 | @param scrollView A scrollView subclass object informing the data source. 98 | @param state The state that uses the specified title. The possible values are described in UIControlState. 99 | @return An attributed string for the dataset button title, combining font, text color, text pararaph style, etc. 100 | */ 101 | - (nullable NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state; 102 | 103 | /** 104 | Asks the data source for the image to be used for the specified button state. 105 | This method will override buttonTitleForEmptyDataSet:forState: and present the image only without any text. 106 | 107 | @param scrollView A scrollView subclass object informing the data source. 108 | @param state The state that uses the specified title. The possible values are described in UIControlState. 109 | @return An image for the dataset button imageview. 110 | */ 111 | - (nullable UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state; 112 | 113 | /** 114 | Asks the data source for a background image to be used for the specified button state. 115 | There is no default style for this call. 116 | 117 | @param scrollView A scrollView subclass informing the data source. 118 | @param state The state that uses the specified image. The values are described in UIControlState. 119 | @return An attributed string for the dataset button title, combining font, text color, text pararaph style, etc. 120 | */ 121 | - (nullable UIImage *)buttonBackgroundImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state; 122 | 123 | /** 124 | Asks the data source for the background color of the dataset. Default is clear color. 125 | 126 | @param scrollView A scrollView subclass object informing the data source. 127 | @return A color to be applied to the dataset background view. 128 | */ 129 | - (nullable UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView; 130 | 131 | /** 132 | Asks the data source for a custom view to be displayed instead of the default views such as labels, imageview and button. Default is nil. 133 | Use this method to show an activity view indicator for loading feedback, or for complete custom empty data set. 134 | Returning a custom view will ignore -offsetForEmptyDataSet and -spaceHeightForEmptyDataSet configurations. 135 | 136 | @param scrollView A scrollView subclass object informing the delegate. 137 | @return The custom view. 138 | */ 139 | - (nullable UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView; 140 | 141 | /** 142 | Asks the data source for a offset for vertical and horizontal alignment of the content. Default is CGPointZero. 143 | 144 | @param scrollView A scrollView subclass object informing the delegate. 145 | @return The offset for vertical and horizontal alignment. 146 | */ 147 | - (CGPoint)offsetForEmptyDataSet:(UIScrollView *)scrollView DZNEmptyDataSetDeprecated(-verticalOffsetForEmptyDataSet:); 148 | - (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView; 149 | 150 | /** 151 | Asks the data source for a vertical space between elements. Default is 11 pts. 152 | 153 | @param scrollView A scrollView subclass object informing the delegate. 154 | @return The space height between elements. 155 | */ 156 | - (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView; 157 | 158 | @end 159 | 160 | 161 | /** 162 | The object that acts as the delegate of the empty datasets. 163 | @discussion The delegate can adopt the DZNEmptyDataSetDelegate protocol. The delegate is not retained. All delegate methods are optional. 164 | 165 | @discussion All delegate methods are optional. Use this delegate for receiving action callbacks. 166 | */ 167 | @protocol DZNEmptyDataSetDelegate 168 | @optional 169 | 170 | /** 171 | Asks the delegate to know if the empty dataset should fade in when displayed. Default is YES. 172 | 173 | @param scrollView A scrollView subclass object informing the delegate. 174 | @return YES if the empty dataset should fade in. 175 | */ 176 | - (BOOL)emptyDataSetShouldFadeIn:(UIScrollView *)scrollView; 177 | 178 | /** 179 | Asks the delegate to know if the empty dataset should still be displayed when the amount of items is more than 0. Default is NO 180 | 181 | @param scrollView A scrollView subclass object informing the delegate. 182 | @return YES if empty dataset should be forced to display 183 | */ 184 | - (BOOL)emptyDataSetShouldBeForcedToDisplay:(UIScrollView *)scrollView; 185 | 186 | /** 187 | Asks the delegate to know if the empty dataset should be rendered and displayed. Default is YES. 188 | 189 | @param scrollView A scrollView subclass object informing the delegate. 190 | @return YES if the empty dataset should show. 191 | */ 192 | - (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView; 193 | 194 | /** 195 | Asks the delegate for touch permission. Default is YES. 196 | 197 | @param scrollView A scrollView subclass object informing the delegate. 198 | @return YES if the empty dataset receives touch gestures. 199 | */ 200 | - (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView; 201 | 202 | /** 203 | Asks the delegate for scroll permission. Default is NO. 204 | 205 | @param scrollView A scrollView subclass object informing the delegate. 206 | @return YES if the empty dataset is allowed to be scrollable. 207 | */ 208 | - (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView; 209 | 210 | /** 211 | Asks the delegate for image view animation permission. Default is NO. 212 | Make sure to return a valid CAAnimation object from imageAnimationForEmptyDataSet: 213 | 214 | @param scrollView A scrollView subclass object informing the delegate. 215 | @return YES if the empty dataset is allowed to animate 216 | */ 217 | - (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView; 218 | 219 | /** 220 | Tells the delegate that the empty dataset view was tapped. 221 | Use this method either to resignFirstResponder of a textfield or searchBar. 222 | 223 | @param scrollView A scrollView subclass informing the delegate. 224 | */ 225 | - (void)emptyDataSetDidTapView:(UIScrollView *)scrollView DZNEmptyDataSetDeprecated(-emptyDataSet:didTapView:); 226 | 227 | /** 228 | Tells the delegate that the action button was tapped. 229 | 230 | @param scrollView A scrollView subclass informing the delegate. 231 | */ 232 | - (void)emptyDataSetDidTapButton:(UIScrollView *)scrollView DZNEmptyDataSetDeprecated(-emptyDataSet:didTapButton:); 233 | 234 | /** 235 | Tells the delegate that the empty dataset view was tapped. 236 | Use this method either to resignFirstResponder of a textfield or searchBar. 237 | 238 | @param scrollView A scrollView subclass informing the delegate. 239 | @param view the view tapped by the user 240 | */ 241 | - (void)emptyDataSet:(UIScrollView *)scrollView didTapView:(UIView *)view; 242 | 243 | /** 244 | Tells the delegate that the action button was tapped. 245 | 246 | @param scrollView A scrollView subclass informing the delegate. 247 | @param button the button tapped by the user 248 | */ 249 | - (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button; 250 | 251 | /** 252 | Tells the delegate that the empty data set will appear. 253 | 254 | @param scrollView A scrollView subclass informing the delegate. 255 | */ 256 | - (void)emptyDataSetWillAppear:(UIScrollView *)scrollView; 257 | 258 | /** 259 | Tells the delegate that the empty data set did appear. 260 | 261 | @param scrollView A scrollView subclass informing the delegate. 262 | */ 263 | - (void)emptyDataSetDidAppear:(UIScrollView *)scrollView; 264 | 265 | /** 266 | Tells the delegate that the empty data set will disappear. 267 | 268 | @param scrollView A scrollView subclass informing the delegate. 269 | */ 270 | - (void)emptyDataSetWillDisappear:(UIScrollView *)scrollView; 271 | 272 | /** 273 | Tells the delegate that the empty data set did disappear. 274 | 275 | @param scrollView A scrollView subclass informing the delegate. 276 | */ 277 | - (void)emptyDataSetDidDisappear:(UIScrollView *)scrollView; 278 | 279 | @end 280 | 281 | #undef DZNEmptyDataSetDeprecated 282 | 283 | NS_ASSUME_NONNULL_END 284 | -------------------------------------------------------------------------------- /EmptyDataHandle/Base/UIScrollView+EmptyDataSet.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+EmptyDataSet.m 3 | // DZNEmptyDataSet 4 | // https://github.com/dzenbot/DZNEmptyDataSet 5 | // 6 | // Created by Ignacio Romero Zurbuchen on 6/20/14. 7 | // Copyright (c) 2016 DZN Labs. All rights reserved. 8 | // Licence: MIT-Licence 9 | // 10 | 11 | #import "UIScrollView+EmptyDataSet.h" 12 | #import 13 | 14 | @interface UIView (DZNConstraintBasedLayoutExtensions) 15 | 16 | - (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute; 17 | 18 | @end 19 | 20 | @interface DZNWeakObjectContainer : NSObject 21 | 22 | @property (nonatomic, readonly, weak) id weakObject; 23 | 24 | - (instancetype)initWithWeakObject:(id)object; 25 | 26 | @end 27 | 28 | @interface DZNEmptyDataSetView : UIView 29 | 30 | @property (nonatomic, readonly) UIView *contentView; 31 | @property (nonatomic, readonly) UILabel *titleLabel; 32 | @property (nonatomic, readonly) UILabel *detailLabel; 33 | @property (nonatomic, readonly) UIImageView *imageView; 34 | @property (nonatomic, readonly) UIButton *button; 35 | @property (nonatomic, strong) UIView *customView; 36 | @property (nonatomic, strong) UITapGestureRecognizer *tapGesture; 37 | 38 | @property (nonatomic, assign) CGFloat verticalOffset; 39 | @property (nonatomic, assign) CGFloat verticalSpace; 40 | 41 | @property (nonatomic, assign) BOOL fadeInOnDisplay; 42 | 43 | - (void)setupConstraints; 44 | - (void)prepareForReuse; 45 | 46 | @end 47 | 48 | 49 | #pragma mark - UIScrollView+EmptyDataSet 50 | 51 | static char const * const kEmptyDataSetSource = "emptyDataSetSource"; 52 | static char const * const kEmptyDataSetDelegate = "emptyDataSetDelegate"; 53 | static char const * const kEmptyDataSetView = "emptyDataSetView"; 54 | 55 | #define kEmptyImageViewAnimationKey @"com.dzn.emptyDataSet.imageViewAnimation" 56 | 57 | @interface UIScrollView () 58 | @property (nonatomic, readonly) DZNEmptyDataSetView *emptyDataSetView; 59 | @end 60 | 61 | @implementation UIScrollView (DZNEmptyDataSet) 62 | 63 | #pragma mark - Getters (Public) 64 | 65 | - (id)emptyDataSetSource 66 | { 67 | DZNWeakObjectContainer *container = objc_getAssociatedObject(self, kEmptyDataSetSource); 68 | return container.weakObject; 69 | } 70 | 71 | - (id)emptyDataSetDelegate 72 | { 73 | DZNWeakObjectContainer *container = objc_getAssociatedObject(self, kEmptyDataSetDelegate); 74 | return container.weakObject; 75 | } 76 | 77 | - (BOOL)isEmptyDataSetVisible 78 | { 79 | UIView *view = objc_getAssociatedObject(self, kEmptyDataSetView); 80 | return view ? !view.hidden : NO; 81 | } 82 | 83 | 84 | #pragma mark - Getters (Private) 85 | 86 | - (DZNEmptyDataSetView *)emptyDataSetView 87 | { 88 | DZNEmptyDataSetView *view = objc_getAssociatedObject(self, kEmptyDataSetView); 89 | 90 | if (!view) 91 | { 92 | view = [DZNEmptyDataSetView new]; 93 | view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 94 | view.hidden = YES; 95 | 96 | view.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dzn_didTapContentView:)]; 97 | view.tapGesture.delegate = self; 98 | [view addGestureRecognizer:view.tapGesture]; 99 | 100 | [self setEmptyDataSetView:view]; 101 | } 102 | return view; 103 | } 104 | 105 | - (BOOL)dzn_canDisplay 106 | { 107 | if (self.emptyDataSetSource && [self.emptyDataSetSource conformsToProtocol:@protocol(DZNEmptyDataSetSource)]) { 108 | if ([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]] || [self isKindOfClass:[UIScrollView class]]) { 109 | return YES; 110 | } 111 | } 112 | 113 | return NO; 114 | } 115 | 116 | - (NSInteger)dzn_itemsCount 117 | { 118 | NSInteger items = 0; 119 | 120 | // UIScollView doesn't respond to 'dataSource' so let's exit 121 | if (![self respondsToSelector:@selector(dataSource)]) { 122 | return items; 123 | } 124 | 125 | // UITableView support 126 | if ([self isKindOfClass:[UITableView class]]) { 127 | 128 | UITableView *tableView = (UITableView *)self; 129 | id dataSource = tableView.dataSource; 130 | 131 | NSInteger sections = 1; 132 | 133 | if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { 134 | sections = [dataSource numberOfSectionsInTableView:tableView]; 135 | } 136 | 137 | if (dataSource && [dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) { 138 | for (NSInteger section = 0; section < sections; section++) { 139 | items += [dataSource tableView:tableView numberOfRowsInSection:section]; 140 | } 141 | } 142 | } 143 | // UICollectionView support 144 | else if ([self isKindOfClass:[UICollectionView class]]) { 145 | 146 | UICollectionView *collectionView = (UICollectionView *)self; 147 | id dataSource = collectionView.dataSource; 148 | 149 | NSInteger sections = 1; 150 | 151 | if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { 152 | sections = [dataSource numberOfSectionsInCollectionView:collectionView]; 153 | } 154 | 155 | if (dataSource && [dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) { 156 | for (NSInteger section = 0; section < sections; section++) { 157 | items += [dataSource collectionView:collectionView numberOfItemsInSection:section]; 158 | } 159 | } 160 | } 161 | 162 | return items; 163 | } 164 | 165 | 166 | #pragma mark - Data Source Getters 167 | 168 | - (NSAttributedString *)dzn_titleLabelString 169 | { 170 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(titleForEmptyDataSet:)]) { 171 | NSAttributedString *string = [self.emptyDataSetSource titleForEmptyDataSet:self]; 172 | if (string) NSAssert([string isKindOfClass:[NSAttributedString class]], @"You must return a valid NSAttributedString object for -titleForEmptyDataSet:"); 173 | return string; 174 | } 175 | return nil; 176 | } 177 | 178 | - (NSAttributedString *)dzn_detailLabelString 179 | { 180 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(descriptionForEmptyDataSet:)]) { 181 | NSAttributedString *string = [self.emptyDataSetSource descriptionForEmptyDataSet:self]; 182 | if (string) NSAssert([string isKindOfClass:[NSAttributedString class]], @"You must return a valid NSAttributedString object for -descriptionForEmptyDataSet:"); 183 | return string; 184 | } 185 | return nil; 186 | } 187 | 188 | - (UIImage *)dzn_image 189 | { 190 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(imageForEmptyDataSet:)]) { 191 | UIImage *image = [self.emptyDataSetSource imageForEmptyDataSet:self]; 192 | if (image) NSAssert([image isKindOfClass:[UIImage class]], @"You must return a valid UIImage object for -imageForEmptyDataSet:"); 193 | return image; 194 | } 195 | return nil; 196 | } 197 | 198 | - (CAAnimation *)dzn_imageAnimation 199 | { 200 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(imageAnimationForEmptyDataSet:)]) { 201 | CAAnimation *imageAnimation = [self.emptyDataSetSource imageAnimationForEmptyDataSet:self]; 202 | if (imageAnimation) NSAssert([imageAnimation isKindOfClass:[CAAnimation class]], @"You must return a valid CAAnimation object for -imageAnimationForEmptyDataSet:"); 203 | return imageAnimation; 204 | } 205 | return nil; 206 | } 207 | 208 | - (UIColor *)dzn_imageTintColor 209 | { 210 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(imageTintColorForEmptyDataSet:)]) { 211 | UIColor *color = [self.emptyDataSetSource imageTintColorForEmptyDataSet:self]; 212 | if (color) NSAssert([color isKindOfClass:[UIColor class]], @"You must return a valid UIColor object for -imageTintColorForEmptyDataSet:"); 213 | return color; 214 | } 215 | return nil; 216 | } 217 | 218 | - (NSAttributedString *)dzn_buttonTitleForState:(UIControlState)state 219 | { 220 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(buttonTitleForEmptyDataSet:forState:)]) { 221 | NSAttributedString *string = [self.emptyDataSetSource buttonTitleForEmptyDataSet:self forState:state]; 222 | if (string) NSAssert([string isKindOfClass:[NSAttributedString class]], @"You must return a valid NSAttributedString object for -buttonTitleForEmptyDataSet:forState:"); 223 | return string; 224 | } 225 | return nil; 226 | } 227 | 228 | - (UIImage *)dzn_buttonImageForState:(UIControlState)state 229 | { 230 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(buttonImageForEmptyDataSet:forState:)]) { 231 | UIImage *image = [self.emptyDataSetSource buttonImageForEmptyDataSet:self forState:state]; 232 | if (image) NSAssert([image isKindOfClass:[UIImage class]], @"You must return a valid UIImage object for -buttonImageForEmptyDataSet:forState:"); 233 | return image; 234 | } 235 | return nil; 236 | } 237 | 238 | - (UIImage *)dzn_buttonBackgroundImageForState:(UIControlState)state 239 | { 240 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(buttonBackgroundImageForEmptyDataSet:forState:)]) { 241 | UIImage *image = [self.emptyDataSetSource buttonBackgroundImageForEmptyDataSet:self forState:state]; 242 | if (image) NSAssert([image isKindOfClass:[UIImage class]], @"You must return a valid UIImage object for -buttonBackgroundImageForEmptyDataSet:forState:"); 243 | return image; 244 | } 245 | return nil; 246 | } 247 | 248 | - (UIColor *)dzn_dataSetBackgroundColor 249 | { 250 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(backgroundColorForEmptyDataSet:)]) { 251 | UIColor *color = [self.emptyDataSetSource backgroundColorForEmptyDataSet:self]; 252 | if (color) NSAssert([color isKindOfClass:[UIColor class]], @"You must return a valid UIColor object for -backgroundColorForEmptyDataSet:"); 253 | return color; 254 | } 255 | return [UIColor clearColor]; 256 | } 257 | 258 | - (UIView *)dzn_customView 259 | { 260 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(customViewForEmptyDataSet:)]) { 261 | UIView *view = [self.emptyDataSetSource customViewForEmptyDataSet:self]; 262 | if (view) NSAssert([view isKindOfClass:[UIView class]], @"You must return a valid UIView object for -customViewForEmptyDataSet:"); 263 | return view; 264 | } 265 | return nil; 266 | } 267 | 268 | - (CGFloat)dzn_verticalOffset 269 | { 270 | CGFloat offset = 0.0; 271 | 272 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(verticalOffsetForEmptyDataSet:)]) { 273 | offset = [self.emptyDataSetSource verticalOffsetForEmptyDataSet:self]; 274 | } 275 | return offset; 276 | } 277 | 278 | - (CGFloat)dzn_verticalSpace 279 | { 280 | if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(spaceHeightForEmptyDataSet:)]) { 281 | return [self.emptyDataSetSource spaceHeightForEmptyDataSet:self]; 282 | } 283 | return 0.0; 284 | } 285 | 286 | 287 | #pragma mark - Delegate Getters & Events (Private) 288 | 289 | - (BOOL)dzn_shouldFadeIn { 290 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldFadeIn:)]) { 291 | return [self.emptyDataSetDelegate emptyDataSetShouldFadeIn:self]; 292 | } 293 | return YES; 294 | } 295 | 296 | - (BOOL)dzn_shouldDisplay 297 | { 298 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldDisplay:)]) { 299 | return [self.emptyDataSetDelegate emptyDataSetShouldDisplay:self]; 300 | } 301 | return YES; 302 | } 303 | 304 | - (BOOL)dzn_shouldBeForcedToDisplay 305 | { 306 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldBeForcedToDisplay:)]) { 307 | return [self.emptyDataSetDelegate emptyDataSetShouldBeForcedToDisplay:self]; 308 | } 309 | return NO; 310 | } 311 | 312 | - (BOOL)dzn_isTouchAllowed 313 | { 314 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAllowTouch:)]) { 315 | return [self.emptyDataSetDelegate emptyDataSetShouldAllowTouch:self]; 316 | } 317 | return YES; 318 | } 319 | 320 | - (BOOL)dzn_isScrollAllowed 321 | { 322 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAllowScroll:)]) { 323 | return [self.emptyDataSetDelegate emptyDataSetShouldAllowScroll:self]; 324 | } 325 | return NO; 326 | } 327 | 328 | - (BOOL)dzn_isImageViewAnimateAllowed 329 | { 330 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAnimateImageView:)]) { 331 | return [self.emptyDataSetDelegate emptyDataSetShouldAnimateImageView:self]; 332 | } 333 | return NO; 334 | } 335 | 336 | - (void)dzn_willAppear 337 | { 338 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetWillAppear:)]) { 339 | [self.emptyDataSetDelegate emptyDataSetWillAppear:self]; 340 | } 341 | } 342 | 343 | - (void)dzn_didAppear 344 | { 345 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidAppear:)]) { 346 | [self.emptyDataSetDelegate emptyDataSetDidAppear:self]; 347 | } 348 | } 349 | 350 | - (void)dzn_willDisappear 351 | { 352 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetWillDisappear:)]) { 353 | [self.emptyDataSetDelegate emptyDataSetWillDisappear:self]; 354 | } 355 | } 356 | 357 | - (void)dzn_didDisappear 358 | { 359 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidDisappear:)]) { 360 | [self.emptyDataSetDelegate emptyDataSetDidDisappear:self]; 361 | } 362 | } 363 | 364 | - (void)dzn_didTapContentView:(id)sender 365 | { 366 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSet:didTapView:)]) { 367 | [self.emptyDataSetDelegate emptyDataSet:self didTapView:sender]; 368 | } 369 | #pragma clang diagnostic push 370 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 371 | else if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidTapView:)]) { 372 | [self.emptyDataSetDelegate emptyDataSetDidTapView:self]; 373 | } 374 | #pragma clang diagnostic pop 375 | } 376 | 377 | - (void)dzn_didTapDataButton:(id)sender 378 | { 379 | if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSet:didTapButton:)]) { 380 | [self.emptyDataSetDelegate emptyDataSet:self didTapButton:sender]; 381 | } 382 | #pragma clang diagnostic push 383 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 384 | else if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidTapButton:)]) { 385 | [self.emptyDataSetDelegate emptyDataSetDidTapButton:self]; 386 | } 387 | #pragma clang diagnostic pop 388 | } 389 | 390 | 391 | #pragma mark - Setters (Public) 392 | 393 | - (void)setEmptyDataSetSource:(id)datasource 394 | { 395 | if (!datasource || ![self dzn_canDisplay]) { 396 | [self dzn_invalidate]; 397 | } 398 | 399 | objc_setAssociatedObject(self, kEmptyDataSetSource, [[DZNWeakObjectContainer alloc] initWithWeakObject:datasource], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 400 | 401 | // We add method sizzling for injecting -dzn_reloadData implementation to the native -reloadData implementation 402 | [self swizzleIfPossible:@selector(reloadData)]; 403 | 404 | // Exclusively for UITableView, we also inject -dzn_reloadData to -endUpdates 405 | if ([self isKindOfClass:[UITableView class]]) { 406 | [self swizzleIfPossible:@selector(endUpdates)]; 407 | } 408 | } 409 | 410 | - (void)setEmptyDataSetDelegate:(id)delegate 411 | { 412 | if (!delegate) { 413 | [self dzn_invalidate]; 414 | } 415 | 416 | objc_setAssociatedObject(self, kEmptyDataSetDelegate, [[DZNWeakObjectContainer alloc] initWithWeakObject:delegate], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 417 | } 418 | 419 | 420 | #pragma mark - Setters (Private) 421 | 422 | - (void)setEmptyDataSetView:(DZNEmptyDataSetView *)view 423 | { 424 | objc_setAssociatedObject(self, kEmptyDataSetView, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 425 | } 426 | 427 | 428 | #pragma mark - Reload APIs (Public) 429 | 430 | - (void)reloadEmptyDataSet 431 | { 432 | [self dzn_reloadEmptyDataSet]; 433 | } 434 | 435 | 436 | #pragma mark - Reload APIs (Private) 437 | 438 | - (void)dzn_reloadEmptyDataSet 439 | { 440 | if (![self dzn_canDisplay]) { 441 | return; 442 | } 443 | 444 | if (([self dzn_shouldDisplay] && [self dzn_itemsCount] == 0) || [self dzn_shouldBeForcedToDisplay]) 445 | { 446 | // Notifies that the empty dataset view will appear 447 | [self dzn_willAppear]; 448 | 449 | DZNEmptyDataSetView *view = self.emptyDataSetView; 450 | 451 | if (!view.superview) { 452 | // Send the view all the way to the back, in case a header and/or footer is present, as well as for sectionHeaders or any other content 453 | if (([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]]) && self.subviews.count > 1) { 454 | [self insertSubview:view atIndex:0]; 455 | } 456 | else { 457 | [self addSubview:view]; 458 | } 459 | } 460 | 461 | // Removing view resetting the view and its constraints it very important to guarantee a good state 462 | [view prepareForReuse]; 463 | 464 | UIView *customView = [self dzn_customView]; 465 | 466 | // If a non-nil custom view is available, let's configure it instead 467 | if (customView) { 468 | view.customView = customView; 469 | } 470 | else { 471 | // Get the data from the data source 472 | NSAttributedString *titleLabelString = [self dzn_titleLabelString]; 473 | NSAttributedString *detailLabelString = [self dzn_detailLabelString]; 474 | 475 | UIImage *buttonImage = [self dzn_buttonImageForState:UIControlStateNormal]; 476 | NSAttributedString *buttonTitle = [self dzn_buttonTitleForState:UIControlStateNormal]; 477 | 478 | UIImage *image = [self dzn_image]; 479 | UIColor *imageTintColor = [self dzn_imageTintColor]; 480 | UIImageRenderingMode renderingMode = imageTintColor ? UIImageRenderingModeAlwaysTemplate : UIImageRenderingModeAlwaysOriginal; 481 | 482 | view.verticalSpace = [self dzn_verticalSpace]; 483 | 484 | // Configure Image 485 | if (image) { 486 | if ([image respondsToSelector:@selector(imageWithRenderingMode:)]) { 487 | view.imageView.image = [image imageWithRenderingMode:renderingMode]; 488 | view.imageView.tintColor = imageTintColor; 489 | } 490 | else { 491 | // iOS 6 fallback: insert code to convert imaged if needed 492 | view.imageView.image = image; 493 | } 494 | } 495 | 496 | // Configure title label 497 | if (titleLabelString) { 498 | view.titleLabel.attributedText = titleLabelString; 499 | } 500 | 501 | // Configure detail label 502 | if (detailLabelString) { 503 | view.detailLabel.attributedText = detailLabelString; 504 | } 505 | 506 | // Configure button 507 | if (buttonImage) { 508 | [view.button setImage:buttonImage forState:UIControlStateNormal]; 509 | [view.button setImage:[self dzn_buttonImageForState:UIControlStateHighlighted] forState:UIControlStateHighlighted]; 510 | } 511 | else if (buttonTitle) { 512 | [view.button setAttributedTitle:buttonTitle forState:UIControlStateNormal]; 513 | [view.button setAttributedTitle:[self dzn_buttonTitleForState:UIControlStateHighlighted] forState:UIControlStateHighlighted]; 514 | [view.button setBackgroundImage:[self dzn_buttonBackgroundImageForState:UIControlStateNormal] forState:UIControlStateNormal]; 515 | [view.button setBackgroundImage:[self dzn_buttonBackgroundImageForState:UIControlStateHighlighted] forState:UIControlStateHighlighted]; 516 | } 517 | } 518 | 519 | // Configure offset 520 | view.verticalOffset = [self dzn_verticalOffset]; 521 | 522 | // Configure the empty dataset view 523 | view.backgroundColor = [self dzn_dataSetBackgroundColor]; 524 | view.hidden = NO; 525 | view.clipsToBounds = YES; 526 | 527 | // Configure empty dataset userInteraction permission 528 | view.userInteractionEnabled = [self dzn_isTouchAllowed]; 529 | 530 | NSLog(@"view==%d",view.userInteractionEnabled); 531 | // Configure empty dataset fade in display 532 | view.fadeInOnDisplay = [self dzn_shouldFadeIn]; 533 | 534 | [view setupConstraints]; 535 | 536 | [UIView performWithoutAnimation:^{ 537 | [view layoutIfNeeded]; 538 | }]; 539 | 540 | 541 | // Configure scroll permission 542 | self.scrollEnabled = [self dzn_isScrollAllowed]; 543 | 544 | // Configure image view animation 545 | if ([self dzn_isImageViewAnimateAllowed]) 546 | { 547 | CAAnimation *animation = [self dzn_imageAnimation]; 548 | 549 | if (animation) { 550 | [self.emptyDataSetView.imageView.layer addAnimation:animation forKey:kEmptyImageViewAnimationKey]; 551 | } 552 | } 553 | else if ([self.emptyDataSetView.imageView.layer animationForKey:kEmptyImageViewAnimationKey]) { 554 | [self.emptyDataSetView.imageView.layer removeAnimationForKey:kEmptyImageViewAnimationKey]; 555 | } 556 | 557 | // Notifies that the empty dataset view did appear 558 | [self dzn_didAppear]; 559 | } 560 | else if (self.isEmptyDataSetVisible) { 561 | [self dzn_invalidate]; 562 | } 563 | 564 | 565 | 566 | 567 | } 568 | 569 | - (void)dzn_invalidate 570 | { 571 | // Notifies that the empty dataset view will disappear 572 | [self dzn_willDisappear]; 573 | 574 | if (self.emptyDataSetView) { 575 | [self.emptyDataSetView prepareForReuse]; 576 | [self.emptyDataSetView removeFromSuperview]; 577 | 578 | [self setEmptyDataSetView:nil]; 579 | } 580 | 581 | self.scrollEnabled = YES; 582 | 583 | // Notifies that the empty dataset view did disappear 584 | [self dzn_didDisappear]; 585 | } 586 | 587 | 588 | #pragma mark - Method Swizzling 589 | 590 | static NSMutableDictionary *_impLookupTable; 591 | static NSString *const DZNSwizzleInfoPointerKey = @"pointer"; 592 | static NSString *const DZNSwizzleInfoOwnerKey = @"owner"; 593 | static NSString *const DZNSwizzleInfoSelectorKey = @"selector"; 594 | 595 | // Based on Bryce Buchanan's swizzling technique http://blog.newrelic.com/2014/04/16/right-way-to-swizzle/ 596 | // And Juzzin's ideas https://github.com/juzzin/JUSEmptyViewController 597 | 598 | void dzn_original_implementation(id self, SEL _cmd) 599 | { 600 | // Fetch original implementation from lookup table 601 | Class baseClass = dzn_baseClassToSwizzleForTarget(self); 602 | NSString *key = dzn_implementationKey(baseClass, _cmd); 603 | 604 | NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key]; 605 | NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey]; 606 | 607 | IMP impPointer = [impValue pointerValue]; 608 | 609 | // We then inject the additional implementation for reloading the empty dataset 610 | // Doing it before calling the original implementation does update the 'isEmptyDataSetVisible' flag on time. 611 | [self dzn_reloadEmptyDataSet]; 612 | 613 | // If found, call original implementation 614 | if (impPointer) { 615 | ((void(*)(id,SEL))impPointer)(self,_cmd); 616 | } 617 | } 618 | 619 | NSString *dzn_implementationKey(Class class, SEL selector) 620 | { 621 | if (!class || !selector) { 622 | return nil; 623 | } 624 | 625 | NSString *className = NSStringFromClass([class class]); 626 | 627 | NSString *selectorName = NSStringFromSelector(selector); 628 | return [NSString stringWithFormat:@"%@_%@",className,selectorName]; 629 | } 630 | 631 | Class dzn_baseClassToSwizzleForTarget(id target) 632 | { 633 | if ([target isKindOfClass:[UITableView class]]) { 634 | return [UITableView class]; 635 | } 636 | else if ([target isKindOfClass:[UICollectionView class]]) { 637 | return [UICollectionView class]; 638 | } 639 | else if ([target isKindOfClass:[UIScrollView class]]) { 640 | return [UIScrollView class]; 641 | } 642 | 643 | return nil; 644 | } 645 | 646 | - (void)swizzleIfPossible:(SEL)selector 647 | { 648 | // Check if the target responds to selector 649 | if (![self respondsToSelector:selector]) { 650 | return; 651 | } 652 | 653 | // Create the lookup table 654 | if (!_impLookupTable) { 655 | _impLookupTable = [[NSMutableDictionary alloc] initWithCapacity:3]; // 3 represent the supported base classes 656 | } 657 | 658 | // We make sure that setImplementation is called once per class kind, UITableView or UICollectionView. 659 | for (NSDictionary *info in [_impLookupTable allValues]) { 660 | Class class = [info objectForKey:DZNSwizzleInfoOwnerKey]; 661 | NSString *selectorName = [info objectForKey:DZNSwizzleInfoSelectorKey]; 662 | 663 | if ([selectorName isEqualToString:NSStringFromSelector(selector)]) { 664 | if ([self isKindOfClass:class]) { 665 | return; 666 | } 667 | } 668 | } 669 | 670 | Class baseClass = dzn_baseClassToSwizzleForTarget(self); 671 | NSString *key = dzn_implementationKey(baseClass, selector); 672 | NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey]; 673 | 674 | // If the implementation for this class already exist, skip!! 675 | if (impValue || !key || !baseClass) { 676 | return; 677 | } 678 | 679 | // Swizzle by injecting additional implementation 680 | Method method = class_getInstanceMethod(baseClass, selector); 681 | IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation); 682 | 683 | // Store the new implementation in the lookup table 684 | NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass, 685 | DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector), 686 | DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]}; 687 | 688 | [_impLookupTable setObject:swizzledInfo forKey:key]; 689 | } 690 | 691 | 692 | #pragma mark - UIGestureRecognizerDelegate Methods 693 | 694 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 695 | { 696 | if ([gestureRecognizer.view isEqual:self.emptyDataSetView]) { 697 | return [self dzn_isTouchAllowed]; 698 | } 699 | 700 | return [super gestureRecognizerShouldBegin:gestureRecognizer]; 701 | } 702 | 703 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 704 | { 705 | UIGestureRecognizer *tapGesture = self.emptyDataSetView.tapGesture; 706 | 707 | if ([gestureRecognizer isEqual:tapGesture] || [otherGestureRecognizer isEqual:tapGesture]) { 708 | return YES; 709 | } 710 | 711 | // defer to emptyDataSetDelegate's implementation if available 712 | if ( (self.emptyDataSetDelegate != (id)self) && [self.emptyDataSetDelegate respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) { 713 | return [(id)self.emptyDataSetDelegate gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer]; 714 | } 715 | 716 | return NO; 717 | } 718 | 719 | @end 720 | 721 | 722 | #pragma mark - DZNEmptyDataSetView 723 | 724 | @interface DZNEmptyDataSetView () 725 | @end 726 | 727 | @implementation DZNEmptyDataSetView 728 | @synthesize contentView = _contentView; 729 | @synthesize titleLabel = _titleLabel, detailLabel = _detailLabel, imageView = _imageView, button = _button; 730 | 731 | #pragma mark - Initialization Methods 732 | 733 | - (instancetype)init 734 | { 735 | self = [super init]; 736 | if (self) { 737 | [self addSubview:self.contentView]; 738 | } 739 | return self; 740 | } 741 | 742 | - (void)didMoveToSuperview 743 | { 744 | self.frame = self.superview.bounds; 745 | 746 | void(^fadeInBlock)(void) = ^{_contentView.alpha = 1.0;}; 747 | 748 | if (self.fadeInOnDisplay) { 749 | [UIView animateWithDuration:0.25 750 | animations:fadeInBlock 751 | completion:NULL]; 752 | } 753 | else { 754 | fadeInBlock(); 755 | } 756 | } 757 | 758 | 759 | #pragma mark - Getters 760 | 761 | - (UIView *)contentView 762 | { 763 | if (!_contentView) 764 | { 765 | _contentView = [UIView new]; 766 | _contentView.translatesAutoresizingMaskIntoConstraints = NO; 767 | _contentView.backgroundColor = [UIColor clearColor]; 768 | _contentView.userInteractionEnabled = YES; 769 | _contentView.alpha = 0; 770 | } 771 | return _contentView; 772 | } 773 | 774 | - (UIImageView *)imageView 775 | { 776 | if (!_imageView) 777 | { 778 | _imageView = [UIImageView new]; 779 | _imageView.translatesAutoresizingMaskIntoConstraints = NO; 780 | _imageView.backgroundColor = [UIColor clearColor]; 781 | _imageView.contentMode = UIViewContentModeScaleAspectFit; 782 | _imageView.userInteractionEnabled = NO; 783 | _imageView.accessibilityIdentifier = @"empty set background image"; 784 | 785 | [_contentView addSubview:_imageView]; 786 | } 787 | return _imageView; 788 | } 789 | 790 | - (UILabel *)titleLabel 791 | { 792 | if (!_titleLabel) 793 | { 794 | _titleLabel = [UILabel new]; 795 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 796 | _titleLabel.backgroundColor = [UIColor clearColor]; 797 | 798 | _titleLabel.font = [UIFont systemFontOfSize:27.0]; 799 | _titleLabel.textColor = [UIColor colorWithWhite:0.6 alpha:1.0]; 800 | _titleLabel.textAlignment = NSTextAlignmentCenter; 801 | _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; 802 | _titleLabel.numberOfLines = 0; 803 | _titleLabel.accessibilityIdentifier = @"empty set title"; 804 | 805 | [_contentView addSubview:_titleLabel]; 806 | } 807 | return _titleLabel; 808 | } 809 | 810 | - (UILabel *)detailLabel 811 | { 812 | if (!_detailLabel) 813 | { 814 | _detailLabel = [UILabel new]; 815 | _detailLabel.translatesAutoresizingMaskIntoConstraints = NO; 816 | _detailLabel.backgroundColor = [UIColor clearColor]; 817 | 818 | _detailLabel.font = [UIFont systemFontOfSize:17.0]; 819 | _detailLabel.textColor = [UIColor colorWithWhite:0.6 alpha:1.0]; 820 | _detailLabel.textAlignment = NSTextAlignmentCenter; 821 | _detailLabel.lineBreakMode = NSLineBreakByWordWrapping; 822 | _detailLabel.numberOfLines = 0; 823 | _detailLabel.accessibilityIdentifier = @"empty set detail label"; 824 | 825 | [_contentView addSubview:_detailLabel]; 826 | } 827 | return _detailLabel; 828 | } 829 | 830 | - (UIButton *)button 831 | { 832 | if (!_button) 833 | { 834 | _button = [UIButton buttonWithType:UIButtonTypeCustom]; 835 | _button.translatesAutoresizingMaskIntoConstraints = NO; 836 | _button.backgroundColor = [UIColor clearColor]; 837 | _button.layer.masksToBounds = YES; 838 | _button.layer.cornerRadius = 5.0; 839 | _button.layer.borderWidth = 0.5; 840 | _button.layer.borderColor = [UIColor lightGrayColor].CGColor; 841 | _button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; 842 | _button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; 843 | _button.accessibilityIdentifier = @"empty set button"; 844 | 845 | [_button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside]; 846 | 847 | [_contentView addSubview:_button]; 848 | } 849 | return _button; 850 | } 851 | 852 | - (BOOL)canShowImage 853 | { 854 | return (_imageView.image && _imageView.superview); 855 | } 856 | 857 | - (BOOL)canShowTitle 858 | { 859 | return (_titleLabel.attributedText.string.length > 0 && _titleLabel.superview); 860 | } 861 | 862 | - (BOOL)canShowDetail 863 | { 864 | return (_detailLabel.attributedText.string.length > 0 && _detailLabel.superview); 865 | } 866 | 867 | - (BOOL)canShowButton 868 | { 869 | if ([_button attributedTitleForState:UIControlStateNormal].string.length > 0 || [_button imageForState:UIControlStateNormal]) { 870 | return (_button.superview != nil); 871 | } 872 | return NO; 873 | } 874 | 875 | 876 | #pragma mark - Setters 877 | 878 | - (void)setCustomView:(UIView *)view 879 | { 880 | if (!view) { 881 | return; 882 | } 883 | 884 | if (_customView) { 885 | [_customView removeFromSuperview]; 886 | _customView = nil; 887 | } 888 | 889 | _customView = view; 890 | _customView.userInteractionEnabled = YES; 891 | _customView.translatesAutoresizingMaskIntoConstraints = NO; 892 | [self.contentView addSubview:_customView]; 893 | } 894 | 895 | 896 | #pragma mark - Action Methods 897 | 898 | - (void)didTapButton:(id)sender 899 | { 900 | SEL selector = NSSelectorFromString(@"dzn_didTapDataButton:"); 901 | 902 | if ([self.superview respondsToSelector:selector]) { 903 | [self.superview performSelector:selector withObject:sender afterDelay:0.0f]; 904 | } 905 | } 906 | 907 | - (void)removeAllConstraints 908 | { 909 | [self removeConstraints:self.constraints]; 910 | [_contentView removeConstraints:_contentView.constraints]; 911 | } 912 | 913 | - (void)prepareForReuse 914 | { 915 | [self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 916 | 917 | _titleLabel = nil; 918 | _detailLabel = nil; 919 | _imageView = nil; 920 | _button = nil; 921 | _customView = nil; 922 | 923 | [self removeAllConstraints]; 924 | } 925 | 926 | 927 | #pragma mark - Auto-Layout Configuration 928 | 929 | - (void)setupConstraints 930 | { 931 | // First, configure the content view constaints 932 | // The content view must alway be centered to its superview 933 | NSLayoutConstraint *centerXConstraint = [self equallyRelatedConstraintWithView:self.contentView attribute:NSLayoutAttributeCenterX]; 934 | NSLayoutConstraint *centerYConstraint = [self equallyRelatedConstraintWithView:self.contentView attribute:NSLayoutAttributeCenterY]; 935 | 936 | [self addConstraint:centerXConstraint]; 937 | [self addConstraint:centerYConstraint]; 938 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.contentView}]]; 939 | 940 | // When a custom offset is available, we adjust the vertical constraints' constants 941 | if (self.verticalOffset != 0 && self.constraints.count > 0) { 942 | centerYConstraint.constant = self.verticalOffset; 943 | } 944 | 945 | // If applicable, set the custom view's constraints 946 | if (_customView) { 947 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[customView]|" options:0 metrics:nil views:@{@"customView":_customView}]]; 948 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[customView]|" options:0 metrics:nil views:@{@"customView":_customView}]]; 949 | } 950 | else { 951 | CGFloat width = CGRectGetWidth(self.frame) ? : CGRectGetWidth([UIScreen mainScreen].bounds); 952 | CGFloat padding = roundf(width/16.0); 953 | CGFloat verticalSpace = self.verticalSpace ? : 11.0; // Default is 11 pts 954 | 955 | NSMutableArray *subviewStrings = [NSMutableArray array]; 956 | NSMutableDictionary *views = [NSMutableDictionary dictionary]; 957 | NSDictionary *metrics = @{@"padding": @(padding)}; 958 | 959 | 960 | 961 | 962 | // Assign the image view's horizontal constraints 963 | if (_imageView.superview) { 964 | 965 | [subviewStrings addObject:@"imageView"]; 966 | views[[subviewStrings lastObject]] = _imageView; 967 | 968 | [self.contentView addConstraint:[self.contentView equallyRelatedConstraintWithView:_imageView attribute:NSLayoutAttributeCenterX]]; 969 | } 970 | 971 | // Assign the title label's horizontal constraints 972 | if ([self canShowTitle]) { 973 | 974 | [subviewStrings addObject:@"titleLabel"]; 975 | views[[subviewStrings lastObject]] = _titleLabel; 976 | 977 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding@750)-[titleLabel(>=0)]-(padding@750)-|" 978 | options:0 metrics:metrics views:views]]; 979 | } 980 | // or removes from its superview 981 | else { 982 | [_titleLabel removeFromSuperview]; 983 | _titleLabel = nil; 984 | } 985 | 986 | // Assign the detail label's horizontal constraints 987 | if ([self canShowDetail]) { 988 | 989 | [subviewStrings addObject:@"detailLabel"]; 990 | views[[subviewStrings lastObject]] = _detailLabel; 991 | 992 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding@750)-[detailLabel(>=0)]-(padding@750)-|" 993 | options:0 metrics:metrics views:views]]; 994 | } 995 | // or removes from its superview 996 | else { 997 | [_detailLabel removeFromSuperview]; 998 | _detailLabel = nil; 999 | } 1000 | 1001 | // Assign the button's horizontal constraints 1002 | if ([self canShowButton]) { 1003 | 1004 | [subviewStrings addObject:@"button"]; 1005 | views[[subviewStrings lastObject]] = _button; 1006 | 1007 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding@750)-[button(>=0)]-(padding@750)-|" 1008 | options:0 metrics:metrics views:views]]; 1009 | } 1010 | // or removes from its superview 1011 | else { 1012 | [_button removeFromSuperview]; 1013 | _button = nil; 1014 | } 1015 | 1016 | 1017 | NSMutableString *verticalFormat = [NSMutableString new]; 1018 | 1019 | // Build a dynamic string format for the vertical constraints, adding a margin between each element. Default is 11 pts. 1020 | for (int i = 0; i < subviewStrings.count; i++) { 1021 | 1022 | NSString *string = subviewStrings[i]; 1023 | [verticalFormat appendFormat:@"[%@]", string]; 1024 | 1025 | if (i < subviewStrings.count-1) { 1026 | [verticalFormat appendFormat:@"-(%.f@750)-", verticalSpace]; 1027 | } 1028 | } 1029 | 1030 | // Assign the vertical constraints to the content view 1031 | if (verticalFormat.length > 0) { 1032 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|%@|", verticalFormat] 1033 | options:0 metrics:metrics views:views]]; 1034 | } 1035 | } 1036 | } 1037 | 1038 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 1039 | { 1040 | UIView *hitView = [super hitTest:point withEvent:event]; 1041 | 1042 | // Return any UIControl instance such as buttons, segmented controls, switches, etc. 1043 | if ([hitView isKindOfClass:[UIControl class]]) { 1044 | return hitView; 1045 | } 1046 | 1047 | // Return either the contentView or customView 1048 | if ([hitView isEqual:_contentView] || [hitView isEqual:_customView]) { 1049 | return hitView; 1050 | } 1051 | 1052 | return nil; 1053 | } 1054 | 1055 | @end 1056 | 1057 | 1058 | #pragma mark - UIView+DZNConstraintBasedLayoutExtensions 1059 | 1060 | @implementation UIView (DZNConstraintBasedLayoutExtensions) 1061 | 1062 | - (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute 1063 | { 1064 | return [NSLayoutConstraint constraintWithItem:view 1065 | attribute:attribute 1066 | relatedBy:NSLayoutRelationEqual 1067 | toItem:self 1068 | attribute:attribute 1069 | multiplier:1.0 1070 | constant:0.0]; 1071 | } 1072 | 1073 | @end 1074 | 1075 | #pragma mark - DZNWeakObjectContainer 1076 | 1077 | @implementation DZNWeakObjectContainer 1078 | 1079 | - (instancetype)initWithWeakObject:(id)object 1080 | { 1081 | self = [super init]; 1082 | if (self) { 1083 | _weakObject = object; 1084 | } 1085 | return self; 1086 | } 1087 | 1088 | @end 1089 | -------------------------------------------------------------------------------- /EmptyDataHandle/Base/XLBaseTableController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XLBaseTableController.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XLBaseTableController : UIViewController 12 | 13 | @property(nonatomic,retain) UITableView *xlTableView; 14 | 15 | //是否显示空数据页面 默认为显示 16 | @property(nonatomic,assign) BOOL isShowEmptyData; 17 | 18 | //空数据页面的title -- 可不传,默认为:暂无任何数据 19 | @property(nonatomic,strong) NSString *noDataTitle; 20 | //空数据页面的图片 -- 可不传,默认图片为:NoData 21 | @property(nonatomic,strong) NSString *noDataImgName; 22 | 23 | //显示副标题的时候,需要赋值副标题,否则不显示 24 | @property(nonatomic,strong) NSString *noDataDetailTitle; 25 | 26 | //按钮标题、图片 --不常用 27 | @property(nonatomic,strong) NSString *btnTitle; 28 | @property(nonatomic,strong) NSString *btnImgName; 29 | -(void)buttonEvent; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /EmptyDataHandle/Base/XLBaseTableController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XLBaseTableController.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "XLBaseTableController.h" 10 | #import "UIScrollView+EmptyDataSet.h" 11 | 12 | @interface XLBaseTableController () 13 | 14 | @end 15 | 16 | @implementation XLBaseTableController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | self.isShowEmptyData = YES; 22 | 23 | [self.view addSubview:self.xlTableView]; 24 | 25 | self.xlTableView.emptyDataSetSource = self; 26 | self.xlTableView.emptyDataSetDelegate = self; 27 | 28 | } 29 | 30 | -(UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView 31 | { 32 | if (self.noDataImgName) { 33 | return [UIImage imageNamed:self.noDataImgName]; 34 | } 35 | return [UIImage imageNamed:@"NoData"]; 36 | } 37 | 38 | - (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView 39 | { 40 | NSString *text = self.noDataTitle?self.noDataTitle:@"亲,您暂时没有任何消息!"; 41 | 42 | NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16.0f], 43 | NSForegroundColorAttributeName: [UIColor grayColor]}; 44 | 45 | return [[NSAttributedString alloc] initWithString:text attributes:attributes]; 46 | } 47 | 48 | - (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView 49 | { 50 | NSString *text = self.noDataDetailTitle; 51 | 52 | NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new]; 53 | paragraph.lineBreakMode = NSLineBreakByWordWrapping; 54 | paragraph.alignment = NSTextAlignmentCenter; 55 | 56 | NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:14.0f], 57 | NSForegroundColorAttributeName: [UIColor lightGrayColor], 58 | NSParagraphStyleAttributeName: paragraph}; 59 | 60 | return self.noDataDetailTitle?[[NSAttributedString alloc] initWithString:text attributes:attributes]:nil; 61 | } 62 | 63 | - (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView 64 | { 65 | return YES; 66 | } 67 | 68 | - (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView 69 | { 70 | return self.isShowEmptyData; 71 | } 72 | 73 | - (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView 74 | { 75 | return -self.xlTableView.tableHeaderView.frame.size.height/2.0f; 76 | } 77 | 78 | - (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state 79 | { 80 | NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16.0f]}; 81 | 82 | return self.btnTitle?[[NSAttributedString alloc] initWithString:self.btnTitle attributes:attributes]:nil; 83 | } 84 | 85 | - (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state 86 | { 87 | return self.btnImgName?[UIImage imageNamed:self.btnImgName]:nil; 88 | } 89 | 90 | - (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button 91 | { 92 | [self buttonEvent]; 93 | } 94 | 95 | #pragma mark 按钮事件 96 | -(void)buttonEvent 97 | { 98 | 99 | } 100 | 101 | -(UITableView *)xlTableView 102 | { 103 | if(_xlTableView == nil) 104 | { 105 | _xlTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height) style:UITableViewStyleGrouped]; 106 | _xlTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 1)]; 107 | _xlTableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 1)]; 108 | 109 | _xlTableView.showsVerticalScrollIndicator = NO; 110 | _xlTableView.showsHorizontalScrollIndicator = NO; 111 | } 112 | 113 | return _xlTableView; 114 | } 115 | 116 | - (void)didReceiveMemoryWarning { 117 | [super didReceiveMemoryWarning]; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /EmptyDataHandle/CustomOneVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOneVC.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "XLBaseTableController.h" 10 | 11 | @interface CustomOneVC : XLBaseTableController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /EmptyDataHandle/CustomOneVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOneVC.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "CustomOneVC.h" 10 | 11 | @interface CustomOneVC () 12 | 13 | @property(nonatomic,retain) NSMutableArray *dataSources; 14 | 15 | @end 16 | 17 | @implementation CustomOneVC 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | self.title = @"自定义显示图片"; 24 | 25 | self.dataSources = [NSMutableArray new]; 26 | 27 | self.xlTableView.dataSource = self; 28 | self.xlTableView.delegate = self; 29 | self.noDataImgName = @"NoOrderData"; 30 | self.noDataTitle = @"亲,您没有订单任何订单消息哦!"; 31 | 32 | } 33 | 34 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 35 | { 36 | static NSString *identifier = @"XLCell"; 37 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 38 | if (!cell) { 39 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 40 | } 41 | return cell; 42 | } 43 | 44 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 45 | { 46 | return self.dataSources.count; 47 | } 48 | 49 | - (void)didReceiveMemoryWarning { 50 | [super didReceiveMemoryWarning]; 51 | } 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /EmptyDataHandle/CustomThreeVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomThreeVC.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "XLBaseTableController.h" 10 | 11 | @interface CustomThreeVC : XLBaseTableController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /EmptyDataHandle/CustomThreeVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomThreeVC.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "CustomThreeVC.h" 10 | 11 | @interface CustomThreeVC () 12 | 13 | @property(nonatomic,retain) NSMutableArray *dataSources; 14 | 15 | @end 16 | 17 | @implementation CustomThreeVC 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | self.title = @"空页面显示按钮触发事件"; 24 | 25 | self.dataSources = [NSMutableArray new]; 26 | 27 | self.xlTableView.dataSource = self; 28 | self.xlTableView.delegate = self; 29 | self.noDataImgName = @"NoOrderData"; 30 | self.noDataTitle = @"亲,您没有订单任何订单消息哦!"; 31 | self.noDataDetailTitle = @"您购买的宝贝将会呈现在这里"; 32 | self.btnTitle = @"马上逛逛"; 33 | 34 | } 35 | 36 | -(void)buttonEvent 37 | { 38 | NSLog(@"马上逛逛"); 39 | } 40 | 41 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 42 | { 43 | static NSString *identifier = @"XLCell"; 44 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 45 | if (!cell) { 46 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 47 | } 48 | return cell; 49 | } 50 | 51 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 52 | { 53 | return self.dataSources.count; 54 | } 55 | 56 | - (void)didReceiveMemoryWarning { 57 | [super didReceiveMemoryWarning]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /EmptyDataHandle/CustomTwoVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTwoVC.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "XLBaseTableController.h" 10 | 11 | @interface CustomTwoVC : XLBaseTableController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /EmptyDataHandle/CustomTwoVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTwoVC.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "CustomTwoVC.h" 10 | 11 | @interface CustomTwoVC () 12 | 13 | @property(nonatomic,retain) NSMutableArray *dataSources; 14 | 15 | @end 16 | 17 | @implementation CustomTwoVC 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | self.title = @"自定义显示图片文字"; 24 | 25 | self.dataSources = [NSMutableArray new]; 26 | 27 | self.xlTableView.dataSource = self; 28 | self.xlTableView.delegate = self; 29 | self.noDataImgName = @"NoOrderData"; 30 | self.noDataTitle = @"亲,您没有订单任何订单消息哦!"; 31 | self.noDataDetailTitle = @"您购买的宝贝将会呈现在这里"; 32 | 33 | } 34 | 35 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 36 | { 37 | static NSString *identifier = @"XLCell"; 38 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 39 | if (!cell) { 40 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 41 | } 42 | return cell; 43 | } 44 | 45 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 46 | { 47 | return self.dataSources.count; 48 | } 49 | 50 | - (void)didReceiveMemoryWarning { 51 | [super didReceiveMemoryWarning]; 52 | 53 | } 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /EmptyDataHandle/DefaultEmptyVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultEmptyVC.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "XLBaseTableController.h" 10 | 11 | @interface DefaultEmptyVC : XLBaseTableController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /EmptyDataHandle/DefaultEmptyVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultEmptyVC.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "DefaultEmptyVC.h" 10 | 11 | @interface DefaultEmptyVC () 12 | 13 | @property(nonatomic,retain) NSMutableArray *dataSources; 14 | 15 | @end 16 | 17 | @implementation DefaultEmptyVC 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | self.title = @"默认空数据页面"; 24 | 25 | self.dataSources = [NSMutableArray new]; 26 | 27 | self.xlTableView.dataSource = self; 28 | self.xlTableView.delegate = self; 29 | 30 | } 31 | 32 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 33 | { 34 | static NSString *identifier = @"XLCell"; 35 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 36 | if (!cell) { 37 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 38 | } 39 | return cell; 40 | } 41 | 42 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 43 | { 44 | return self.dataSources.count; 45 | } 46 | 47 | 48 | - (void)didReceiveMemoryWarning { 49 | [super didReceiveMemoryWarning]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /EmptyDataHandle/EmptyDataHandle.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | EmptyDataHandle.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /EmptyDataHandle/EmptyDataHandle.xcdatamodeld/EmptyDataHandle.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /EmptyDataHandle/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /EmptyDataHandle/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "XLBaseTableController.h" 11 | 12 | @interface ViewController : XLBaseTableController 13 | 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /EmptyDataHandle/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "DefaultEmptyVC.h" 11 | #import "CustomOneVC.h" 12 | #import "CustomTwoVC.h" 13 | #import "CustomThreeVC.h" 14 | 15 | @interface ViewController () 16 | 17 | @property(nonatomic,retain) NSArray *dataSources; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | // Do any additional setup after loading the view. 26 | 27 | self.view.backgroundColor = [UIColor whiteColor]; 28 | self.title = @"常见空数据页面显示"; 29 | 30 | self.dataSources = @[@"默认显示",@"自定义显示图片",@"自定义显示图片、文字",@"空数据时显示按钮触发事件"]; 31 | 32 | self.xlTableView.dataSource = self; 33 | self.xlTableView.delegate = self; 34 | self.isShowEmptyData = NO; 35 | 36 | } 37 | 38 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 39 | { 40 | static NSString *identifier = @"XLCell"; 41 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 42 | if (!cell) { 43 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 44 | } 45 | cell.textLabel.text = self.dataSources[indexPath.row]; 46 | return cell; 47 | } 48 | 49 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 50 | { 51 | return self.dataSources.count; 52 | } 53 | 54 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 55 | { 56 | return 50; 57 | } 58 | 59 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 60 | { 61 | if (indexPath.row == 0) { 62 | [self.navigationController pushViewController:[[DefaultEmptyVC alloc] init] animated:YES]; 63 | } 64 | if (indexPath.row == 1) { 65 | [self.navigationController pushViewController:[[CustomOneVC alloc] init] animated:YES]; 66 | } 67 | if (indexPath.row == 2) { 68 | [self.navigationController pushViewController:[[CustomTwoVC alloc] init] animated:YES]; 69 | } 70 | if (indexPath.row == 3) { 71 | [self.navigationController pushViewController:[[CustomThreeVC alloc] init] animated:YES]; 72 | } 73 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 74 | } 75 | 76 | - (void)didReceiveMemoryWarning { 77 | [super didReceiveMemoryWarning]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /EmptyDataHandle/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // EmptyDataHandle 4 | // 5 | // Created by apple on 17/1/23. 6 | // Copyright © 2017年 ZXL. 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 | --------------------------------------------------------------------------------