├── .DS_Store ├── MutableLevelTableView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── qiager.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── yangka.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── qiager.xcuserdatad │ └── xcschemes │ │ ├── MutableLevelTableView.xcscheme │ │ └── xcschememanagement.plist │ └── yangka.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── MutableLevelTableView.xcscheme │ └── xcschememanagement.plist ├── MutableLevelTableView ├── .DS_Store ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── YKMultiLevelTableView │ ├── .DS_Store │ ├── YKMultiLevelTableView.h │ ├── YKMultiLevelTableView.m │ ├── YKNodeModel.h │ ├── YKNodeModel.m │ ├── YK_minus@2x.png │ └── YK_plus@2x.png └── main.m └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/.DS_Store -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8F736B351D810D28009F7047 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F736B341D810D28009F7047 /* main.m */; }; 11 | 8F736B381D810D28009F7047 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F736B371D810D28009F7047 /* AppDelegate.m */; }; 12 | 8F736B3B1D810D28009F7047 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F736B3A1D810D28009F7047 /* ViewController.m */; }; 13 | 8F736B3E1D810D28009F7047 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8F736B3C1D810D28009F7047 /* Main.storyboard */; }; 14 | 8F736B401D810D28009F7047 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F736B3F1D810D28009F7047 /* Assets.xcassets */; }; 15 | 8F736B431D810D28009F7047 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8F736B411D810D28009F7047 /* LaunchScreen.storyboard */; }; 16 | 8F78F3761D8298720069279D /* YK_minus@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8F78F3701D8298720069279D /* YK_minus@2x.png */; }; 17 | 8F78F3771D8298720069279D /* YK_plus@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8F78F3711D8298720069279D /* YK_plus@2x.png */; }; 18 | 8F78F3781D8298720069279D /* YKMultiLevelTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F78F3731D8298720069279D /* YKMultiLevelTableView.m */; }; 19 | 8F78F3791D8298720069279D /* YKNodeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F78F3751D8298720069279D /* YKNodeModel.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 8F736B301D810D28009F7047 /* MutableLevelTableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MutableLevelTableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 8F736B341D810D28009F7047 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 25 | 8F736B361D810D28009F7047 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 26 | 8F736B371D810D28009F7047 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 27 | 8F736B391D810D28009F7047 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 28 | 8F736B3A1D810D28009F7047 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 29 | 8F736B3D1D810D28009F7047 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | 8F736B3F1D810D28009F7047 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | 8F736B421D810D28009F7047 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | 8F736B441D810D28009F7047 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 8F78F3701D8298720069279D /* YK_minus@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "YK_minus@2x.png"; sourceTree = ""; }; 34 | 8F78F3711D8298720069279D /* YK_plus@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "YK_plus@2x.png"; sourceTree = ""; }; 35 | 8F78F3721D8298720069279D /* YKMultiLevelTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YKMultiLevelTableView.h; sourceTree = ""; }; 36 | 8F78F3731D8298720069279D /* YKMultiLevelTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YKMultiLevelTableView.m; sourceTree = ""; }; 37 | 8F78F3741D8298720069279D /* YKNodeModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YKNodeModel.h; sourceTree = ""; }; 38 | 8F78F3751D8298720069279D /* YKNodeModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YKNodeModel.m; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 8F736B2D1D810D28009F7047 /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 8F736B271D810D27009F7047 = { 53 | isa = PBXGroup; 54 | children = ( 55 | 8F736B321D810D28009F7047 /* MutableLevelTableView */, 56 | 8F736B311D810D28009F7047 /* Products */, 57 | ); 58 | sourceTree = ""; 59 | }; 60 | 8F736B311D810D28009F7047 /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 8F736B301D810D28009F7047 /* MutableLevelTableView.app */, 64 | ); 65 | name = Products; 66 | sourceTree = ""; 67 | }; 68 | 8F736B321D810D28009F7047 /* MutableLevelTableView */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 8F78F36F1D8298720069279D /* YKMultiLevelTableView */, 72 | 8F736B361D810D28009F7047 /* AppDelegate.h */, 73 | 8F736B371D810D28009F7047 /* AppDelegate.m */, 74 | 8F736B391D810D28009F7047 /* ViewController.h */, 75 | 8F736B3A1D810D28009F7047 /* ViewController.m */, 76 | 8F736B3C1D810D28009F7047 /* Main.storyboard */, 77 | 8F736B3F1D810D28009F7047 /* Assets.xcassets */, 78 | 8F736B411D810D28009F7047 /* LaunchScreen.storyboard */, 79 | 8F736B441D810D28009F7047 /* Info.plist */, 80 | 8F736B331D810D28009F7047 /* Supporting Files */, 81 | ); 82 | path = MutableLevelTableView; 83 | sourceTree = ""; 84 | }; 85 | 8F736B331D810D28009F7047 /* Supporting Files */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 8F736B341D810D28009F7047 /* main.m */, 89 | ); 90 | name = "Supporting Files"; 91 | sourceTree = ""; 92 | }; 93 | 8F78F36F1D8298720069279D /* YKMultiLevelTableView */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 8F78F3701D8298720069279D /* YK_minus@2x.png */, 97 | 8F78F3711D8298720069279D /* YK_plus@2x.png */, 98 | 8F78F3721D8298720069279D /* YKMultiLevelTableView.h */, 99 | 8F78F3731D8298720069279D /* YKMultiLevelTableView.m */, 100 | 8F78F3741D8298720069279D /* YKNodeModel.h */, 101 | 8F78F3751D8298720069279D /* YKNodeModel.m */, 102 | ); 103 | path = YKMultiLevelTableView; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 8F736B2F1D810D28009F7047 /* MutableLevelTableView */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 8F736B471D810D28009F7047 /* Build configuration list for PBXNativeTarget "MutableLevelTableView" */; 112 | buildPhases = ( 113 | 8F736B2C1D810D28009F7047 /* Sources */, 114 | 8F736B2D1D810D28009F7047 /* Frameworks */, 115 | 8F736B2E1D810D28009F7047 /* Resources */, 116 | ); 117 | buildRules = ( 118 | ); 119 | dependencies = ( 120 | ); 121 | name = MutableLevelTableView; 122 | productName = MutableLevelTableView; 123 | productReference = 8F736B301D810D28009F7047 /* MutableLevelTableView.app */; 124 | productType = "com.apple.product-type.application"; 125 | }; 126 | /* End PBXNativeTarget section */ 127 | 128 | /* Begin PBXProject section */ 129 | 8F736B281D810D28009F7047 /* Project object */ = { 130 | isa = PBXProject; 131 | attributes = { 132 | LastUpgradeCheck = 0730; 133 | ORGANIZATIONNAME = "杨卡"; 134 | TargetAttributes = { 135 | 8F736B2F1D810D28009F7047 = { 136 | CreatedOnToolsVersion = 7.3; 137 | DevelopmentTeam = 4QVJTFD4ZG; 138 | }; 139 | }; 140 | }; 141 | buildConfigurationList = 8F736B2B1D810D28009F7047 /* Build configuration list for PBXProject "MutableLevelTableView" */; 142 | compatibilityVersion = "Xcode 3.2"; 143 | developmentRegion = English; 144 | hasScannedForEncodings = 0; 145 | knownRegions = ( 146 | en, 147 | Base, 148 | ); 149 | mainGroup = 8F736B271D810D27009F7047; 150 | productRefGroup = 8F736B311D810D28009F7047 /* Products */; 151 | projectDirPath = ""; 152 | projectRoot = ""; 153 | targets = ( 154 | 8F736B2F1D810D28009F7047 /* MutableLevelTableView */, 155 | ); 156 | }; 157 | /* End PBXProject section */ 158 | 159 | /* Begin PBXResourcesBuildPhase section */ 160 | 8F736B2E1D810D28009F7047 /* Resources */ = { 161 | isa = PBXResourcesBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | 8F78F3771D8298720069279D /* YK_plus@2x.png in Resources */, 165 | 8F736B431D810D28009F7047 /* LaunchScreen.storyboard in Resources */, 166 | 8F736B401D810D28009F7047 /* Assets.xcassets in Resources */, 167 | 8F736B3E1D810D28009F7047 /* Main.storyboard in Resources */, 168 | 8F78F3761D8298720069279D /* YK_minus@2x.png in Resources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXResourcesBuildPhase section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | 8F736B2C1D810D28009F7047 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 8F736B3B1D810D28009F7047 /* ViewController.m in Sources */, 180 | 8F78F3781D8298720069279D /* YKMultiLevelTableView.m in Sources */, 181 | 8F736B381D810D28009F7047 /* AppDelegate.m in Sources */, 182 | 8F78F3791D8298720069279D /* YKNodeModel.m in Sources */, 183 | 8F736B351D810D28009F7047 /* main.m in Sources */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | /* End PBXSourcesBuildPhase section */ 188 | 189 | /* Begin PBXVariantGroup section */ 190 | 8F736B3C1D810D28009F7047 /* Main.storyboard */ = { 191 | isa = PBXVariantGroup; 192 | children = ( 193 | 8F736B3D1D810D28009F7047 /* Base */, 194 | ); 195 | name = Main.storyboard; 196 | sourceTree = ""; 197 | }; 198 | 8F736B411D810D28009F7047 /* LaunchScreen.storyboard */ = { 199 | isa = PBXVariantGroup; 200 | children = ( 201 | 8F736B421D810D28009F7047 /* Base */, 202 | ); 203 | name = LaunchScreen.storyboard; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXVariantGroup section */ 207 | 208 | /* Begin XCBuildConfiguration section */ 209 | 8F736B451D810D28009F7047 /* Debug */ = { 210 | isa = XCBuildConfiguration; 211 | buildSettings = { 212 | ALWAYS_SEARCH_USER_PATHS = NO; 213 | CLANG_ANALYZER_NONNULL = YES; 214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 215 | CLANG_CXX_LIBRARY = "libc++"; 216 | CLANG_ENABLE_MODULES = YES; 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | CLANG_WARN_BOOL_CONVERSION = YES; 219 | CLANG_WARN_CONSTANT_CONVERSION = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INT_CONVERSION = YES; 224 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = dwarf; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | ENABLE_TESTABILITY = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu99; 233 | GCC_DYNAMIC_NO_PIC = NO; 234 | GCC_NO_COMMON_BLOCKS = YES; 235 | GCC_OPTIMIZATION_LEVEL = 0; 236 | GCC_PREPROCESSOR_DEFINITIONS = ( 237 | "DEBUG=1", 238 | "$(inherited)", 239 | ); 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 242 | GCC_WARN_UNDECLARED_SELECTOR = YES; 243 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 244 | GCC_WARN_UNUSED_FUNCTION = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 247 | MTL_ENABLE_DEBUG_INFO = YES; 248 | ONLY_ACTIVE_ARCH = YES; 249 | SDKROOT = iphoneos; 250 | TARGETED_DEVICE_FAMILY = "1,2"; 251 | }; 252 | name = Debug; 253 | }; 254 | 8F736B461D810D28009F7047 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | SDKROOT = iphoneos; 288 | TARGETED_DEVICE_FAMILY = "1,2"; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Release; 292 | }; 293 | 8F736B481D810D28009F7047 /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | DEVELOPMENT_TEAM = 4QVJTFD4ZG; 298 | INFOPLIST_FILE = MutableLevelTableView/Info.plist; 299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 300 | PRODUCT_BUNDLE_IDENTIFIER = com.yangka.MutableLevelTableView; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | }; 303 | name = Debug; 304 | }; 305 | 8F736B491D810D28009F7047 /* Release */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | DEVELOPMENT_TEAM = 4QVJTFD4ZG; 310 | INFOPLIST_FILE = MutableLevelTableView/Info.plist; 311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 312 | PRODUCT_BUNDLE_IDENTIFIER = com.yangka.MutableLevelTableView; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | }; 315 | name = Release; 316 | }; 317 | /* End XCBuildConfiguration section */ 318 | 319 | /* Begin XCConfigurationList section */ 320 | 8F736B2B1D810D28009F7047 /* Build configuration list for PBXProject "MutableLevelTableView" */ = { 321 | isa = XCConfigurationList; 322 | buildConfigurations = ( 323 | 8F736B451D810D28009F7047 /* Debug */, 324 | 8F736B461D810D28009F7047 /* Release */, 325 | ); 326 | defaultConfigurationIsVisible = 0; 327 | defaultConfigurationName = Release; 328 | }; 329 | 8F736B471D810D28009F7047 /* Build configuration list for PBXNativeTarget "MutableLevelTableView" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | 8F736B481D810D28009F7047 /* Debug */, 333 | 8F736B491D810D28009F7047 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | /* End XCConfigurationList section */ 339 | }; 340 | rootObject = 8F736B281D810D28009F7047 /* Project object */; 341 | } 342 | -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/project.xcworkspace/xcuserdata/qiager.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/MutableLevelTableView.xcodeproj/project.xcworkspace/xcuserdata/qiager.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/project.xcworkspace/xcuserdata/yangka.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/MutableLevelTableView.xcodeproj/project.xcworkspace/xcuserdata/yangka.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/xcuserdata/qiager.xcuserdatad/xcschemes/MutableLevelTableView.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 | -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/xcuserdata/qiager.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MutableLevelTableView.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 8F736B2F1D810D28009F7047 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/xcuserdata/yangka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/xcuserdata/yangka.xcuserdatad/xcschemes/MutableLevelTableView.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 | -------------------------------------------------------------------------------- /MutableLevelTableView.xcodeproj/xcuserdata/yangka.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MutableLevelTableView.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 8F736B2F1D810D28009F7047 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /MutableLevelTableView/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/MutableLevelTableView/.DS_Store -------------------------------------------------------------------------------- /MutableLevelTableView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /MutableLevelTableView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /MutableLevelTableView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /MutableLevelTableView/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 | -------------------------------------------------------------------------------- /MutableLevelTableView/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 | -------------------------------------------------------------------------------- /MutableLevelTableView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /MutableLevelTableView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /MutableLevelTableView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "YKMultiLevelTableView.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | CGRect rect = self.view.frame; 22 | CGRect frame = CGRectMake(20, 20, CGRectGetWidth(rect)-40, CGRectGetHeight(rect)-20); 23 | YKMultiLevelTableView *mutableTable = [[YKMultiLevelTableView alloc] initWithFrame:frame 24 | nodes:[self returnData] 25 | rootNodeID:@"" 26 | needPreservation:YES 27 | selectBlock:^(YKNodeModel *node) { 28 | NSLog(@"--select node name=%@", node.name); 29 | 30 | }]; 31 | [self.view addSubview:mutableTable]; 32 | } 33 | 34 | //节点加入顺序没有要求,测试数据只是为了易于理解 35 | //isExpand=YES,必须在父节点展开时才有效 36 | 37 | //- (NSMutableArray*)returnData{ 38 | // YKNodeModel *node1 = [YKNodeModel nodeWithParentID:@"" name:@"Node1" childrenID:@"1" level:1 isExpand:YES]; 39 | // 40 | // YKNodeModel *node10 = [YKNodeModel nodeWithParentID:@"1" name:@"Node10" childrenID:@"10" level:2 isExpand:NO]; 41 | // YKNodeModel *node11 = [YKNodeModel nodeWithParentID:@"1" name:@"Node11" childrenID:@"11" level:2 isExpand:YES]; 42 | // 43 | // YKNodeModel *node100 = [YKNodeModel nodeWithParentID:@"10" name:@"Node100" childrenID:@"100" level:3 isExpand:NO]; 44 | // YKNodeModel *node101 = [YKNodeModel nodeWithParentID:@"10" name:@"Node101" childrenID:@"101" level:3 isExpand:NO]; 45 | // YKNodeModel *node110 = [YKNodeModel nodeWithParentID:@"11" name:@"Node110" childrenID:@"110" level:3 isExpand:NO]; 46 | // YKNodeModel *node111 = [YKNodeModel nodeWithParentID:@"11" name:@"Node111" childrenID:@"111" level:3 isExpand:YES]; 47 | // 48 | // YKNodeModel *node1110 = [YKNodeModel nodeWithParentID:@"111" name:@"Node1110" childrenID:@"1110" level:4 isExpand:NO]; 49 | // YKNodeModel *node1111 = [YKNodeModel nodeWithParentID:@"111" name:@"Node1111" childrenID:@"1111" level:4 isExpand:NO]; 50 | // 51 | // YKNodeModel *node2 = [YKNodeModel nodeWithParentID:@"" name:@"Node2" childrenID:@"2" level:1 isExpand:YES]; 52 | // 53 | // YKNodeModel *node20 = [YKNodeModel nodeWithParentID:@"2" name:@"Node20" childrenID:@"20" level:2 isExpand:NO]; 54 | // YKNodeModel *node200 = [YKNodeModel nodeWithParentID:@"20" name:@"Node200" childrenID:@"200" level:3 isExpand:NO]; 55 | // YKNodeModel *node201 = [YKNodeModel nodeWithParentID:@"20" name:@"Node101" childrenID:@"201" level:3 isExpand:NO]; 56 | // YKNodeModel *node202 = [YKNodeModel nodeWithParentID:@"20" name:@"Node202" childrenID:@"202" level:3 isExpand:NO]; 57 | // 58 | // YKNodeModel *node21 = [YKNodeModel nodeWithParentID:@"2" name:@"Node21" childrenID:@"21" level:2 isExpand:NO]; 59 | // YKNodeModel *node210 = [YKNodeModel nodeWithParentID:@"21" name:@"Node210" childrenID:@"210" level:3 isExpand:NO]; 60 | // YKNodeModel *node211 = [YKNodeModel nodeWithParentID:@"21" name:@"Node211" childrenID:@"211" level:3 isExpand:NO]; 61 | // YKNodeModel *node212 = [YKNodeModel nodeWithParentID:@"21" name:@"Node212" childrenID:@"212" level:3 isExpand:NO]; 62 | // YKNodeModel *node2110 = [YKNodeModel nodeWithParentID:@"211" name:@"Node2110" childrenID:@"2110" level:4 isExpand:NO]; 63 | // YKNodeModel *node2111 = [YKNodeModel nodeWithParentID:@"211" name:@"Node2111" childrenID:@"2111" level:4 isExpand:NO]; 64 | // 65 | // return [NSMutableArray arrayWithObjects:node1, 66 | // node10, 67 | // node100, node101, 68 | // node1110, node1111, 69 | // node11, 70 | // node110, node111, 71 | // node2, 72 | // node20, 73 | // node200, node201, node202, 74 | // node21, 75 | // node210, node211, 76 | // node2110, node2111, 77 | // node212,nil]; 78 | //} 79 | 80 | 81 | - (NSArray*)returnData{ 82 | NSArray *list = @[@{@"parentID":@"", @"name":@"Node1", @"ID":@"1"}, 83 | @{@"parentID":@"1", @"name":@"Node10", @"ID":@"10"}, 84 | @{@"parentID":@"1", @"name":@"Node11", @"ID":@"11"}, 85 | @{@"parentID":@"10", @"name":@"Node100", @"ID":@"100"}, 86 | @{@"parentID":@"10", @"name":@"Node101", @"ID":@"101"}, 87 | @{@"parentID":@"11", @"name":@"Node110", @"ID":@"110"}, 88 | @{@"parentID":@"11", @"name":@"Node111", @"ID":@"111"}, 89 | @{@"parentID":@"111", @"name":@"Node1110", @"ID":@"1110"}, 90 | @{@"parentID":@"111", @"name":@"Node1111", @"ID":@"1111"}, 91 | @{@"parentID":@"", @"name":@"Node2", @"ID":@"2"}, 92 | @{@"parentID":@"2", @"name":@"Node20", @"ID":@"20"}, 93 | @{@"parentID":@"20", @"name":@"Node200", @"ID":@"200"}, 94 | @{@"parentID":@"20", @"name":@"Node101", @"ID":@"201"}, 95 | @{@"parentID":@"20", @"name":@"Node202", @"ID":@"202"}, 96 | @{@"parentID":@"2", @"name":@"Node21", @"ID":@"21"}, 97 | @{@"parentID":@"21", @"name":@"Node210", @"ID":@"210"}, 98 | @{@"parentID":@"21", @"name":@"Node211", @"ID":@"211"}, 99 | @{@"parentID":@"21", @"name":@"Node212", @"ID":@"212"}, 100 | @{@"parentID":@"211", @"name":@"Node2110", @"ID":@"2110"}, 101 | @{@"parentID":@"211", @"name":@"Node2111", @"ID":@"2111"},]; 102 | 103 | NSMutableArray *array = [NSMutableArray array]; 104 | for (NSDictionary *dic in list) { 105 | YKNodeModel *node = [YKNodeModel nodeWithParentID:dic[@"parentID"] 106 | name:dic[@"name"] 107 | childrenID:dic[@"ID"] 108 | isExpand:NO]; 109 | [array addObject:node]; 110 | } 111 | 112 | return [array copy]; 113 | } 114 | 115 | - (void)didReceiveMemoryWarning { 116 | [super didReceiveMemoryWarning]; 117 | // Dispose of any resources that can be recreated. 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /MutableLevelTableView/YKMultiLevelTableView/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/MutableLevelTableView/YKMultiLevelTableView/.DS_Store -------------------------------------------------------------------------------- /MutableLevelTableView/YKMultiLevelTableView/YKMultiLevelTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YKMutableLevelTableView.h 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YKNodeModel.h" 11 | 12 | typedef void(^YKSelectBlock)(YKNodeModel *node); 13 | 14 | @interface YKMultiLevelTableView : UITableView 15 | 16 | - (id)initWithFrame:(CGRect)frame nodes:(NSArray*)nodes rootNodeID:(NSString*)rootID needPreservation:(BOOL)need selectBlock:(YKSelectBlock)block; 17 | @end 18 | -------------------------------------------------------------------------------- /MutableLevelTableView/YKMultiLevelTableView/YKMultiLevelTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YKMutableLevelTableView.m 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import "YKMultiLevelTableView.h" 10 | #import "YKNodeModel.h" 11 | 12 | 13 | @interface YKNodeCell : UITableViewCell 14 | 15 | @property (nonatomic, strong) YKNodeModel *node; 16 | 17 | @property (nonatomic, strong) UIImageView *leftImage; 18 | 19 | @property (nonatomic, strong) UILabel *nodeLabel; 20 | 21 | @property (nonatomic, strong) UIView *line; 22 | 23 | @property (nonatomic, assign) CGRect rect; 24 | 25 | @end 26 | 27 | 28 | #define RGB(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)] 29 | #define RandomColor RGB(arc4random_uniform(255), arc4random_uniform(255), arc4random_uniform(255), 1.0) 30 | 31 | static CGFloat const leftMargin = 30.0; //left indentation 32 | @implementation YKNodeCell 33 | 34 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 35 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 36 | if (self) { 37 | 38 | self.selectionStyle = UITableViewCellSelectionStyleNone; 39 | 40 | _leftImage = [[UIImageView alloc] initWithFrame:CGRectZero]; 41 | [self addSubview:_leftImage]; 42 | 43 | _nodeLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 44 | _nodeLabel.font =[UIFont systemFontOfSize:16]; 45 | [self addSubview:_nodeLabel]; 46 | 47 | _line = [[UIView alloc] initWithFrame:CGRectZero]; 48 | _line.backgroundColor = [UIColor lightGrayColor]; 49 | [self addSubview:_line]; 50 | } 51 | return self; 52 | } 53 | 54 | - (void)setNode:(YKNodeModel *)node{ 55 | _node = node; 56 | 57 | //set indentation 58 | CGFloat indentationX = (node.level-1)*leftMargin; 59 | [self moveNode:indentationX]; 60 | 61 | //text color 62 | CGFloat rgbValue = (node.level-1)*50; 63 | _nodeLabel.textColor = RGB(rgbValue, rgbValue, rgbValue, 1.0); 64 | 65 | 66 | _nodeLabel.text = node.name; 67 | if (node.isExpand || node.isLeaf) { 68 | _leftImage.image = [UIImage imageNamed:@"YK_minus"]; 69 | }else{ 70 | _leftImage.image = [UIImage imageNamed:@"YK_plus"]; 71 | } 72 | 73 | //hidden left log for leaf node or not 74 | // _leftImage.hidden = node.isLeaf; 75 | } 76 | 77 | - (void)moveNode:(CGFloat)indentationX{ 78 | 79 | CGFloat cellHeight = _rect.size.height; 80 | CGFloat cellWidth = _rect.size.width; 81 | 82 | CGRect frame1 = CGRectMake(0, (cellHeight-leftMargin)/2, leftMargin, leftMargin); 83 | frame1.origin.x = indentationX; 84 | _leftImage.frame = frame1; 85 | 86 | CGRect frame = CGRectMake(leftMargin, 0, cellWidth-leftMargin, cellHeight); 87 | frame.origin.x = leftMargin+indentationX; 88 | _nodeLabel.frame = frame; 89 | 90 | CGRect frame2 = CGRectMake(0, cellHeight-1, cellWidth, 1); 91 | frame2.origin.x = indentationX; 92 | _line.frame = frame2; 93 | } 94 | @end 95 | 96 | 97 | //_______________________________________________________________________________________________________________ 98 | #pragma mark 99 | #pragma mark YKMultiLevelTableView 100 | @interface YKMultiLevelTableView () 101 | 102 | @property (nonatomic, copy) NSString *rootID; 103 | 104 | //all nodes 105 | @property (nonatomic, copy) NSMutableArray *nodes; 106 | 107 | //show the last status all child nodes keep when yes, or just show next level child nodes 108 | @property (nonatomic, assign ,getter=isPreservation) BOOL preservation; 109 | 110 | @property (nonatomic, strong) NSMutableArray *tempNodes; 111 | 112 | @property (nonatomic, strong) NSMutableArray *reloadArray; 113 | 114 | @property (nonatomic, copy) YKSelectBlock block; 115 | 116 | @end 117 | 118 | static CGFloat const cellHeight = 45.0; 119 | @implementation YKMultiLevelTableView 120 | 121 | #pragma mark 122 | #pragma mark life cycle 123 | - (id)initWithFrame:(CGRect)frame nodes:(NSArray*)nodes rootNodeID:(NSString*)rootID needPreservation:(BOOL)need selectBlock:(YKSelectBlock)block{ 124 | self = [self initWithFrame:frame]; 125 | if (self) { 126 | self.rootID = rootID ?: @""; 127 | self.preservation = need; 128 | self.nodes = [nodes copy]; 129 | self.block = [block copy]; 130 | } 131 | return self; 132 | } 133 | 134 | - (id)initWithFrame:(CGRect)frame{ 135 | self = [super initWithFrame:frame]; 136 | if (self) { 137 | 138 | self.backgroundColor = [UIColor whiteColor]; 139 | _tempNodes = [NSMutableArray array]; 140 | _reloadArray = [NSMutableArray array]; 141 | self.delegate = self; 142 | self.dataSource = self; 143 | self.separatorStyle =UITableViewCellSeparatorStyleNone; 144 | } 145 | return self; 146 | } 147 | 148 | #pragma mark 149 | #pragma mark set node's leaf and root propertys ,and level 150 | - (void)setNodes:(NSMutableArray *)nodes{ 151 | _nodes = nodes; 152 | 153 | [self judgeLeafAndRootNodes]; 154 | 155 | [self updateNodesLevel]; 156 | 157 | [self addFirstLoadNodes]; 158 | 159 | [self reloadData]; 160 | } 161 | 162 | - (void)addFirstLoadNodes{ 163 | // add parent nodes on the upper level 164 | for (int i = 0 ; i<_nodes.count;i++) { 165 | 166 | YKNodeModel *node = _nodes[i]; 167 | if (node.isRoot) { 168 | [_tempNodes addObject:node]; 169 | 170 | if (node.isExpand) { 171 | [self expandNodesForParentID:node.childrenID insertIndex:[_tempNodes indexOfObject:node]]; 172 | } 173 | } 174 | } 175 | [_reloadArray removeAllObjects]; 176 | } 177 | 178 | //judge leaf node and root node 179 | - (void)judgeLeafAndRootNodes{ 180 | for (int i = 0 ; i<_nodes.count;i++) { 181 | YKNodeModel *node = _nodes[i]; 182 | 183 | 184 | BOOL isLeaf = YES; 185 | BOOL isRoot = YES; 186 | for (YKNodeModel *tempNode in _nodes) { 187 | if ([tempNode.parentID isEqualToString:node.childrenID]) { 188 | isLeaf = NO; 189 | } 190 | if ([tempNode.childrenID isEqualToString:node.parentID]) { 191 | isRoot = NO; 192 | } 193 | if (!isRoot && !isLeaf) { 194 | break; 195 | } 196 | } 197 | node.leaf = isLeaf; 198 | node.root = isRoot; 199 | } 200 | } 201 | 202 | //set depath for all nodes 203 | - (void)updateNodesLevel{ 204 | [self setDepth:1 parentIDs:@[_rootID] childrenNodes:_nodes]; 205 | } 206 | 207 | - (void)setDepth:(NSUInteger)level parentIDs:(NSArray*)parentIDs childrenNodes:(NSMutableArray*)childrenNodes{ 208 | 209 | NSMutableArray *newParentIDs = [NSMutableArray array]; 210 | NSMutableArray *leftNodes = [childrenNodes mutableCopy]; 211 | 212 | for (YKNodeModel *node in childrenNodes) { 213 | if ([parentIDs containsObject:node.parentID]) { 214 | node.level = level; 215 | [leftNodes removeObject:node]; 216 | [newParentIDs addObject:node.childrenID]; 217 | } 218 | } 219 | 220 | if (leftNodes.count>0) { 221 | level += 1; 222 | [self setDepth:level parentIDs:[newParentIDs copy] childrenNodes:leftNodes]; 223 | } 224 | } 225 | 226 | #pragma mark 227 | #pragma mark UITableView delegate 228 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 229 | return _tempNodes.count; 230 | } 231 | 232 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 233 | return cellHeight; 234 | } 235 | 236 | - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 237 | static NSString *identifier = @"cell"; 238 | YKNodeCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 239 | if (!cell) { 240 | cell = [[YKNodeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 241 | } 242 | cell.rect =CGRectMake(0, 0, CGRectGetWidth(self.frame), cellHeight); 243 | cell.node = [_tempNodes objectAtIndex:indexPath.row]; 244 | return cell; 245 | } 246 | 247 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 248 | 249 | YKNodeModel *currentNode = [_tempNodes objectAtIndex:indexPath.row]; 250 | if (currentNode.isLeaf) { 251 | self.block(currentNode); 252 | return; 253 | }else{ 254 | currentNode.expand = !currentNode.expand; 255 | } 256 | 257 | [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; 258 | 259 | [_reloadArray removeAllObjects]; 260 | if (currentNode.isExpand) { 261 | //expand 262 | [self expandNodesForParentID:currentNode.childrenID insertIndex:indexPath.row]; 263 | [tableView insertRowsAtIndexPaths:_reloadArray withRowAnimation:UITableViewRowAnimationNone]; 264 | }else{ 265 | //fold 266 | [self foldNodesForLevel:currentNode.level currentIndex:indexPath.row]; 267 | [tableView deleteRowsAtIndexPaths:_reloadArray withRowAnimation:UITableViewRowAnimationNone]; 268 | } 269 | } 270 | 271 | #pragma mark 272 | #pragma mark fold and expand 273 | - (void)foldNodesForLevel:(NSUInteger)level currentIndex:(NSUInteger)currentIndex{ 274 | 275 | if (currentIndex+1<_tempNodes.count) { 276 | NSMutableArray *tempArr = [_tempNodes copy]; 277 | for (NSUInteger i = currentIndex+1 ; i 10 | 11 | @interface YKNodeModel : NSObject 12 | 13 | @property (nonatomic, strong) NSString *parentID; 14 | 15 | @property (nonatomic, strong) NSString *childrenID; 16 | 17 | @property (nonatomic, strong) NSString *name; 18 | 19 | @property (nonatomic, assign, getter=isExpand) BOOL expand; 20 | 21 | 22 | @property (nonatomic, assign) NSUInteger level;// depth in the tree sturct 23 | 24 | @property (nonatomic, assign, getter=isLeaf) BOOL leaf; 25 | 26 | @property (nonatomic, assign, getter=isRoot) BOOL root; 27 | 28 | /** 29 | * 初始化节点 30 | * 31 | * @param parentID parent node's ID 32 | * @param name node's name 33 | * @param childrenID this node's ID 34 | * @param level depth in the tree 35 | * @param bol this node's child node is expand or not 36 | */ 37 | + (instancetype)nodeWithParentID:(NSString*)parentID name:(NSString*)name childrenID:(NSString*)childrenID level:(NSUInteger)level isExpand:(BOOL)bol; 38 | 39 | + (instancetype)nodeWithParentID:(NSString*)parentID name:(NSString*)name childrenID:(NSString*)childrenID isExpand:(BOOL)bol; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /MutableLevelTableView/YKMultiLevelTableView/YKNodeModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // YKNodeModel.m 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. All rights reserved. 7 | // 8 | 9 | #import "YKNodeModel.h" 10 | 11 | @implementation YKNodeModel 12 | 13 | + (instancetype)nodeWithParentID:(NSString *)parentID name:(NSString *)name childrenID:(NSString *)childrenID isExpand:(BOOL)bol{ 14 | return [self nodeWithParentID:parentID name:name childrenID:childrenID level:-1 isExpand:bol]; 15 | } 16 | 17 | + (instancetype)nodeWithParentID:(NSString*)parentID name:(NSString*)name childrenID:(NSString*)childrenID level:(NSUInteger)level isExpand:(BOOL)bol{ 18 | 19 | YKNodeModel *node = [[YKNodeModel alloc] init]; 20 | node.parentID = parentID; 21 | node.name = name; 22 | node.childrenID = childrenID; 23 | node.level = level; 24 | node.expand = bol; 25 | 26 | return node; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /MutableLevelTableView/YKMultiLevelTableView/YK_minus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/MutableLevelTableView/YKMultiLevelTableView/YK_minus@2x.png -------------------------------------------------------------------------------- /MutableLevelTableView/YKMultiLevelTableView/YK_plus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangKa/YKMutableLevelTableView/c1dcaffd30e437362a427e87c977b7f614bfb082/MutableLevelTableView/YKMultiLevelTableView/YK_plus@2x.png -------------------------------------------------------------------------------- /MutableLevelTableView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MutableLevelTableView 4 | // 5 | // Created by 杨卡 on 16/9/8. 6 | // Copyright © 2016年 杨卡. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YKMutableLevelTableView 2 | 3 | 在项目开发中,层级列表经常遇到,简单点的二级列表利用UITableView的Header就可以实现,再简单点的三级列表通过对Cell高度进行调整也可以实现三级列表的效果。但遇到多级列表,尤其是层次不明的动态列表就比较麻烦了。 4 | 5 | ####原理 6 | 层级列表和树形结构比较类似,不过不是二叉树,而是多叉树。每个节点只需要拥有指向父节点和子节点的两个指针,就能形成一颗树。我们将多级列表中每一级对象看作一个node,node拥有两个属性,分别为父节点和子节点的ID。 7 | 8 | 每棵树有个一个虚拟的root节点,它的ID为rootID,所有节点中凡是父节点ID为rootID的便是第一级,对应树结构中的depth(深度)。这样每一个node对象就都拥有了parentID和childrenID, childrenID为node对象的ID。 9 | 10 | 我们可以通过rootID查出第一级node,再根据第一级node的childrenID查出下一级,依次类推,确定所有节点的父子关系。同时也可以确定叶子节点和第一级节点,也可称 11 | 为根节点。 12 | 13 | ####效果图 14 | #####1.一般多级列表 15 | ![一般多级列表.gif](http://upload-images.jianshu.io/upload_images/1074670-f796caa47bcaf5bf.gif?imageMogr2/auto-orient/strip) 16 | 17 | #####2.记录节点历史状态的列表 18 | ![记录节点历史状态.gif](http://upload-images.jianshu.io/upload_images/1074670-7decff477d395e9c.gif?imageMogr2/auto-orient/strip) 19 | 20 | ###思路 21 | 1.首先根据`rootID`获取所有第一级节点,并放入UITableView的数据源`dataSourceArr`中,展示初始化列表 22 | 2.`展开:`点击节点cell,根据`childrenID`查找下一级nodes,并插入到`dataSourceArr` 中currentNode的后面,刷新展示 23 | 3.`收拢:`点击以打开节点cell,从`dataSourceArr`的CurrentIndex+1开始,如果该节点的level小于currentNode的level,则移除node,否则停止刷新列表。 24 | 4.点击cell为叶子节点则不响应展开或收拢操作,并把节点信息通过返回。 25 | 26 | dataSourceArr中是这样的一种符合树层级结构的顺序: 27 | ![dataSourceArr中顺序.png](http://upload-images.jianshu.io/upload_images/1074670-1978cba55c1d18c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 28 | 29 | ###定义节点对象 30 | ![节点对象.png](http://upload-images.jianshu.io/upload_images/1074670-6bf8ab2bf9d6bfb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 31 | 32 | ###遇到问题 33 | ####1.局部刷新的问题 34 | 每次展开或收拢以后刷新列表,一开始采用 35 | - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation 36 | 37 | 但会导致节目有整体闪烁的效果,体验不好。最后考虑采用局部刷新`insertRowsAtIndexPaths`和`deleteRowsAtIndexPaths`。 38 | 但在刷新中会报错 39 |  *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 2 from section 0 which only contains 2 rows before the update' 40 | 41 | 推测原因是`current Cell在刷新时的numberOfRowsInSection和刷新insert or del的cell时numberOfRowsInSection不一致导致`。然后尝试current cell和其他cell分别刷新,完美刷新。 42 | ``` 43 | [_reloadArray removeAllObjects]; 44 | [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; 45 | 46 | if (currentNode.isExpand) { 47 | //expand 48 | [self expandNodesForParentID:currentNode.childrenID insertIndex:indexPath.row]; 49 | [tableView insertRowsAtIndexPaths:_reloadArray withRowAnimation:UITableViewRowAnimationNone]; 50 | }else{ 51 | //fold 52 | [self foldNodesForLevel:currentNode.level currentIndex:indexPath.row]; 53 | [tableView deleteRowsAtIndexPaths:_reloadArray withRowAnimation:UITableViewRowAnimationNone]; 54 | } 55 | ``` 56 | ####2.怎么保存节点历史状态 57 | 当文件级层比较多时,有时希望能关掉层级后再打开时还能保留子层级的打开状态。我们可以会给每一个node一个是否展开的属性,当fold时只修改currentNode的expand属性,expand时对子节点序isexpand=YES的进行遍历插入。 58 | ``` 59 | //expand 60 | - (NSUInteger)expandNodesForParentID:(NSString*)parentID insertIndex:(NSUInteger)insertIndex{ 61 | 62 | for (int i = 0 ; i<_nodes.count;i++) { 63 | YKNodeModel *node = _nodes[i]; 64 | if ([node.parentID isEqualToString:parentID]) { 65 | if (!self.isPreservation) { 66 | node.expand = NO; 67 | } 68 | insertIndex++; 69 | [_tempNodes insertObject:node atIndex:insertIndex]; 70 | [_reloadArray addObject:[NSIndexPath indexPathForRow:insertIndex inSection:0]];//need reload nodes 71 | 72 | if (node.isExpand) { 73 | insertIndex = [self expandNodesForParentID:node.childrenID insertIndex:insertIndex]; 74 | } 75 | } 76 | } 77 | 78 | return insertIndex; 79 | } 80 | ``` 81 | 82 | ####demo地址: 83 | https://github.com/YangKa/YKMutableLevelTableView.git 第一次写,欢迎star 84 | 85 | --------------------------------------------------------------------------------