├── .gitignore ├── .swift-version ├── .travis.yml ├── CITreeView.podspec ├── CITreeView ├── CITreeView.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── CITreeView.xcscheme └── CITreeView │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CITreeViewClasses │ ├── CITreeView.swift │ ├── CITreeViewController.swift │ └── CITreeViewNode.swift │ ├── Controllers │ └── ViewController.swift │ ├── Info.plist │ ├── Models │ └── CITreeViewData.swift │ └── Views │ ├── CITreeViewCell.swift │ └── CITreeViewCell.xib ├── CITreeViewClasses ├── CITreeView.swift ├── CITreeViewController.swift └── CITreeViewNode.swift ├── CITreeView_01.gif ├── CITreeView_02.gif ├── LICENSE ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift #1 2 | osx_image: xcode9.2 #2 3 | xcode_project: CITreeView.xcodeproj #3 4 | xcode_scheme: CITreeView #4 5 | -------------------------------------------------------------------------------- /CITreeView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CITreeView' 3 | s.version = '1.7.0' 4 | s.summary = 'CITreeView created to implement and maintain that wanted TreeView structures for IOS platforms easy way.' 5 | 6 | s.description = <<-DESC 7 | CITreeView created to implement and maintain that wanted TreeView structures for IOS platforms easy way. CITreeView offers an infinite treeview structure and is constantly being developed 8 | DESC 9 | 10 | s.homepage = 'https://github.com/cenksk/CITreeView' 11 | s.license = { :type => 'MIT', :file => 'LICENSE' } 12 | s.author = { 'Cenk Işık' => 'isik.cnk@gmail.com' } 13 | s.source = { :git => 'https://github.com/cenksk/CITreeView.git', :tag => s.version.to_s } 14 | 15 | s.ios.deployment_target = '10.0' 16 | s.source_files = "CITreeViewClasses/*.swift" 17 | 18 | 19 | end 20 | -------------------------------------------------------------------------------- /CITreeView/CITreeView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 187EB6C620190F8E00C8DC19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6C520190F8E00C8DC19 /* AppDelegate.swift */; }; 11 | 187EB6C820190F8E00C8DC19 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6C720190F8E00C8DC19 /* ViewController.swift */; }; 12 | 187EB6CB20190F8E00C8DC19 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 187EB6C920190F8E00C8DC19 /* Main.storyboard */; }; 13 | 187EB6CD20190F8E00C8DC19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 187EB6CC20190F8E00C8DC19 /* Assets.xcassets */; }; 14 | 187EB6D020190F8E00C8DC19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 187EB6CE20190F8E00C8DC19 /* LaunchScreen.storyboard */; }; 15 | 187EB6D92019107A00C8DC19 /* CITreeViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6D82019107A00C8DC19 /* CITreeViewData.swift */; }; 16 | 187EB6DB2019164F00C8DC19 /* CITreeViewNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6DA2019164F00C8DC19 /* CITreeViewNode.swift */; }; 17 | 187EB6DD2019169B00C8DC19 /* CITreeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6DC2019169B00C8DC19 /* CITreeViewController.swift */; }; 18 | 187EB6DF2019186200C8DC19 /* CITreeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6DE2019186200C8DC19 /* CITreeView.swift */; }; 19 | 187EB6E320191BFF00C8DC19 /* CITreeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187EB6E120191BFF00C8DC19 /* CITreeViewCell.swift */; }; 20 | 187EB6E420191BFF00C8DC19 /* CITreeViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 187EB6E220191BFF00C8DC19 /* CITreeViewCell.xib */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 187EB6C220190F8E00C8DC19 /* CITreeView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CITreeView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 187EB6C520190F8E00C8DC19 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | 187EB6C720190F8E00C8DC19 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 27 | 187EB6CA20190F8E00C8DC19 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | 187EB6CC20190F8E00C8DC19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 187EB6CF20190F8E00C8DC19 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 30 | 187EB6D120190F8E00C8DC19 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 187EB6D82019107A00C8DC19 /* CITreeViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CITreeViewData.swift; sourceTree = ""; }; 32 | 187EB6DA2019164F00C8DC19 /* CITreeViewNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CITreeViewNode.swift; sourceTree = ""; }; 33 | 187EB6DC2019169B00C8DC19 /* CITreeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CITreeViewController.swift; sourceTree = ""; }; 34 | 187EB6DE2019186200C8DC19 /* CITreeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CITreeView.swift; sourceTree = ""; }; 35 | 187EB6E120191BFF00C8DC19 /* CITreeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CITreeViewCell.swift; sourceTree = ""; }; 36 | 187EB6E220191BFF00C8DC19 /* CITreeViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CITreeViewCell.xib; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 187EB6BF20190F8E00C8DC19 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 187EB6B920190F8E00C8DC19 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 187EB6C420190F8E00C8DC19 /* CITreeView */, 54 | 187EB6C320190F8E00C8DC19 /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 187EB6C320190F8E00C8DC19 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 187EB6C220190F8E00C8DC19 /* CITreeView.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | 187EB6C420190F8E00C8DC19 /* CITreeView */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 187EB6E620191F2200C8DC19 /* Models */, 70 | 187EB6E520191CFB00C8DC19 /* Controllers */, 71 | 187EB6E020191BE600C8DC19 /* Views */, 72 | 187EB6D72019102C00C8DC19 /* CITreeViewClasses */, 73 | 187EB6C520190F8E00C8DC19 /* AppDelegate.swift */, 74 | 187EB6C920190F8E00C8DC19 /* Main.storyboard */, 75 | 187EB6CC20190F8E00C8DC19 /* Assets.xcassets */, 76 | 187EB6CE20190F8E00C8DC19 /* LaunchScreen.storyboard */, 77 | 187EB6D120190F8E00C8DC19 /* Info.plist */, 78 | ); 79 | path = CITreeView; 80 | sourceTree = ""; 81 | }; 82 | 187EB6D72019102C00C8DC19 /* CITreeViewClasses */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 187EB6DA2019164F00C8DC19 /* CITreeViewNode.swift */, 86 | 187EB6DC2019169B00C8DC19 /* CITreeViewController.swift */, 87 | 187EB6DE2019186200C8DC19 /* CITreeView.swift */, 88 | ); 89 | path = CITreeViewClasses; 90 | sourceTree = ""; 91 | }; 92 | 187EB6E020191BE600C8DC19 /* Views */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 187EB6E120191BFF00C8DC19 /* CITreeViewCell.swift */, 96 | 187EB6E220191BFF00C8DC19 /* CITreeViewCell.xib */, 97 | ); 98 | path = Views; 99 | sourceTree = ""; 100 | }; 101 | 187EB6E520191CFB00C8DC19 /* Controllers */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 187EB6C720190F8E00C8DC19 /* ViewController.swift */, 105 | ); 106 | path = Controllers; 107 | sourceTree = ""; 108 | }; 109 | 187EB6E620191F2200C8DC19 /* Models */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 187EB6D82019107A00C8DC19 /* CITreeViewData.swift */, 113 | ); 114 | path = Models; 115 | sourceTree = ""; 116 | }; 117 | /* End PBXGroup section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | 187EB6C120190F8E00C8DC19 /* CITreeView */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = 187EB6D420190F8E00C8DC19 /* Build configuration list for PBXNativeTarget "CITreeView" */; 123 | buildPhases = ( 124 | 187EB6BE20190F8E00C8DC19 /* Sources */, 125 | 187EB6BF20190F8E00C8DC19 /* Frameworks */, 126 | 187EB6C020190F8E00C8DC19 /* Resources */, 127 | ); 128 | buildRules = ( 129 | ); 130 | dependencies = ( 131 | ); 132 | name = CITreeView; 133 | productName = CITreeView; 134 | productReference = 187EB6C220190F8E00C8DC19 /* CITreeView.app */; 135 | productType = "com.apple.product-type.application"; 136 | }; 137 | /* End PBXNativeTarget section */ 138 | 139 | /* Begin PBXProject section */ 140 | 187EB6BA20190F8E00C8DC19 /* Project object */ = { 141 | isa = PBXProject; 142 | attributes = { 143 | LastSwiftUpdateCheck = 0920; 144 | LastUpgradeCheck = 0920; 145 | ORGANIZATIONNAME = "Cenk Işık"; 146 | TargetAttributes = { 147 | 187EB6C120190F8E00C8DC19 = { 148 | CreatedOnToolsVersion = 9.2; 149 | LastSwiftMigration = 1010; 150 | ProvisioningStyle = Automatic; 151 | }; 152 | }; 153 | }; 154 | buildConfigurationList = 187EB6BD20190F8E00C8DC19 /* Build configuration list for PBXProject "CITreeView" */; 155 | compatibilityVersion = "Xcode 8.0"; 156 | developmentRegion = en; 157 | hasScannedForEncodings = 0; 158 | knownRegions = ( 159 | en, 160 | Base, 161 | ); 162 | mainGroup = 187EB6B920190F8E00C8DC19; 163 | productRefGroup = 187EB6C320190F8E00C8DC19 /* Products */; 164 | projectDirPath = ""; 165 | projectRoot = ""; 166 | targets = ( 167 | 187EB6C120190F8E00C8DC19 /* CITreeView */, 168 | ); 169 | }; 170 | /* End PBXProject section */ 171 | 172 | /* Begin PBXResourcesBuildPhase section */ 173 | 187EB6C020190F8E00C8DC19 /* Resources */ = { 174 | isa = PBXResourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | 187EB6D020190F8E00C8DC19 /* LaunchScreen.storyboard in Resources */, 178 | 187EB6CD20190F8E00C8DC19 /* Assets.xcassets in Resources */, 179 | 187EB6E420191BFF00C8DC19 /* CITreeViewCell.xib in Resources */, 180 | 187EB6CB20190F8E00C8DC19 /* Main.storyboard in Resources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXResourcesBuildPhase section */ 185 | 186 | /* Begin PBXSourcesBuildPhase section */ 187 | 187EB6BE20190F8E00C8DC19 /* Sources */ = { 188 | isa = PBXSourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 187EB6DB2019164F00C8DC19 /* CITreeViewNode.swift in Sources */, 192 | 187EB6D92019107A00C8DC19 /* CITreeViewData.swift in Sources */, 193 | 187EB6DD2019169B00C8DC19 /* CITreeViewController.swift in Sources */, 194 | 187EB6C820190F8E00C8DC19 /* ViewController.swift in Sources */, 195 | 187EB6E320191BFF00C8DC19 /* CITreeViewCell.swift in Sources */, 196 | 187EB6DF2019186200C8DC19 /* CITreeView.swift in Sources */, 197 | 187EB6C620190F8E00C8DC19 /* AppDelegate.swift in Sources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXSourcesBuildPhase section */ 202 | 203 | /* Begin PBXVariantGroup section */ 204 | 187EB6C920190F8E00C8DC19 /* Main.storyboard */ = { 205 | isa = PBXVariantGroup; 206 | children = ( 207 | 187EB6CA20190F8E00C8DC19 /* Base */, 208 | ); 209 | name = Main.storyboard; 210 | sourceTree = ""; 211 | }; 212 | 187EB6CE20190F8E00C8DC19 /* LaunchScreen.storyboard */ = { 213 | isa = PBXVariantGroup; 214 | children = ( 215 | 187EB6CF20190F8E00C8DC19 /* Base */, 216 | ); 217 | name = LaunchScreen.storyboard; 218 | sourceTree = ""; 219 | }; 220 | /* End PBXVariantGroup section */ 221 | 222 | /* Begin XCBuildConfiguration section */ 223 | 187EB6D220190F8E00C8DC19 /* Debug */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | CLANG_ANALYZER_NONNULL = YES; 228 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 230 | CLANG_CXX_LIBRARY = "libc++"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_COMMA = YES; 236 | CLANG_WARN_CONSTANT_CONVERSION = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | CODE_SIGN_IDENTITY = "iPhone Developer"; 253 | COPY_PHASE_STRIP = NO; 254 | DEBUG_INFORMATION_FORMAT = dwarf; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | ENABLE_TESTABILITY = YES; 257 | GCC_C_LANGUAGE_STANDARD = gnu11; 258 | GCC_DYNAMIC_NO_PIC = NO; 259 | GCC_NO_COMMON_BLOCKS = YES; 260 | GCC_OPTIMIZATION_LEVEL = 0; 261 | GCC_PREPROCESSOR_DEFINITIONS = ( 262 | "DEBUG=1", 263 | "$(inherited)", 264 | ); 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 272 | MTL_ENABLE_DEBUG_INFO = YES; 273 | ONLY_ACTIVE_ARCH = YES; 274 | SDKROOT = iphoneos; 275 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 277 | }; 278 | name = Debug; 279 | }; 280 | 187EB6D320190F8E00C8DC19 /* Release */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_ANALYZER_NONNULL = YES; 285 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_ENABLE_MODULES = YES; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_COMMA = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 304 | CLANG_WARN_STRICT_PROTOTYPES = YES; 305 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 306 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 307 | CLANG_WARN_UNREACHABLE_CODE = YES; 308 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 309 | CODE_SIGN_IDENTITY = "iPhone Developer"; 310 | COPY_PHASE_STRIP = NO; 311 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 312 | ENABLE_NS_ASSERTIONS = NO; 313 | ENABLE_STRICT_OBJC_MSGSEND = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu11; 315 | GCC_NO_COMMON_BLOCKS = YES; 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 318 | GCC_WARN_UNDECLARED_SELECTOR = YES; 319 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 320 | GCC_WARN_UNUSED_FUNCTION = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 323 | MTL_ENABLE_DEBUG_INFO = NO; 324 | SDKROOT = iphoneos; 325 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 326 | VALIDATE_PRODUCT = YES; 327 | }; 328 | name = Release; 329 | }; 330 | 187EB6D520190F8E00C8DC19 /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | CODE_SIGN_STYLE = Automatic; 335 | DEVELOPMENT_TEAM = ""; 336 | INFOPLIST_FILE = CITreeView/Info.plist; 337 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 338 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 339 | PRODUCT_BUNDLE_IDENTIFIER = com.cenkisik.CITreeView; 340 | PRODUCT_NAME = "$(TARGET_NAME)"; 341 | SWIFT_VERSION = 4.2; 342 | TARGETED_DEVICE_FAMILY = "1,2"; 343 | }; 344 | name = Debug; 345 | }; 346 | 187EB6D620190F8E00C8DC19 /* Release */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 350 | CODE_SIGN_STYLE = Automatic; 351 | DEVELOPMENT_TEAM = ""; 352 | INFOPLIST_FILE = CITreeView/Info.plist; 353 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 354 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 355 | PRODUCT_BUNDLE_IDENTIFIER = com.cenkisik.CITreeView; 356 | PRODUCT_NAME = "$(TARGET_NAME)"; 357 | SWIFT_VERSION = 4.2; 358 | TARGETED_DEVICE_FAMILY = "1,2"; 359 | }; 360 | name = Release; 361 | }; 362 | /* End XCBuildConfiguration section */ 363 | 364 | /* Begin XCConfigurationList section */ 365 | 187EB6BD20190F8E00C8DC19 /* Build configuration list for PBXProject "CITreeView" */ = { 366 | isa = XCConfigurationList; 367 | buildConfigurations = ( 368 | 187EB6D220190F8E00C8DC19 /* Debug */, 369 | 187EB6D320190F8E00C8DC19 /* Release */, 370 | ); 371 | defaultConfigurationIsVisible = 0; 372 | defaultConfigurationName = Release; 373 | }; 374 | 187EB6D420190F8E00C8DC19 /* Build configuration list for PBXNativeTarget "CITreeView" */ = { 375 | isa = XCConfigurationList; 376 | buildConfigurations = ( 377 | 187EB6D520190F8E00C8DC19 /* Debug */, 378 | 187EB6D620190F8E00C8DC19 /* Release */, 379 | ); 380 | defaultConfigurationIsVisible = 0; 381 | defaultConfigurationName = Release; 382 | }; 383 | /* End XCConfigurationList section */ 384 | }; 385 | rootObject = 187EB6BA20190F8E00C8DC19 /* Project object */; 386 | } 387 | -------------------------------------------------------------------------------- /CITreeView/CITreeView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CITreeView/CITreeView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CITreeView/CITreeView.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CITreeView/CITreeView.xcodeproj/xcshareddata/xcschemes/CITreeView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // 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. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CITreeView/CITreeView/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 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/CITreeViewClasses/CITreeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewController.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc 12 | public protocol CITreeViewDataSource: NSObjectProtocol { 13 | func treeView(_ treeView: CITreeView, cellForRowAt indexPath: IndexPath, with treeViewNode: CITreeViewNode) -> UITableViewCell 14 | func treeViewSelectedNodeChildren(for treeViewNodeItem: Any) -> [Any] 15 | func treeViewDataArray() -> [Any] 16 | } 17 | 18 | @objc 19 | public protocol CITreeViewDelegate: NSObjectProtocol { 20 | func treeView(_ treeView: CITreeView, heightForRowAt indexPath: IndexPath, with treeViewNode: CITreeViewNode) -> CGFloat 21 | func treeView(_ treeView: CITreeView, didSelectRowAt treeViewNode: CITreeViewNode, at indexPath: IndexPath) 22 | func treeView(_ treeView: CITreeView, didDeselectRowAt treeViewNode: CITreeViewNode, at indexPath: IndexPath) 23 | func treeViewNode(_ treeViewNode: CITreeViewNode, willExpandAt indexPath: IndexPath) 24 | func treeViewNode(_ treeViewNode: CITreeViewNode, didExpandAt indexPath: IndexPath) 25 | func treeViewNode(_ treeViewNode: CITreeViewNode, willCollapseAt indexPath: IndexPath) 26 | func treeViewNode(_ treeViewNode: CITreeViewNode, didCollapseAt indexPath: IndexPath) 27 | 28 | } 29 | 30 | public class CITreeView: UITableView { 31 | 32 | @IBOutlet open weak var treeViewDataSource:CITreeViewDataSource? 33 | @IBOutlet open weak var treeViewDelegate: CITreeViewDelegate? 34 | fileprivate var treeViewController = CITreeViewController(treeViewNodes: []) 35 | fileprivate var selectedTreeViewNode:CITreeViewNode? 36 | public var collapseNoneSelectedRows = false 37 | fileprivate var mainDataArray:[CITreeViewNode] = [] 38 | 39 | 40 | override public init(frame: CGRect, style: UITableView.Style) { 41 | super.init(frame: frame, style: style) 42 | commonInit() 43 | } 44 | 45 | required public init?(coder aDecoder: NSCoder) { 46 | super.init(coder: aDecoder) 47 | commonInit() 48 | } 49 | 50 | func commonInit(){ 51 | super.delegate = self 52 | super.dataSource = self 53 | treeViewController.treeViewControllerDelegate = self as CITreeViewControllerDelegate 54 | self.backgroundColor = UIColor.clear 55 | } 56 | 57 | override public func reloadData() { 58 | 59 | guard let treeViewDataSource = self.treeViewDataSource else { 60 | mainDataArray = [CITreeViewNode]() 61 | return 62 | } 63 | 64 | mainDataArray = [CITreeViewNode]() 65 | treeViewController.treeViewNodes.removeAll() 66 | for item in treeViewDataSource.treeViewDataArray() { 67 | treeViewController.addTreeViewNode(with: item) 68 | } 69 | mainDataArray = treeViewController.treeViewNodes 70 | 71 | super.reloadData() 72 | } 73 | 74 | public func reloadDataWithoutChangingRowStates() { 75 | 76 | guard let treeViewDataSource = self.treeViewDataSource else { 77 | mainDataArray = [CITreeViewNode]() 78 | return 79 | } 80 | 81 | if treeViewDataSource.treeViewDataArray().count > treeViewController.treeViewNodes.count { 82 | mainDataArray = [CITreeViewNode]() 83 | treeViewController.treeViewNodes.removeAll() 84 | for item in treeViewDataSource.treeViewDataArray() { 85 | treeViewController.addTreeViewNode(with: item) 86 | } 87 | mainDataArray = treeViewController.treeViewNodes 88 | } 89 | super.reloadData() 90 | } 91 | 92 | fileprivate func deleteRows() { 93 | if treeViewController.indexPathsArray.count > 0 { 94 | self.beginUpdates() 95 | self.deleteRows(at: treeViewController.indexPathsArray, with: .automatic) 96 | self.endUpdates() 97 | } 98 | } 99 | 100 | public func deleteRow(at indexPath:IndexPath) { 101 | self.beginUpdates() 102 | self.deleteRows(at: [indexPath], with: .automatic) 103 | self.endUpdates() 104 | } 105 | 106 | fileprivate func insertRows() { 107 | if treeViewController.indexPathsArray.count > 0 { 108 | self.beginUpdates() 109 | self.insertRows(at: treeViewController.indexPathsArray, with: .automatic) 110 | self.endUpdates() 111 | } 112 | } 113 | 114 | fileprivate func collapseRows(for treeViewNode: CITreeViewNode, atIndexPath indexPath: IndexPath ,completion: @escaping () -> Void) { 115 | guard let treeViewDelegate = self.treeViewDelegate else { return } 116 | if #available(iOS 11.0, *) { 117 | self.performBatchUpdates({ 118 | deleteRows() 119 | }, completion: { (complete) in 120 | treeViewDelegate.treeViewNode(treeViewNode, didCollapseAt: indexPath) 121 | completion() 122 | }) 123 | } else { 124 | CATransaction.begin() 125 | CATransaction.setCompletionBlock({ 126 | treeViewDelegate.treeViewNode(treeViewNode, didCollapseAt: indexPath) 127 | completion() 128 | }) 129 | deleteRows() 130 | CATransaction.commit() 131 | } 132 | } 133 | 134 | fileprivate func expandRows(for treeViewNode: CITreeViewNode, withSelected indexPath: IndexPath) { 135 | guard let treeViewDelegate = self.treeViewDelegate else {return} 136 | if #available(iOS 11.0, *) { 137 | self.performBatchUpdates({ 138 | insertRows() 139 | }, completion: { (complete) in 140 | treeViewDelegate.treeViewNode(treeViewNode, didExpandAt: indexPath) 141 | }) 142 | } else { 143 | CATransaction.begin() 144 | CATransaction.setCompletionBlock({ 145 | treeViewDelegate.treeViewNode(treeViewNode, didExpandAt: indexPath) 146 | }) 147 | insertRows() 148 | CATransaction.commit() 149 | } 150 | } 151 | 152 | func getAllCells() -> [UITableViewCell] { 153 | var cells = [UITableViewCell]() 154 | for section in 0 ..< self.numberOfSections{ 155 | for row in 0 ..< self.numberOfRows(inSection: section){ 156 | cells.append(self.cellForRow(at: IndexPath(row: row, section: section))!) 157 | } 158 | } 159 | return cells 160 | } 161 | 162 | public func expandAllRows() { 163 | treeViewController.expandAllRows() 164 | reloadDataWithoutChangingRowStates() 165 | } 166 | 167 | public func collapseAllRows() { 168 | treeViewController.collapseAllRows() 169 | reloadDataWithoutChangingRowStates() 170 | } 171 | } 172 | 173 | extension CITreeView: UITableViewDelegate { 174 | 175 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 176 | let treeViewNode = treeViewController.getTreeViewNode(atIndex: indexPath.row) 177 | return (self.treeViewDelegate?.treeView(tableView as! CITreeView, heightForRowAt: indexPath, with: treeViewNode))! 178 | } 179 | 180 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 181 | selectedTreeViewNode = treeViewController.getTreeViewNode(atIndex: indexPath.row) 182 | guard let treeViewDelegate = self.treeViewDelegate else { return } 183 | 184 | if let justSelectedTreeViewNode = selectedTreeViewNode { 185 | treeViewDelegate.treeView(tableView as! CITreeView, didSelectRowAt: justSelectedTreeViewNode, at: indexPath) 186 | var willExpandIndexPath = indexPath 187 | if justSelectedTreeViewNode.expand { 188 | treeViewController.collapseRows(for: justSelectedTreeViewNode, atIndexPath: indexPath) 189 | collapseRows(for: justSelectedTreeViewNode, atIndexPath: indexPath){} 190 | } else { 191 | if collapseNoneSelectedRows, 192 | selectedTreeViewNode?.level == 0, 193 | let collapsedTreeViewNode = treeViewController.collapseAllRowsExceptOne(), 194 | treeViewController.indexPathsArray.count > 0 { 195 | 196 | collapseRows(for: collapsedTreeViewNode, atIndexPath: indexPath){ 197 | for (index, treeViewNode) in self.mainDataArray.enumerated() { 198 | if treeViewNode == justSelectedTreeViewNode { 199 | willExpandIndexPath.row = index 200 | } 201 | } 202 | self.treeViewController.expandRows(atIndexPath: willExpandIndexPath, with: justSelectedTreeViewNode, openWithChildrens: false) 203 | self.expandRows(for: justSelectedTreeViewNode, withSelected: indexPath) 204 | } 205 | } else { 206 | treeViewController.expandRows(atIndexPath: willExpandIndexPath, with: justSelectedTreeViewNode, openWithChildrens: false) 207 | expandRows(for: justSelectedTreeViewNode, withSelected: indexPath) 208 | } 209 | } 210 | } 211 | } 212 | } 213 | 214 | extension CITreeView: UITableViewDataSource { 215 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 216 | return treeViewController.treeViewNodes.count 217 | } 218 | 219 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 220 | let treeViewNode = treeViewController.getTreeViewNode(atIndex: indexPath.row) 221 | return (self.treeViewDataSource?.treeView(tableView as! CITreeView, cellForRowAt: indexPath, with: treeViewNode))! 222 | } 223 | } 224 | 225 | extension CITreeView: CITreeViewControllerDelegate { 226 | public func getChildren(for treeViewNodeItem: Any, at indexPath: IndexPath) -> [Any] { 227 | return (self.treeViewDataSource?.treeViewSelectedNodeChildren(for: treeViewNodeItem)) ?? [] 228 | } 229 | 230 | public func willCollapseTreeViewNode(_ treeViewNode: CITreeViewNode, at indexPath: IndexPath) { 231 | self.treeViewDelegate?.treeViewNode(treeViewNode, willCollapseAt: indexPath) 232 | } 233 | 234 | public func willExpandTreeViewNode(_ treeViewNode: CITreeViewNode, at indexPath: IndexPath) { 235 | self.treeViewDelegate?.treeViewNode(treeViewNode, willExpandAt: indexPath) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/CITreeViewClasses/CITreeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewController.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol CITreeViewControllerDelegate: NSObjectProtocol { 12 | func getChildren(for treeViewNodeItem: Any, at indexPath: IndexPath) -> [Any] 13 | func willExpandTreeViewNode(_ treeViewNode: CITreeViewNode, at indexPath: IndexPath) 14 | func willCollapseTreeViewNode(_ treeViewNode: CITreeViewNode, at indexPath: IndexPath) 15 | } 16 | 17 | public class CITreeViewController: NSObject { 18 | 19 | var treeViewNodes: [CITreeViewNode] = [] 20 | var indexPathsArray: [IndexPath] = [] 21 | weak var treeViewControllerDelegate: CITreeViewControllerDelegate? 22 | 23 | init(treeViewNodes: [CITreeViewNode]) { 24 | self.treeViewNodes = treeViewNodes 25 | } 26 | 27 | //MARK: Tree View Nodes Functions 28 | func addTreeViewNode(with item: Any) { 29 | let treeViewNode = CITreeViewNode(item: item) 30 | treeViewNodes.append(treeViewNode) 31 | } 32 | 33 | func getTreeViewNode(atIndex index: Int) -> CITreeViewNode { 34 | if treeViewNodes.indices.contains(index){ 35 | return treeViewNodes[index] 36 | } 37 | return treeViewNodes.last! 38 | } 39 | 40 | func index(of treeViewNode: CITreeViewNode) -> Int? { 41 | return treeViewNodes.index(of: treeViewNode) 42 | } 43 | 44 | func insertTreeViewNode(parent parentTreeViewNode: CITreeViewNode, with item: Any, to index: Int) { 45 | let treeViewNode = CITreeViewNode(item: item) 46 | treeViewNode.parentNode = parentTreeViewNode 47 | treeViewNodes.insert(treeViewNode, at: index) 48 | } 49 | 50 | func removeTreeViewNodesAtRange(from start:Int , to end:Int) { 51 | treeViewNodes.removeSubrange(start ... end) 52 | } 53 | 54 | func setExpandTreeViewNode(atIndex index:Int) { 55 | treeViewNodes[index].expand = true 56 | } 57 | 58 | func setCollapseTreeViewNode(atIndex index:Int) { 59 | treeViewNodes[index].expand = false 60 | } 61 | 62 | func setLevelTreeViewNode(atIndex index: Int, to level: Int) { 63 | treeViewNodes[index].level = level + 1 64 | } 65 | 66 | // MARK: Expand Rows 67 | 68 | func addIndexPath(withRow row: Int) { 69 | let indexPath = IndexPath(row: row, section: 0) 70 | indexPathsArray.append(indexPath) 71 | } 72 | 73 | func expandRows(atIndexPath indexPath: IndexPath, with selectedTreeViewNode: CITreeViewNode) { 74 | let children = self.treeViewControllerDelegate?.getChildren(for: selectedTreeViewNode.item, at: indexPath) 75 | indexPathsArray = [IndexPath]() 76 | var row = indexPath.row + 1 77 | 78 | if (children?.count)! > 0 { 79 | self.treeViewControllerDelegate?.willExpandTreeViewNode(selectedTreeViewNode, at: indexPath) 80 | setExpandTreeViewNode(atIndex: indexPath.row) 81 | } 82 | 83 | for item in children!{ 84 | addIndexPath(withRow: row) 85 | insertTreeViewNode(parent: selectedTreeViewNode, with: item, to: row) 86 | setLevelTreeViewNode(atIndex: row, to: selectedTreeViewNode.level) 87 | row += 1 88 | } 89 | } 90 | 91 | // MARK: Collapse Rows 92 | func removeIndexPath(withRow row: inout Int, and indexPath:IndexPath) { 93 | let treeViewNode = getTreeViewNode(atIndex: row) 94 | let children = self.treeViewControllerDelegate?.getChildren(for: treeViewNode.item, at: indexPath) 95 | 96 | let index = IndexPath(row: row, section: indexPath.section) 97 | indexPathsArray.append(index) 98 | row += 1 99 | 100 | if (treeViewNode.expand) { 101 | for _ in children!{ 102 | removeIndexPath(withRow: &row, and: indexPath) 103 | } 104 | } 105 | } 106 | 107 | func collapseRows(for treeViewNode: CITreeViewNode, atIndexPath indexPath: IndexPath) { 108 | guard let treeViewControllerDelegate = self.treeViewControllerDelegate else { return } 109 | let treeViewNodeChildren = treeViewControllerDelegate.getChildren(for: treeViewNode.item, at: indexPath) 110 | indexPathsArray = [IndexPath]() 111 | var row = indexPath.row + 1 112 | 113 | if treeViewNodeChildren.count > 0 { 114 | treeViewControllerDelegate.willCollapseTreeViewNode(treeViewNode, at: indexPath) 115 | } 116 | 117 | setCollapseTreeViewNode(atIndex: indexPath.row) 118 | 119 | for _ in treeViewNodeChildren{ 120 | removeIndexPath(withRow: &row, and: indexPath) 121 | } 122 | 123 | if indexPathsArray.count > 0 { 124 | removeTreeViewNodesAtRange(from: (indexPathsArray.first?.row)!, to: (indexPathsArray.last?.row)!) 125 | } 126 | } 127 | 128 | 129 | @discardableResult func collapseAllRowsExceptOne() -> CITreeViewNode? { 130 | indexPathsArray = [IndexPath]() 131 | var collapsedTreeViewNode:CITreeViewNode? = nil 132 | var indexPath = IndexPath(row: 0, section: 0) 133 | for treeViewNode in treeViewNodes { 134 | if treeViewNode.expand , treeViewNode.level == 0 { 135 | collapseRows(for: treeViewNode, atIndexPath: indexPath) 136 | collapsedTreeViewNode = treeViewNode 137 | } 138 | indexPath.row += 1 139 | } 140 | return collapsedTreeViewNode 141 | } 142 | @discardableResult 143 | func expandRows(atIndexPath indexPath: IndexPath, with selectedTreeViewNode: CITreeViewNode, openWithChildrens: Bool) -> Int { 144 | guard let treeViewControllerDelegate = self.treeViewControllerDelegate else { return 0 } 145 | let treeViewNodeChildren = treeViewControllerDelegate.getChildren(for: selectedTreeViewNode.item, at: indexPath) 146 | indexPathsArray = [IndexPath]() 147 | var row = indexPath.row + 1 148 | setExpandTreeViewNode(atIndex: indexPath.row) 149 | 150 | if treeViewNodeChildren.count > 0 { 151 | treeViewControllerDelegate.willExpandTreeViewNode(selectedTreeViewNode, at: indexPath) 152 | for item in treeViewNodeChildren { 153 | addIndexPath(withRow: row) 154 | insertTreeViewNode(parent: selectedTreeViewNode, with: item, to: row) 155 | setLevelTreeViewNode(atIndex: row, to: selectedTreeViewNode.level) 156 | if openWithChildrens { 157 | let treeViewNode = getTreeViewNode(atIndex: row) 158 | let indexPath = IndexPath(row: row, section: 0) 159 | row = expandRows(atIndexPath: indexPath, with: treeViewNode, openWithChildrens: openWithChildrens) 160 | } else { 161 | row += 1 162 | } 163 | } 164 | } 165 | return row 166 | } 167 | 168 | func collapseAllRows(){ 169 | indexPathsArray = [IndexPath]() 170 | var indexPath = IndexPath(row: 0, section: 0) 171 | for treeViewNode in treeViewNodes { 172 | indexPath = getIndexPathOfTreeViewNode(treeViewNode: treeViewNode) 173 | if treeViewNode.level != 0 { 174 | setCollapseTreeViewNode(atIndex: indexPath.row) 175 | treeViewNodes.remove(at: indexPath.row) 176 | } else { 177 | setCollapseTreeViewNode(atIndex: indexPath.row) 178 | } 179 | } 180 | } 181 | 182 | func expandAllRows() { 183 | indexPathsArray = [IndexPath]() 184 | var indexPath = IndexPath(row: 0, section: 0) 185 | for treeViewNode in treeViewNodes { 186 | if !treeViewNode.expand { 187 | indexPath = getIndexPathOfTreeViewNode(treeViewNode: treeViewNode) 188 | indexPath.row = expandRows(atIndexPath: indexPath, with: treeViewNode, openWithChildrens: true) 189 | 190 | } 191 | } 192 | } 193 | 194 | func getIndexPathOfTreeViewNode(treeViewNode: CITreeViewNode) -> IndexPath { 195 | for (index,node) in treeViewNodes.enumerated() { 196 | if treeViewNode == node { 197 | return IndexPath(row: index, section: 0) 198 | } 199 | } 200 | return IndexPath(row:0, section:0) 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/CITreeViewClasses/CITreeViewNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewNode.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class CITreeViewNode: NSObject { 12 | public var parentNode: CITreeViewNode? 13 | public var expand: Bool = false 14 | public var level: Int = 0 15 | public var item: Any 16 | 17 | init(item: Any) { 18 | self.item = item 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Controllers/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | var data : [CITreeViewData] = [] 14 | //var treeView:CITreeView! 15 | 16 | let treeViewCellIdentifier = "TreeViewCellIdentifier" 17 | let treeViewCellNibName = "CITreeViewCell" 18 | 19 | @IBOutlet weak var sampleTreeView: CITreeView! 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | data = CITreeViewData.getDefaultCITreeViewData() 23 | sampleTreeView.collapseNoneSelectedRows = false 24 | sampleTreeView.register(UINib(nibName: treeViewCellNibName, bundle: nil), forCellReuseIdentifier: treeViewCellIdentifier) 25 | } 26 | 27 | @IBAction func reloadBarButtonAction(_ sender: UIBarButtonItem) { 28 | sampleTreeView.expandAllRows() 29 | } 30 | @IBAction func collapseAllRowsBarButtonAction(_ sender: UIBarButtonItem) { 31 | sampleTreeView.collapseAllRows() 32 | 33 | } 34 | } 35 | 36 | extension ViewController : CITreeViewDelegate { 37 | func willExpandTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 38 | 39 | func didExpandTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 40 | 41 | func willCollapseTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 42 | 43 | func didCollapseTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 44 | 45 | 46 | func treeView(_ treeView: CITreeView, heightForRowAt indexPath: IndexPath, withTreeViewNode treeViewNode: CITreeViewNode) -> CGFloat { 47 | return 60 48 | } 49 | 50 | func treeView(_ treeView: CITreeView, didDeselectRowAt treeViewNode: CITreeViewNode, atIndexPath indexPath: IndexPath) { 51 | 52 | } 53 | 54 | func treeView(_ treeView: CITreeView, didSelectRowAt treeViewNode: CITreeViewNode, atIndexPath indexPath: IndexPath) { 55 | if let parentNode = treeViewNode.parentNode{ 56 | print(parentNode.item) 57 | } 58 | } 59 | } 60 | 61 | extension ViewController : CITreeViewDataSource { 62 | func treeViewSelectedNodeChildren(for treeViewNodeItem: AnyObject) -> [AnyObject] { 63 | if let dataObj = treeViewNodeItem as? CITreeViewData { 64 | return dataObj.children 65 | } 66 | return [] 67 | } 68 | 69 | func treeViewDataArray() -> [AnyObject] { 70 | return data 71 | } 72 | 73 | func treeView(_ treeView: CITreeView, atIndexPath indexPath: IndexPath, withTreeViewNode treeViewNode: CITreeViewNode) -> UITableViewCell { 74 | let cell = treeView.dequeueReusableCell(withIdentifier: treeViewCellIdentifier) as! CITreeViewCell 75 | let dataObj = treeViewNode.item as! CITreeViewData 76 | cell.nameLabel.text = dataObj.name 77 | cell.setupCell(level: treeViewNode.level) 78 | 79 | return cell; 80 | } 81 | 82 | } 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.1.0 19 | CFBundleVersion 20 | 2 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Models/CITreeViewData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewData.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CITreeViewData { 12 | 13 | let name : String 14 | var children : [CITreeViewData] 15 | 16 | init(name : String, children: [CITreeViewData]) { 17 | self.name = name 18 | self.children = children 19 | } 20 | 21 | convenience init(name : String) { 22 | self.init(name: name, children: [CITreeViewData]()) 23 | } 24 | 25 | func addChild(_ child : CITreeViewData) { 26 | self.children.append(child) 27 | } 28 | 29 | func removeChild(_ child : CITreeViewData) { 30 | self.children = self.children.filter( {$0 !== child}) 31 | } 32 | } 33 | 34 | extension CITreeViewData { 35 | 36 | static func getDefaultCITreeViewData() -> [CITreeViewData] { 37 | 38 | let subChild121 = CITreeViewData(name: "Albea") 39 | let subChild122 = CITreeViewData(name: "Egea") 40 | let subChild123 = CITreeViewData(name: "Linea") 41 | let subChild124 = CITreeViewData(name: "Siena") 42 | 43 | let child11 = CITreeViewData(name: "Volvo") 44 | let child12 = CITreeViewData(name: "Fiat", children:[subChild121, subChild122, subChild123, subChild124]) 45 | let child13 = CITreeViewData(name: "Alfa Romeo") 46 | let child14 = CITreeViewData(name: "Mercedes") 47 | let parent1 = CITreeViewData(name: "Sedan", children: [child11, child12, child13, child14]) 48 | 49 | let subChild221 = CITreeViewData(name: "Discovery") 50 | let subChild222 = CITreeViewData(name: "Evoque") 51 | let subChild223 = CITreeViewData(name: "Defender") 52 | let subChild224 = CITreeViewData(name: "Freelander") 53 | 54 | let child21 = CITreeViewData(name: "GMC") 55 | let child22 = CITreeViewData(name: "Land Rover" , children: [subChild221,subChild222,subChild223,subChild224]) 56 | let parent2 = CITreeViewData(name: "SUV", children: [child21, child22]) 57 | 58 | 59 | let child31 = CITreeViewData(name: "Wolkswagen") 60 | let child32 = CITreeViewData(name: "Toyota") 61 | let child33 = CITreeViewData(name: "Dodge") 62 | let parent3 = CITreeViewData(name: "Truck", children: [child31, child32,child33]) 63 | 64 | let subChildChild5321 = CITreeViewData(name: "Carrera", children: [child31, child32,child33]) 65 | let subChildChild5322 = CITreeViewData(name: "Carrera 4 GTS") 66 | let subChildChild5323 = CITreeViewData(name: "Targa 4") 67 | let subChildChild5324 = CITreeViewData(name: "Turbo S") 68 | 69 | let parent4 = CITreeViewData(name: "Van",children:[subChildChild5321,subChildChild5322,subChildChild5323,subChildChild5324]) 70 | 71 | 72 | 73 | let subChild531 = CITreeViewData(name: "Cayman") 74 | let subChild532 = CITreeViewData(name: "911",children:[subChildChild5321,subChildChild5322,subChildChild5323,subChildChild5324]) 75 | 76 | let child51 = CITreeViewData(name: "Renault") 77 | let child52 = CITreeViewData(name: "Ferrari") 78 | let child53 = CITreeViewData(name: "Porshe", children: [subChild531, subChild532]) 79 | let child54 = CITreeViewData(name: "Maserati") 80 | let child55 = CITreeViewData(name: "Bugatti") 81 | let parent5 = CITreeViewData(name: "Sports Car",children:[child51,child52,child53,child54,child55]) 82 | 83 | 84 | return [parent5,parent2,parent1,parent3,parent4] 85 | } 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Views/CITreeViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewCell.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CITreeViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var leadingConstraint: NSLayoutConstraint! 14 | 15 | @IBOutlet weak var nameLabel: UILabel! 16 | @IBOutlet weak var avatarImageView: UIImageView! 17 | 18 | let leadingValueForChildrenCell:CGFloat = 30 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | } 22 | 23 | func setupCell(level:Int) 24 | { 25 | self.leadingConstraint.constant = leadingValueForChildrenCell * CGFloat(level + 1) 26 | self.avatarImageView.layer.cornerRadius = self.avatarImageView.frame.size.height / 2 27 | switch level { 28 | case 0: 29 | self.avatarImageView.backgroundColor = UIColor.orange 30 | case 1: 31 | self.avatarImageView.backgroundColor = UIColor.green 32 | case 2: 33 | self.avatarImageView.backgroundColor = UIColor.blue 34 | default: 35 | self.avatarImageView.backgroundColor = UIColor.black 36 | } 37 | 38 | self.layoutIfNeeded() 39 | } 40 | 41 | func getRandomColor() -> UIColor{ 42 | 43 | let red:CGFloat = CGFloat(drand48()) 44 | let green:CGFloat = CGFloat(drand48()) 45 | let blue:CGFloat = CGFloat(drand48()) 46 | 47 | return UIColor(red:red, green: green, blue: blue, alpha: 1.0) 48 | } 49 | 50 | override func setSelected(_ selected: Bool, animated: Bool) { 51 | super.setSelected(selected, animated: animated) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /CITreeView/CITreeView/Views/CITreeViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /CITreeViewClasses/CITreeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeView.swift 3 | // TreeViewExample 4 | // 5 | // Created by Cenk IŞIK on 11.01.2018. 6 | // Copyright © 2018 Cenk IŞIK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc 12 | public protocol CITreeViewDataSource : NSObjectProtocol{ 13 | func treeView(_ treeView:CITreeView, atIndexPath indexPath:IndexPath, withTreeViewNode treeViewNode:CITreeViewNode) -> UITableViewCell 14 | func treeViewSelectedNodeChildren(for treeViewNodeItem:AnyObject) -> [AnyObject] 15 | func treeViewDataArray() -> [AnyObject] 16 | } 17 | 18 | @objc 19 | public protocol CITreeViewDelegate : NSObjectProtocol{ 20 | 21 | func treeView(_ treeView: CITreeView, heightForRowAt indexPath: IndexPath, withTreeViewNode treeViewNode:CITreeViewNode) -> CGFloat 22 | func treeView(_ treeView: CITreeView, didSelectRowAt treeViewNode:CITreeViewNode, atIndexPath indexPath:IndexPath) 23 | func treeView(_ treeView: CITreeView, didDeselectRowAt treeViewNode:CITreeViewNode, atIndexPath indexPath: IndexPath) 24 | func willExpandTreeViewNode(treeViewNode:CITreeViewNode, atIndexPath: IndexPath) 25 | func didExpandTreeViewNode(treeViewNode:CITreeViewNode, atIndexPath: IndexPath) 26 | func willCollapseTreeViewNode(treeViewNode:CITreeViewNode, atIndexPath: IndexPath) 27 | func didCollapseTreeViewNode(treeViewNode:CITreeViewNode, atIndexPath: IndexPath) 28 | 29 | } 30 | 31 | public class CITreeView: UITableView { 32 | 33 | @IBOutlet open weak var treeViewDataSource:CITreeViewDataSource? 34 | @IBOutlet open weak var treeViewDelegate: CITreeViewDelegate? 35 | fileprivate var treeViewController = CITreeViewController(treeViewNodes: []) 36 | fileprivate var selectedTreeViewNode:CITreeViewNode? 37 | public var collapseNoneSelectedRows = false 38 | fileprivate var mainDataArray:[CITreeViewNode] = [] 39 | 40 | 41 | override public init(frame: CGRect, style: UITableView.Style) { 42 | super.init(frame: frame, style: style) 43 | commonInit() 44 | } 45 | 46 | required public init?(coder aDecoder: NSCoder) { 47 | super.init(coder: aDecoder) 48 | commonInit() 49 | } 50 | 51 | func commonInit(){ 52 | super.delegate = self 53 | super.dataSource = self 54 | treeViewController.treeViewControllerDelegate = self as CITreeViewControllerDelegate 55 | self.backgroundColor = UIColor.clear 56 | } 57 | 58 | override public func reloadData() { 59 | 60 | guard let treeViewDataSource = self.treeViewDataSource else { 61 | mainDataArray = [CITreeViewNode]() 62 | return 63 | } 64 | 65 | mainDataArray = [CITreeViewNode]() 66 | treeViewController.treeViewNodes.removeAll() 67 | for item in treeViewDataSource.treeViewDataArray() { 68 | treeViewController.addTreeViewNode(with: item) 69 | } 70 | mainDataArray = treeViewController.treeViewNodes 71 | 72 | super.reloadData() 73 | } 74 | 75 | public func reloadDataWithoutChangingRowStates() { 76 | 77 | guard let treeViewDataSource = self.treeViewDataSource else { 78 | mainDataArray = [CITreeViewNode]() 79 | return 80 | } 81 | 82 | if (treeViewDataSource.treeViewDataArray()).count > treeViewController.treeViewNodes.count { 83 | mainDataArray = [CITreeViewNode]() 84 | treeViewController.treeViewNodes.removeAll() 85 | for item in treeViewDataSource.treeViewDataArray() { 86 | treeViewController.addTreeViewNode(with: item) 87 | } 88 | mainDataArray = treeViewController.treeViewNodes 89 | } 90 | super.reloadData() 91 | } 92 | 93 | fileprivate func deleteRows() { 94 | if treeViewController.indexPathsArray.count > 0 { 95 | self.beginUpdates() 96 | self.deleteRows(at: treeViewController.indexPathsArray, with: .automatic) 97 | self.endUpdates() 98 | } 99 | } 100 | 101 | public func deleteRow(at indexPath:IndexPath) { 102 | self.beginUpdates() 103 | self.deleteRows(at: [indexPath], with: .automatic) 104 | self.endUpdates() 105 | } 106 | 107 | fileprivate func insertRows() { 108 | if treeViewController.indexPathsArray.count > 0 { 109 | self.beginUpdates() 110 | self.insertRows(at: treeViewController.indexPathsArray, with: .automatic) 111 | self.endUpdates() 112 | } 113 | } 114 | 115 | fileprivate func collapseRows(for treeViewNode: CITreeViewNode, atIndexPath indexPath: IndexPath ,completion: @escaping () -> Void) { 116 | guard let treeViewDelegate = self.treeViewDelegate else { return } 117 | if #available(iOS 11.0, *) { 118 | self.performBatchUpdates({ 119 | deleteRows() 120 | }, completion: { (complete) in 121 | treeViewDelegate.didCollapseTreeViewNode(treeViewNode: treeViewNode, atIndexPath:indexPath) 122 | completion() 123 | }) 124 | } else { 125 | CATransaction.begin() 126 | CATransaction.setCompletionBlock({ 127 | treeViewDelegate.didCollapseTreeViewNode(treeViewNode: treeViewNode, atIndexPath: indexPath) 128 | completion() 129 | }) 130 | deleteRows() 131 | CATransaction.commit() 132 | } 133 | } 134 | 135 | fileprivate func expandRows(for treeViewNode: CITreeViewNode, withSelected indexPath: IndexPath) { 136 | guard let treeViewDelegate = self.treeViewDelegate else {return} 137 | if #available(iOS 11.0, *) { 138 | self.performBatchUpdates({ 139 | insertRows() 140 | }, completion: { (complete) in 141 | treeViewDelegate.didExpandTreeViewNode(treeViewNode: treeViewNode, atIndexPath: indexPath) 142 | }) 143 | } else { 144 | CATransaction.begin() 145 | CATransaction.setCompletionBlock({ 146 | treeViewDelegate.didExpandTreeViewNode(treeViewNode: treeViewNode, atIndexPath: indexPath) 147 | }) 148 | insertRows() 149 | CATransaction.commit() 150 | } 151 | } 152 | 153 | func getAllCells() -> [UITableViewCell] { 154 | var cells = [UITableViewCell]() 155 | for section in 0 ..< self.numberOfSections{ 156 | for row in 0 ..< self.numberOfRows(inSection: section){ 157 | cells.append(self.cellForRow(at: IndexPath(row: row, section: section))!) 158 | } 159 | } 160 | return cells 161 | } 162 | public func expandAllRows() { 163 | treeViewController.expandAllRows() 164 | reloadDataWithoutChangingRowStates() 165 | 166 | } 167 | 168 | public func collapseAllRows() { 169 | treeViewController.collapseAllRows() 170 | reloadDataWithoutChangingRowStates() 171 | } 172 | } 173 | 174 | extension CITreeView : UITableViewDelegate { 175 | 176 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 177 | let treeViewNode = treeViewController.getTreeViewNode(atIndex: indexPath.row) 178 | return (self.treeViewDelegate?.treeView(tableView as! CITreeView,heightForRowAt: indexPath,withTreeViewNode :treeViewNode))! 179 | } 180 | 181 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 182 | selectedTreeViewNode = treeViewController.getTreeViewNode(atIndex: indexPath.row) 183 | guard let treeViewDelegate = self.treeViewDelegate else { return } 184 | 185 | if let justSelectedTreeViewNode = selectedTreeViewNode { 186 | treeViewDelegate.treeView(tableView as! CITreeView, didSelectRowAt: justSelectedTreeViewNode, atIndexPath: indexPath) 187 | var willExpandIndexPath = indexPath 188 | if justSelectedTreeViewNode.expand { 189 | treeViewController.collapseRows(for: justSelectedTreeViewNode, atIndexPath: indexPath) 190 | collapseRows(for: justSelectedTreeViewNode, atIndexPath: indexPath){} 191 | } 192 | else 193 | { 194 | if collapseNoneSelectedRows, 195 | selectedTreeViewNode?.level == 0, 196 | let collapsedTreeViewNode = treeViewController.collapseAllRowsExceptOne(), 197 | treeViewController.indexPathsArray.count > 0 { 198 | 199 | collapseRows(for: collapsedTreeViewNode, atIndexPath: indexPath){ 200 | for (index, treeViewNode) in self.mainDataArray.enumerated() { 201 | if treeViewNode == justSelectedTreeViewNode { 202 | willExpandIndexPath.row = index 203 | } 204 | } 205 | self.treeViewController.expandRows(atIndexPath: willExpandIndexPath, with: justSelectedTreeViewNode, openWithChildrens: false) 206 | self.expandRows(for: justSelectedTreeViewNode, withSelected: indexPath) 207 | } 208 | 209 | }else{ 210 | treeViewController.expandRows(atIndexPath: willExpandIndexPath, with: justSelectedTreeViewNode, openWithChildrens: false) 211 | expandRows(for: justSelectedTreeViewNode, withSelected: indexPath) 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | extension CITreeView : UITableViewDataSource { 219 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 220 | return treeViewController.treeViewNodes.count 221 | } 222 | 223 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 224 | let treeViewNode = treeViewController.getTreeViewNode(atIndex: indexPath.row) 225 | return (self.treeViewDataSource?.treeView(tableView as! CITreeView, atIndexPath: indexPath, withTreeViewNode: treeViewNode))! 226 | } 227 | } 228 | 229 | extension CITreeView : CITreeViewControllerDelegate { 230 | public func getChildren(forTreeViewNodeItem item: AnyObject, with indexPath: IndexPath) -> [AnyObject] { 231 | return (self.treeViewDataSource?.treeViewSelectedNodeChildren(for: item))! 232 | } 233 | 234 | public func willCollapseTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) { 235 | self.treeViewDelegate?.willCollapseTreeViewNode(treeViewNode: treeViewNode, atIndexPath: atIndexPath) 236 | } 237 | 238 | public func willExpandTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) { 239 | self.treeViewDelegate?.willExpandTreeViewNode(treeViewNode: treeViewNode, atIndexPath: atIndexPath) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /CITreeViewClasses/CITreeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewController.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol CITreeViewControllerDelegate : NSObjectProtocol { 12 | func getChildren(forTreeViewNodeItem item:AnyObject, with indexPath:IndexPath) -> [AnyObject] 13 | func willExpandTreeViewNode(treeViewNode:CITreeViewNode, atIndexPath: IndexPath) 14 | func willCollapseTreeViewNode(treeViewNode:CITreeViewNode, atIndexPath: IndexPath) 15 | } 16 | 17 | public class CITreeViewController:NSObject { 18 | 19 | var treeViewNodes:[CITreeViewNode] = [] 20 | var indexPathsArray:[IndexPath] = [] 21 | weak var treeViewControllerDelegate:CITreeViewControllerDelegate? 22 | 23 | init(treeViewNodes : [CITreeViewNode]) { 24 | self.treeViewNodes = treeViewNodes 25 | } 26 | 27 | //MARK: Tree View Nodes Functions 28 | func addTreeViewNode(with item:AnyObject){ 29 | let treeViewNode = CITreeViewNode(item: item) 30 | treeViewNodes.append(treeViewNode) 31 | } 32 | 33 | func getTreeViewNode(atIndex index: Int) -> CITreeViewNode 34 | { 35 | if treeViewNodes.indices.contains(index){ 36 | return treeViewNodes[index] 37 | } 38 | return treeViewNodes.last! 39 | } 40 | 41 | func index(of treeViewNode: CITreeViewNode) -> Int? { 42 | return treeViewNodes.firstIndex(of: treeViewNode) 43 | } 44 | 45 | func insertTreeViewNode(parent parentTreeViewNode:CITreeViewNode, with item:AnyObject, to index : Int) 46 | { 47 | let treeViewNode = CITreeViewNode(item: item) 48 | treeViewNode.parentNode = parentTreeViewNode 49 | treeViewNodes.insert(treeViewNode, at: index) 50 | } 51 | 52 | func removeTreeViewNodesAtRange(from start:Int , to end:Int) 53 | { 54 | treeViewNodes.removeSubrange(start ... end) 55 | } 56 | 57 | func setExpandTreeViewNode(atIndex index:Int){ 58 | treeViewNodes[index].expand = true 59 | } 60 | 61 | func setCollapseTreeViewNode(atIndex index:Int){ 62 | treeViewNodes[index].expand = false 63 | } 64 | 65 | func setLevelTreeViewNode(atIndex index:Int, to level:Int){ 66 | treeViewNodes[index].level = level + 1 67 | } 68 | 69 | // MARK: Expand Rows 70 | 71 | func addIndexPath(withRow row:Int){ 72 | let indexPath = IndexPath(row: row , section: 0) 73 | indexPathsArray.append(indexPath) 74 | } 75 | 76 | func expandRows(atIndexPath indexPath:IndexPath, with selectedTreeViewNode:CITreeViewNode){ 77 | let children = self.treeViewControllerDelegate?.getChildren(forTreeViewNodeItem: selectedTreeViewNode.item, with: indexPath) 78 | indexPathsArray = [IndexPath]() 79 | var row = indexPath.row + 1 80 | 81 | if (children?.count)! > 0 { 82 | self.treeViewControllerDelegate?.willExpandTreeViewNode(treeViewNode: selectedTreeViewNode, atIndexPath: indexPath) 83 | setExpandTreeViewNode(atIndex: indexPath.row) 84 | } 85 | 86 | for item in children!{ 87 | addIndexPath(withRow: row) 88 | insertTreeViewNode(parent: selectedTreeViewNode, with: item, to: row) 89 | setLevelTreeViewNode(atIndex: row, to: selectedTreeViewNode.level) 90 | row += 1 91 | } 92 | } 93 | 94 | // MARK: Collapse Rows 95 | func removeIndexPath(withRow row:inout Int, and indexPath:IndexPath){ 96 | let treeViewNode = getTreeViewNode(atIndex: row) 97 | let children = self.treeViewControllerDelegate?.getChildren(forTreeViewNodeItem: treeViewNode.item, with: indexPath) 98 | 99 | let index = IndexPath(row: row , section: indexPath.section) 100 | indexPathsArray.append(index) 101 | row += 1 102 | 103 | if (treeViewNode.expand) { 104 | for _ in children!{ 105 | removeIndexPath(withRow: &row, and: indexPath) 106 | } 107 | } 108 | } 109 | 110 | func collapseRows(for treeViewNode :CITreeViewNode, atIndexPath indexPath:IndexPath){ 111 | guard let treeViewControllerDelegate = self.treeViewControllerDelegate else {return} 112 | let treeViewNodeChildren = treeViewControllerDelegate.getChildren(forTreeViewNodeItem: treeViewNode.item, with: indexPath) 113 | indexPathsArray = [IndexPath]() 114 | var row = indexPath.row + 1 115 | 116 | if treeViewNodeChildren.count > 0 { 117 | treeViewControllerDelegate.willCollapseTreeViewNode(treeViewNode: treeViewNode, atIndexPath: indexPath) 118 | } 119 | 120 | setCollapseTreeViewNode(atIndex: indexPath.row) 121 | 122 | for _ in treeViewNodeChildren{ 123 | removeIndexPath(withRow: &row, and: indexPath) 124 | } 125 | if indexPathsArray.count > 0 { 126 | removeTreeViewNodesAtRange(from: (indexPathsArray.first?.row)!, to: (indexPathsArray.last?.row)!) 127 | } 128 | 129 | } 130 | 131 | 132 | @discardableResult func collapseAllRowsExceptOne() -> CITreeViewNode?{ 133 | indexPathsArray = [IndexPath]() 134 | var collapsedTreeViewNode:CITreeViewNode? = nil 135 | var indexPath = IndexPath(row: 0, section: 0) 136 | for treeViewNode in treeViewNodes { 137 | if treeViewNode.expand , treeViewNode.level == 0 { 138 | collapseRows(for: treeViewNode, atIndexPath: indexPath) 139 | collapsedTreeViewNode = treeViewNode 140 | } 141 | indexPath.row += 1 142 | } 143 | return collapsedTreeViewNode 144 | } 145 | @discardableResult 146 | func expandRows(atIndexPath indexPath:IndexPath, with selectedTreeViewNode:CITreeViewNode, openWithChildrens:Bool) -> Int{ 147 | guard let treeViewControllerDelegate = self.treeViewControllerDelegate else {return 0} 148 | let treeViewNodeChildren = treeViewControllerDelegate.getChildren(forTreeViewNodeItem: selectedTreeViewNode.item, with: indexPath) 149 | indexPathsArray = [IndexPath]() 150 | var row = indexPath.row + 1 151 | setExpandTreeViewNode(atIndex: indexPath.row) 152 | 153 | if treeViewNodeChildren.count > 0 { 154 | treeViewControllerDelegate.willExpandTreeViewNode(treeViewNode: selectedTreeViewNode, atIndexPath: indexPath) 155 | for item in treeViewNodeChildren{ 156 | addIndexPath(withRow: row) 157 | insertTreeViewNode(parent: selectedTreeViewNode, with: item, to: row) 158 | setLevelTreeViewNode(atIndex: row, to: selectedTreeViewNode.level) 159 | if openWithChildrens { 160 | let treeViewNode = getTreeViewNode(atIndex: row) 161 | let indexPath = IndexPath(row: row, section: 0) 162 | row = expandRows(atIndexPath: indexPath, with: treeViewNode, openWithChildrens: openWithChildrens) 163 | }else { 164 | row += 1 165 | } 166 | 167 | } 168 | } 169 | return row 170 | } 171 | 172 | func collapseAllRows(){ 173 | indexPathsArray = [IndexPath]() 174 | var indexPath = IndexPath(row: 0, section: 0) 175 | for treeViewNode in treeViewNodes { 176 | indexPath = getIndexPathOfTreeViewNode(treeViewNode: treeViewNode) 177 | if treeViewNode.level != 0 { 178 | setCollapseTreeViewNode(atIndex: indexPath.row) 179 | treeViewNodes.remove(at: indexPath.row) 180 | }else{ 181 | setCollapseTreeViewNode(atIndex: indexPath.row) 182 | } 183 | } 184 | } 185 | 186 | func expandAllRows() { 187 | indexPathsArray = [IndexPath]() 188 | var indexPath = IndexPath(row: 0, section: 0) 189 | for treeViewNode in treeViewNodes { 190 | if !treeViewNode.expand { 191 | indexPath = getIndexPathOfTreeViewNode(treeViewNode: treeViewNode) 192 | indexPath.row = expandRows(atIndexPath: indexPath, with: treeViewNode, openWithChildrens: true) 193 | 194 | } 195 | } 196 | } 197 | 198 | func getIndexPathOfTreeViewNode(treeViewNode:CITreeViewNode) -> IndexPath { 199 | for (index,node) in treeViewNodes.enumerated() { 200 | if treeViewNode == node { 201 | return IndexPath(row: index, section: 0) 202 | } 203 | } 204 | return IndexPath(row:0, section:0) 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /CITreeViewClasses/CITreeViewNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CITreeViewNode.swift 3 | // CITreeView 4 | // 5 | // Created by Apple on 24.01.2018. 6 | // Copyright © 2018 Cenk Işık. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class CITreeViewNode: NSObject { 12 | public var parentNode:CITreeViewNode? 13 | public var expand:Bool = false 14 | public var level:Int = 0 15 | public var item:AnyObject 16 | 17 | init(item:AnyObject) { 18 | self.item = item 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CITreeView_01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cenksk/CITreeView/ac16e579c97dfc777b42ab753f64f0db53293e41/CITreeView_01.gif -------------------------------------------------------------------------------- /CITreeView_02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cenksk/CITreeView/ac16e579c97dfc777b42ab753f64f0db53293e41/CITreeView_02.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cenk Işık 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "CITreeView", 7 | products: [ 8 | .library(name: "CITreeView", targets: ["CITreeView"]) 9 | ], 10 | targets: [ 11 | .target(name: "CITreeView", path: "CITreeViewClasses") 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CITreeView 3 | 4 | CITreeView created to implement and maintain that wanted TreeView structures for IOS platforms easy way. CITreeView provides endless treeview structure. You just add your data to CITreeview datasource. 5 | 6 | [![Twitter: @cekjacks](https://img.shields.io/badge/contact-%40cekjacks-blue.svg)](https://twitter.com/cekjacks) 7 | [![CocoaPods](https://img.shields.io/badge/pod-v1.6.1-blue.svg)](https://github.com/cenksk/CITreeView) 8 | [![Platform](https://img.shields.io/badge/platform-ios-lightgrey.svg)](http://cocoadocs.org/docsets/CITreeView) 9 | 10 | 11 | 12 | 13 | ![](https://github.com/cenksk/CITreeView/blob/master/CITreeView_01.gif) | ![](https://github.com/cenksk/CITreeView/blob/master/CITreeView_02.gif) 14 | 15 | 16 | ## Installation 17 | 18 | ### CocoaPods (Recommended) 19 | 20 | 1. Add additional entry to your Podfile. 21 | 22 | ``` 23 | pod 'CITreeView', '~> 1.6.1' 24 | ``` 25 | 26 | 2. Pod Install 27 | 28 | ``` 29 | pod install 30 | ``` 31 | 32 | ### Manual 33 | 34 | 1. Download .zip file 35 | 2. Just drag and drop CITreeViewClasses folder to your project 36 | 37 | ## Usage 38 | 39 | ### Initialization 40 | 1. Firstly, import CITreeView 41 | 42 | ``` 43 | import CITreeView 44 | ``` 45 | 46 | 2. Add CITreeViewDelegate and CITreeViewDataSource to your view controller 47 | 48 | ``` 49 | class ViewController:UIViewController,CITreeViewDelegate,CITreeViewDataSource 50 | ``` 51 | 52 | 3. Initialize and configure CITreeView 53 | 54 | ``` 55 | let ciTreeView = CITreeView.init(frame: self.view.bounds, style: UITableViewStyle.plain) 56 | ciTreeView.treeViewDelegate = self 57 | ciTreeView.treeViewDataSource = self 58 | self.view.addSubview(ciTreeView) 59 | ``` 60 | 61 | 3. Implement required methods of the CITreeView's delegates 62 | 63 | ``` 64 | func treeView(_ treeView: CITreeView, atIndexPath indexPath: IndexPath, withTreeViewNode treeViewNode: CITreeViewNode) -> UITableViewCell { 65 | 66 | return cell; 67 | } 68 | ``` 69 | 70 | ``` 71 | func treeViewSelectedNodeChildren(for treeViewNodeItem: AnyObject) -> [AnyObject] { 72 | if let dataObj = treeViewNodeItem as? CITreeViewData { 73 | return dataObj.children 74 | } 75 | return [] 76 | } 77 | ``` 78 | ``` 79 | func treeViewDataArray() -> [AnyObject] { 80 | return yourDataArray 81 | } 82 | ``` 83 | ``` 84 | func treeView(_ tableView: CITreeView, heightForRowAt indexPath: IndexPath, withTreeViewNode treeViewNode:CITreeViewNode) -> CGFloat { 85 | return UITableViewAutomaticDimension 86 | } 87 | ``` 88 | 89 | ``` 90 | func treeView(_ treeView: CITreeView, didSelectRowAt treeViewNode:CITreeViewNode) {} 91 | 92 | func willExpandTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 93 | 94 | func didExpandTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 95 | 96 | func willCollapseTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 97 | 98 | func didCollapseTreeViewNode(treeViewNode: CITreeViewNode, atIndexPath: IndexPath) {} 99 | ``` 100 | ### Features 101 | 102 | 1. You can only open one node at a time if you wish. If another parent node is selected while a node is opened, the open nodes will closed automatically. 103 | 104 | ``` 105 | ciTreeView.collapseNoneSelectedRows = true 106 | ``` 107 | 108 | ### Recently Added Features 109 | 110 | ``` 111 | 1. connect CITreeView delegate and datasource with interface builder 112 | 2. expand all rows with expandAllRows() 113 | 3. collapse all rows with collapseAllRows() 114 | 4. get parent node of selected node as CITreeViewNode.parentNode 115 | 5. reload data without lose rows states that expanded or collapses with reloadDataWithoutChangingRowStates() 116 | ``` 117 | 118 | License 119 | ----------------- 120 | 121 | MIT licensed, Copyright (c) 2018 Cenk Işık, [@cekjacks](https://twitter.com/cekjacks) 122 | --------------------------------------------------------------------------------