├── .gitignore ├── AZExpandable.podspec ├── Example ├── ExpandableCell.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── ExpandableCell.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── ExpandableCell │ ├── Supporting Files │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── View Controller │ │ ├── BasicDemoViewController.swift │ │ ├── CellFactory.swift │ │ ├── ComplexDemoViewController.swift │ │ ├── LoadMoreDemoViewController.swift │ │ └── PickerItemsController.swift │ ├── ViewModels │ │ ├── TableViewModel.swift │ │ └── TableViewModelFactory.swift │ └── Views │ │ ├── Base.lproj │ │ └── Demo.storyboard │ │ ├── CenteredLabelCell.swift │ │ └── CenteredLabelCell.xib └── Podfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Source ├── ExpandableCell-Bridging-Header.h ├── ExpandableTable │ ├── Cells │ │ ├── ActivityIndicatorCell.swift │ │ ├── DatePickerCell.swift │ │ ├── PickerCell.swift │ │ └── UIView+Autolayout.swift │ ├── ExpandableCellInfo.swift │ ├── ExpandableTable.swift │ ├── IndexPath+SyntaxSugar.swift │ └── PagingTable.swift └── Proxy │ ├── ExpandableProxy.h │ └── ExpandableProxy.m ├── _config.yml ├── logo.png └── logo_long.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | .build/ 37 | 38 | # Carthage 39 | Carthage/Build 40 | ExpandableCell.xcworkspace/contents.xcworkspacedata 41 | Pods/Manifest.lock 42 | Pods/Target Support Files/AZExpandable/AZExpandable-dummy.m 43 | Pods/Target Support Files/AZExpandable/AZExpandable-prefix.pch 44 | Pods/Target Support Files/AZExpandable/AZExpandable-umbrella.h 45 | Pods/Target Support Files/AZExpandable/AZExpandable.modulemap 46 | Pods/Target Support Files/AZExpandable/AZExpandable.xcconfig 47 | Pods/Target Support Files/AZExpandable/Info.plist 48 | Pods/Target Support Files/Pods-ExpandableCell/Info.plist 49 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-acknowledgements.markdown 50 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-acknowledgements.plist 51 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-dummy.m 52 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-frameworks.sh 53 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-resources.sh 54 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-umbrella.h 55 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell.debug.xcconfig 56 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell.modulemap 57 | Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell.release.xcconfig 58 | -------------------------------------------------------------------------------- /AZExpandable.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'AZExpandable' 4 | s.version = '1.1.0' 5 | s.summary = 'AZExpandable is a lightweight proxy for UITableView to expand cells.' 6 | 7 | s.platform = :ios 8 | s.ios.deployment_target = '9.0' 9 | s.swift_version = '5.0' 10 | 11 | s.homepage = 'https://github.com/azonov/expandableTable.git' 12 | s.license = { :type => 'MIT', :file => 'LICENSE' } 13 | s.author = { 'Andrey Zonov' => 'andryzonov@gmail.com' } 14 | s.source = { :git => 'https://github.com/azonov/expandableTable.git', :tag => s.version.to_s } 15 | 16 | s.source_files = 'Source/**/*' 17 | 18 | end 19 | -------------------------------------------------------------------------------- /Example/ExpandableCell.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 544BBBECEC7CBA6DC12582BA /* Pods_ExpandableCell.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CAEC0521AA1804F5AD4A219 /* Pods_ExpandableCell.framework */; }; 11 | 5759872B1FA39FE200239B5E /* TableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5759872A1FA39FE200239B5E /* TableViewModel.swift */; }; 12 | 579732361F971A43006DB071 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579732351F971A43006DB071 /* AppDelegate.swift */; }; 13 | 579732381F971A43006DB071 /* ComplexDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579732371F971A43006DB071 /* ComplexDemoViewController.swift */; }; 14 | 5797323B1F971A43006DB071 /* Demo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 579732391F971A43006DB071 /* Demo.storyboard */; }; 15 | 5797323D1F971A43006DB071 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5797323C1F971A43006DB071 /* Assets.xcassets */; }; 16 | 579732401F971A43006DB071 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5797323E1F971A43006DB071 /* LaunchScreen.storyboard */; }; 17 | 57FF07FF1FB2CBC2008C0958 /* TableViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FF07FE1FB2CBC2008C0958 /* TableViewModelFactory.swift */; }; 18 | 57FF08071FB2D957008C0958 /* CenteredLabelCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 57FF08061FB2D957008C0958 /* CenteredLabelCell.xib */; }; 19 | 57FF08091FB2D9BB008C0958 /* CenteredLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FF08081FB2D9BB008C0958 /* CenteredLabelCell.swift */; }; 20 | 57FF080D1FB2DB73008C0958 /* CellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FF080C1FB2DB73008C0958 /* CellFactory.swift */; }; 21 | FB3A0DBB1FE82D2A00D5C0A6 /* PickerItemsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB3A0DBA1FE82D2A00D5C0A6 /* PickerItemsController.swift */; }; 22 | FBAA601B219F0E24004CC2FA /* LoadMoreDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBAA601A219F0E24004CC2FA /* LoadMoreDemoViewController.swift */; }; 23 | FBDF2A041FE8FCC600889B28 /* BasicDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBDF2A031FE8FCC600889B28 /* BasicDemoViewController.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 5759872A1FA39FE200239B5E /* TableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewModel.swift; sourceTree = ""; }; 28 | 579732321F971A43006DB071 /* ExpandableCell.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExpandableCell.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 579732351F971A43006DB071 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 30 | 579732371F971A43006DB071 /* ComplexDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplexDemoViewController.swift; sourceTree = ""; }; 31 | 5797323A1F971A43006DB071 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Demo.storyboard; sourceTree = ""; }; 32 | 5797323C1F971A43006DB071 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | 5797323F1F971A43006DB071 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | 579732411F971A43006DB071 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 57FF07FE1FB2CBC2008C0958 /* TableViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewModelFactory.swift; sourceTree = ""; }; 36 | 57FF08061FB2D957008C0958 /* CenteredLabelCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CenteredLabelCell.xib; sourceTree = ""; }; 37 | 57FF08081FB2D9BB008C0958 /* CenteredLabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenteredLabelCell.swift; sourceTree = ""; }; 38 | 57FF080C1FB2DB73008C0958 /* CellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellFactory.swift; sourceTree = ""; }; 39 | 5B9DB0A27CB6D3B14B4FA504 /* Pods-ExpandableCell.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExpandableCell.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell.debug.xcconfig"; sourceTree = ""; }; 40 | 8A984765D95495A0BB3DFE38 /* Pods-ExpandableCell.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExpandableCell.release.xcconfig"; path = "Pods/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell.release.xcconfig"; sourceTree = ""; }; 41 | 9CAEC0521AA1804F5AD4A219 /* Pods_ExpandableCell.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExpandableCell.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | FB3A0DBA1FE82D2A00D5C0A6 /* PickerItemsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerItemsController.swift; sourceTree = ""; }; 43 | FBAA601A219F0E24004CC2FA /* LoadMoreDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreDemoViewController.swift; sourceTree = ""; }; 44 | FBDF2A031FE8FCC600889B28 /* BasicDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicDemoViewController.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 5797322F1F971A43006DB071 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 544BBBECEC7CBA6DC12582BA /* Pods_ExpandableCell.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 0FDE01C8E17946A046C6AC32 /* Frameworks */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 9CAEC0521AA1804F5AD4A219 /* Pods_ExpandableCell.framework */, 63 | ); 64 | name = Frameworks; 65 | sourceTree = ""; 66 | }; 67 | 2CD19F1321C5BD94F03DDF41 /* Pods */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 5B9DB0A27CB6D3B14B4FA504 /* Pods-ExpandableCell.debug.xcconfig */, 71 | 8A984765D95495A0BB3DFE38 /* Pods-ExpandableCell.release.xcconfig */, 72 | ); 73 | name = Pods; 74 | sourceTree = ""; 75 | }; 76 | 579732291F971A43006DB071 = { 77 | isa = PBXGroup; 78 | children = ( 79 | 579732341F971A43006DB071 /* ExpandableCell */, 80 | 579732331F971A43006DB071 /* Products */, 81 | 2CD19F1321C5BD94F03DDF41 /* Pods */, 82 | 0FDE01C8E17946A046C6AC32 /* Frameworks */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 579732331F971A43006DB071 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 579732321F971A43006DB071 /* ExpandableCell.app */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | 579732341F971A43006DB071 /* ExpandableCell */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 57FF08031FB2CEDF008C0958 /* ViewModels */, 98 | 57FF08021FB2CED3008C0958 /* Views */, 99 | 57FF08011FB2CEB8008C0958 /* View Controller */, 100 | 57FF08001FB2CE51008C0958 /* Supporting Files */, 101 | ); 102 | path = ExpandableCell; 103 | sourceTree = ""; 104 | }; 105 | 57FF08001FB2CE51008C0958 /* Supporting Files */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 5797323C1F971A43006DB071 /* Assets.xcassets */, 109 | 5797323E1F971A43006DB071 /* LaunchScreen.storyboard */, 110 | 579732411F971A43006DB071 /* Info.plist */, 111 | 579732351F971A43006DB071 /* AppDelegate.swift */, 112 | ); 113 | path = "Supporting Files"; 114 | sourceTree = ""; 115 | }; 116 | 57FF08011FB2CEB8008C0958 /* View Controller */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | FBDF2A031FE8FCC600889B28 /* BasicDemoViewController.swift */, 120 | 579732371F971A43006DB071 /* ComplexDemoViewController.swift */, 121 | FBAA601A219F0E24004CC2FA /* LoadMoreDemoViewController.swift */, 122 | FB3A0DBA1FE82D2A00D5C0A6 /* PickerItemsController.swift */, 123 | 57FF080C1FB2DB73008C0958 /* CellFactory.swift */, 124 | ); 125 | path = "View Controller"; 126 | sourceTree = ""; 127 | }; 128 | 57FF08021FB2CED3008C0958 /* Views */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 579732391F971A43006DB071 /* Demo.storyboard */, 132 | 57FF08081FB2D9BB008C0958 /* CenteredLabelCell.swift */, 133 | 57FF08061FB2D957008C0958 /* CenteredLabelCell.xib */, 134 | ); 135 | path = Views; 136 | sourceTree = ""; 137 | }; 138 | 57FF08031FB2CEDF008C0958 /* ViewModels */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 5759872A1FA39FE200239B5E /* TableViewModel.swift */, 142 | 57FF07FE1FB2CBC2008C0958 /* TableViewModelFactory.swift */, 143 | ); 144 | path = ViewModels; 145 | sourceTree = ""; 146 | }; 147 | /* End PBXGroup section */ 148 | 149 | /* Begin PBXNativeTarget section */ 150 | 579732311F971A43006DB071 /* ExpandableCell */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = 579732441F971A43006DB071 /* Build configuration list for PBXNativeTarget "ExpandableCell" */; 153 | buildPhases = ( 154 | 3B9DABDA8FCD5AA9B2C3ADF1 /* [CP] Check Pods Manifest.lock */, 155 | 5797322E1F971A43006DB071 /* Sources */, 156 | 5797322F1F971A43006DB071 /* Frameworks */, 157 | 579732301F971A43006DB071 /* Resources */, 158 | C68FC4EE3E073A697ABC5066 /* [CP] Embed Pods Frameworks */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = ExpandableCell; 165 | productName = ExpandableCell; 166 | productReference = 579732321F971A43006DB071 /* ExpandableCell.app */; 167 | productType = "com.apple.product-type.application"; 168 | }; 169 | /* End PBXNativeTarget section */ 170 | 171 | /* Begin PBXProject section */ 172 | 5797322A1F971A43006DB071 /* Project object */ = { 173 | isa = PBXProject; 174 | attributes = { 175 | LastSwiftUpdateCheck = 0900; 176 | LastUpgradeCheck = 1010; 177 | ORGANIZATIONNAME = "Andrey Zonov"; 178 | TargetAttributes = { 179 | 579732311F971A43006DB071 = { 180 | CreatedOnToolsVersion = 9.0.1; 181 | LastSwiftMigration = 1030; 182 | ProvisioningStyle = Automatic; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = 5797322D1F971A43006DB071 /* Build configuration list for PBXProject "ExpandableCell" */; 187 | compatibilityVersion = "Xcode 8.0"; 188 | developmentRegion = en; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | en, 192 | Base, 193 | ); 194 | mainGroup = 579732291F971A43006DB071; 195 | productRefGroup = 579732331F971A43006DB071 /* Products */; 196 | projectDirPath = ""; 197 | projectRoot = ""; 198 | targets = ( 199 | 579732311F971A43006DB071 /* ExpandableCell */, 200 | ); 201 | }; 202 | /* End PBXProject section */ 203 | 204 | /* Begin PBXResourcesBuildPhase section */ 205 | 579732301F971A43006DB071 /* Resources */ = { 206 | isa = PBXResourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 579732401F971A43006DB071 /* LaunchScreen.storyboard in Resources */, 210 | 5797323D1F971A43006DB071 /* Assets.xcassets in Resources */, 211 | 5797323B1F971A43006DB071 /* Demo.storyboard in Resources */, 212 | 57FF08071FB2D957008C0958 /* CenteredLabelCell.xib in Resources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXShellScriptBuildPhase section */ 219 | 3B9DABDA8FCD5AA9B2C3ADF1 /* [CP] Check Pods Manifest.lock */ = { 220 | isa = PBXShellScriptBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | inputPaths = ( 225 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 226 | "${PODS_ROOT}/Manifest.lock", 227 | ); 228 | name = "[CP] Check Pods Manifest.lock"; 229 | outputPaths = ( 230 | "$(DERIVED_FILE_DIR)/Pods-ExpandableCell-checkManifestLockResult.txt", 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 235 | showEnvVarsInLog = 0; 236 | }; 237 | C68FC4EE3E073A697ABC5066 /* [CP] Embed Pods Frameworks */ = { 238 | isa = PBXShellScriptBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | ); 242 | inputPaths = ( 243 | "${PODS_ROOT}/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-frameworks.sh", 244 | "${BUILT_PRODUCTS_DIR}/AZExpandable/AZExpandable.framework", 245 | ); 246 | name = "[CP] Embed Pods Frameworks"; 247 | outputPaths = ( 248 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AZExpandable.framework", 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | shellPath = /bin/sh; 252 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExpandableCell/Pods-ExpandableCell-frameworks.sh\"\n"; 253 | showEnvVarsInLog = 0; 254 | }; 255 | /* End PBXShellScriptBuildPhase section */ 256 | 257 | /* Begin PBXSourcesBuildPhase section */ 258 | 5797322E1F971A43006DB071 /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | 5759872B1FA39FE200239B5E /* TableViewModel.swift in Sources */, 263 | 57FF08091FB2D9BB008C0958 /* CenteredLabelCell.swift in Sources */, 264 | 579732381F971A43006DB071 /* ComplexDemoViewController.swift in Sources */, 265 | 57FF07FF1FB2CBC2008C0958 /* TableViewModelFactory.swift in Sources */, 266 | FB3A0DBB1FE82D2A00D5C0A6 /* PickerItemsController.swift in Sources */, 267 | 57FF080D1FB2DB73008C0958 /* CellFactory.swift in Sources */, 268 | FBAA601B219F0E24004CC2FA /* LoadMoreDemoViewController.swift in Sources */, 269 | FBDF2A041FE8FCC600889B28 /* BasicDemoViewController.swift in Sources */, 270 | 579732361F971A43006DB071 /* AppDelegate.swift in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXSourcesBuildPhase section */ 275 | 276 | /* Begin PBXVariantGroup section */ 277 | 579732391F971A43006DB071 /* Demo.storyboard */ = { 278 | isa = PBXVariantGroup; 279 | children = ( 280 | 5797323A1F971A43006DB071 /* Base */, 281 | ); 282 | name = Demo.storyboard; 283 | sourceTree = ""; 284 | }; 285 | 5797323E1F971A43006DB071 /* LaunchScreen.storyboard */ = { 286 | isa = PBXVariantGroup; 287 | children = ( 288 | 5797323F1F971A43006DB071 /* Base */, 289 | ); 290 | name = LaunchScreen.storyboard; 291 | sourceTree = ""; 292 | }; 293 | /* End PBXVariantGroup section */ 294 | 295 | /* Begin XCBuildConfiguration section */ 296 | 579732421F971A43006DB071 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_ANALYZER_NONNULL = YES; 301 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 303 | CLANG_CXX_LIBRARY = "libc++"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 307 | CLANG_WARN_BOOL_CONVERSION = YES; 308 | CLANG_WARN_COMMA = YES; 309 | CLANG_WARN_CONSTANT_CONVERSION = YES; 310 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 311 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 312 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 313 | CLANG_WARN_EMPTY_BODY = YES; 314 | CLANG_WARN_ENUM_CONVERSION = YES; 315 | CLANG_WARN_INFINITE_RECURSION = YES; 316 | CLANG_WARN_INT_CONVERSION = YES; 317 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 319 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 321 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 322 | CLANG_WARN_STRICT_PROTOTYPES = YES; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | CODE_SIGN_IDENTITY = "iPhone Developer"; 328 | COPY_PHASE_STRIP = NO; 329 | DEBUG_INFORMATION_FORMAT = dwarf; 330 | ENABLE_STRICT_OBJC_MSGSEND = YES; 331 | ENABLE_TESTABILITY = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu11; 333 | GCC_DYNAMIC_NO_PIC = NO; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_OPTIMIZATION_LEVEL = 0; 336 | GCC_PREPROCESSOR_DEFINITIONS = ( 337 | "DEBUG=1", 338 | "$(inherited)", 339 | ); 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 347 | MTL_ENABLE_DEBUG_INFO = YES; 348 | ONLY_ACTIVE_ARCH = YES; 349 | SDKROOT = iphoneos; 350 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 351 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 352 | SWIFT_VERSION = 4.2; 353 | }; 354 | name = Debug; 355 | }; 356 | 579732431F971A43006DB071 /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ALWAYS_SEARCH_USER_PATHS = NO; 360 | CLANG_ANALYZER_NONNULL = YES; 361 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INFINITE_RECURSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 379 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 382 | CLANG_WARN_STRICT_PROTOTYPES = YES; 383 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 384 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 385 | CLANG_WARN_UNREACHABLE_CODE = YES; 386 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 387 | CODE_SIGN_IDENTITY = "iPhone Developer"; 388 | COPY_PHASE_STRIP = NO; 389 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 390 | ENABLE_NS_ASSERTIONS = NO; 391 | ENABLE_STRICT_OBJC_MSGSEND = YES; 392 | GCC_C_LANGUAGE_STANDARD = gnu11; 393 | GCC_NO_COMMON_BLOCKS = YES; 394 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 395 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 396 | GCC_WARN_UNDECLARED_SELECTOR = YES; 397 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 398 | GCC_WARN_UNUSED_FUNCTION = YES; 399 | GCC_WARN_UNUSED_VARIABLE = YES; 400 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 401 | MTL_ENABLE_DEBUG_INFO = NO; 402 | SDKROOT = iphoneos; 403 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 404 | SWIFT_VERSION = 4.2; 405 | VALIDATE_PRODUCT = YES; 406 | }; 407 | name = Release; 408 | }; 409 | 579732451F971A43006DB071 /* Debug */ = { 410 | isa = XCBuildConfiguration; 411 | baseConfigurationReference = 5B9DB0A27CB6D3B14B4FA504 /* Pods-ExpandableCell.debug.xcconfig */; 412 | buildSettings = { 413 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 414 | CLANG_ENABLE_MODULES = YES; 415 | CODE_SIGN_STYLE = Automatic; 416 | INFOPLIST_FILE = "ExpandableCell/Supporting Files/Info.plist"; 417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 419 | PRODUCT_BUNDLE_IDENTIFIER = com.azonov.ExpandableCell; 420 | PRODUCT_NAME = "$(TARGET_NAME)"; 421 | SWIFT_OBJC_BRIDGING_HEADER = ""; 422 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 423 | SWIFT_VERSION = 5.0; 424 | TARGETED_DEVICE_FAMILY = "1,2"; 425 | }; 426 | name = Debug; 427 | }; 428 | 579732461F971A43006DB071 /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | baseConfigurationReference = 8A984765D95495A0BB3DFE38 /* Pods-ExpandableCell.release.xcconfig */; 431 | buildSettings = { 432 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 433 | CLANG_ENABLE_MODULES = YES; 434 | CODE_SIGN_STYLE = Automatic; 435 | INFOPLIST_FILE = "ExpandableCell/Supporting Files/Info.plist"; 436 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 438 | PRODUCT_BUNDLE_IDENTIFIER = com.azonov.ExpandableCell; 439 | PRODUCT_NAME = "$(TARGET_NAME)"; 440 | SWIFT_OBJC_BRIDGING_HEADER = ""; 441 | SWIFT_VERSION = 5.0; 442 | TARGETED_DEVICE_FAMILY = "1,2"; 443 | }; 444 | name = Release; 445 | }; 446 | /* End XCBuildConfiguration section */ 447 | 448 | /* Begin XCConfigurationList section */ 449 | 5797322D1F971A43006DB071 /* Build configuration list for PBXProject "ExpandableCell" */ = { 450 | isa = XCConfigurationList; 451 | buildConfigurations = ( 452 | 579732421F971A43006DB071 /* Debug */, 453 | 579732431F971A43006DB071 /* Release */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 579732441F971A43006DB071 /* Build configuration list for PBXNativeTarget "ExpandableCell" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 579732451F971A43006DB071 /* Debug */, 462 | 579732461F971A43006DB071 /* Release */, 463 | ); 464 | defaultConfigurationIsVisible = 0; 465 | defaultConfigurationName = Release; 466 | }; 467 | /* End XCConfigurationList section */ 468 | }; 469 | rootObject = 5797322A1F971A43006DB071 /* Project object */; 470 | } 471 | -------------------------------------------------------------------------------- /Example/ExpandableCell.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/ExpandableCell.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ExpandableCell.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/ExpandableCell/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 18/10/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Example/ExpandableCell/Supporting Files/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 | } -------------------------------------------------------------------------------- /Example/ExpandableCell/Supporting Files/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 | -------------------------------------------------------------------------------- /Example/ExpandableCell/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Demo 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 | -------------------------------------------------------------------------------- /Example/ExpandableCell/View Controller/BasicDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicDemoViewController.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 19/12/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AZExpandable 11 | 12 | class BasicDemoViewController: UIViewController { 13 | 14 | // MARK: IBOutlet's 15 | @IBOutlet private weak var tableView: UITableView! 16 | 17 | // MARK: Private Properties 18 | private var expandableTable: ExpandableTable! 19 | private var expandedCell: ExpandedCellInfo? 20 | 21 | // MARK: Lifecycle 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | CellFactory.registerCells(for: tableView) 26 | expandableTable = ExpandableTable(with: tableView, infoProvider: self) 27 | } 28 | 29 | private func toggleItem(at indexPath: IndexPath) { 30 | guard expandedCell?.indexPath != indexPath else { 31 | expandableTable.unexpandCell() 32 | expandedCell = nil 33 | return 34 | } 35 | 36 | // Construct your cell here 37 | let cellType: ExpandedCellInfo.CellType = .custom { [weak self] indexPath -> (UITableViewCell) in 38 | let centeredCell = self?.tableView.dequeueReusableCell(withIdentifier: "CenteredLabelCell", 39 | for: indexPath) as! CenteredLabelCell 40 | centeredCell.configure(with: "Hint Inside your custom cell") 41 | centeredCell.backgroundColor = .lightGray 42 | return centeredCell 43 | } 44 | let cell = ExpandedCellInfo(for: indexPath, cellType: cellType) 45 | expandedCell = cell 46 | expandableTable.expandCell(cell) 47 | } 48 | } 49 | 50 | // MARK: - UITableViewDataSource 51 | extension BasicDemoViewController: UITableViewDataSource { 52 | 53 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 54 | return 100 55 | } 56 | 57 | func tableView(_ tableView: UITableView, 58 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 59 | let cell = tableView.dequeueReusableCell(withIdentifier: "CenteredLabelCell", for: indexPath) as! CenteredLabelCell 60 | cell.configure(with: "Tap to reveal hint for \(indexPath.row) row") 61 | return cell 62 | } 63 | } 64 | 65 | // MARK: - UITableViewDelegate 66 | extension BasicDemoViewController: UITableViewDelegate { 67 | 68 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 69 | toggleItem(at: indexPath) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Example/ExpandableCell/View Controller/CellFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellFactory.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 08/11/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CellFactory { 12 | 13 | // MARK: Private 14 | private static let cellIdentifier = "CenteredLabelCell" 15 | 16 | // MARK: Public 17 | 18 | enum CellFactoryError: Error { 19 | case unsupportedType 20 | case wrongIdentifier 21 | } 22 | 23 | // MARK: Public 24 | static func cell(for cellModel: TableViewModel.Section.Cell, 25 | in tableView: UITableView, 26 | indexPath: IndexPath, 27 | cellAction: @escaping (() -> ())) throws -> UITableViewCell 28 | { 29 | guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, 30 | for: indexPath) as? CenteredLabelCell 31 | else { throw CellFactoryError.wrongIdentifier } 32 | 33 | cell.configure(with: cellModel.title) 34 | 35 | return cell 36 | } 37 | 38 | static func registerCells(for tableView: UITableView) { 39 | let cellIdentifiers = [cellIdentifier] 40 | for cellIdentifier in cellIdentifiers { 41 | let nib = UINib(nibName: cellIdentifier, bundle: .main) 42 | tableView.register(nib, forCellReuseIdentifier: cellIdentifier) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/ExpandableCell/View Controller/ComplexDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComplexDemoViewController.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 18/10/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AZExpandable 11 | 12 | class ComplexDemoViewController: UIViewController { 13 | 14 | // MARK: IBOutlet's 15 | @IBOutlet private weak var tableView: UITableView! 16 | 17 | // MARK: Private Properties 18 | private var expandableTable: ExpandableTable! 19 | private var expandedCell: ExpandedCellInfo? 20 | private var tableViewModel = TableViewModelFactory.staticExpandableTable 21 | private var pickerController: PickerItemsController? 22 | 23 | // MARK: Lifecycle 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | CellFactory.registerCells(for: tableView) 28 | expandableTable = ExpandableTable(with: tableView, infoProvider: self) 29 | // DataSource and Delegate for PickerView, closure is about value changing 30 | pickerController = PickerItemsController { title in 31 | if let indexPath = self.expandedCell?.indexPath { 32 | var viewModel = self.tableViewModel.cell(for: indexPath) 33 | viewModel.title = title 34 | self.tableViewModel.replace(cell: viewModel, at: indexPath) 35 | self.tableView.reloadRows(at: [indexPath], with: .automatic) 36 | } 37 | } 38 | } 39 | 40 | private func toggleItem(at indexPath: IndexPath) { 41 | guard expandedCell?.indexPath != indexPath else { 42 | expandableTable.unexpandCell() 43 | expandedCell = nil 44 | return 45 | } 46 | let cellViewModel = tableViewModel.sections[indexPath.section].cells[indexPath.row] 47 | let cellType: ExpandedCellInfo.CellType 48 | switch cellViewModel.expandingType { 49 | case .date: 50 | cellType = .datePicker { datePicker in 51 | datePicker.minimumDate = Date() 52 | datePicker.addTarget(self.pickerController, 53 | action: #selector(PickerItemsController.datePickerDidChangeValue), 54 | for: .valueChanged) 55 | } 56 | 57 | case .picker: 58 | cellType = .picker { picker in 59 | picker.dataSource = self.pickerController 60 | picker.delegate = self.pickerController 61 | } 62 | 63 | case .custom: 64 | cellType = .custom { [weak self] indexPath -> (UITableViewCell) in 65 | let centeredCell = self?.tableView.dequeueReusableCell(withIdentifier: "CenteredLabelCell", 66 | for: indexPath) as! CenteredLabelCell 67 | centeredCell.configure(with: "Hint Inside your custom cell") 68 | centeredCell.backgroundColor = .lightGray 69 | return centeredCell 70 | } 71 | } 72 | let cell = ExpandedCellInfo(for: indexPath, cellType: cellType) 73 | expandedCell = cell 74 | expandableTable.expandCell(cell) 75 | } 76 | } 77 | 78 | // MARK: - UITableViewDataSource 79 | extension ComplexDemoViewController: UITableViewDataSource { 80 | 81 | func numberOfSections(in tableView: UITableView) -> Int { 82 | return tableViewModel.sections.count 83 | } 84 | 85 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 86 | let section = tableViewModel.sections[section] 87 | return section.title 88 | } 89 | 90 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 91 | return tableViewModel.sections[section].cells.count 92 | } 93 | 94 | func tableView(_ tableView: UITableView, 95 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 96 | let cellModel = tableViewModel.sections[indexPath.section].cells[indexPath.row] 97 | do { 98 | return try CellFactory.cell(for: cellModel, in: tableView, indexPath: indexPath) { [weak self] in 99 | self?.toggleItem(at: indexPath) 100 | } 101 | } catch { 102 | assertionFailure("handle \(error)") 103 | return UITableViewCell(style: .default, reuseIdentifier: "default") 104 | } 105 | } 106 | } 107 | 108 | // MARK: - UITableViewDelegate 109 | extension ComplexDemoViewController: UITableViewDelegate { 110 | 111 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 112 | toggleItem(at: indexPath) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Example/ExpandableCell/View Controller/LoadMoreDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadMoreDemoViewController.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 11/17/18. 6 | // Copyright © 2018 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AZExpandable 11 | 12 | class LoadMoreDemoViewController: UIViewController { 13 | 14 | // MARK: IBOutlet's 15 | @IBOutlet private weak var tableView: UITableView! 16 | 17 | // MARK: Private Properties 18 | private var pagingTable: PagingTable! 19 | private var rowsCount = 20 20 | 21 | // MARK: Lifecycle 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | CellFactory.registerCells(for: tableView) 26 | pagingTable = PagingTable(tableView: tableView, infoProvider: self) 27 | { [weak self] paging in 28 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { 29 | self?.rowsCount += 20 30 | self?.tableView.reloadData() 31 | }) 32 | } 33 | } 34 | 35 | @IBAction func paginationValueChanged(_ sender: UISwitch) { 36 | if sender.isOn { 37 | try? pagingTable.addPagingIndicator() 38 | } else { 39 | try? pagingTable.removePagingIndicator() 40 | } 41 | } 42 | } 43 | 44 | // MARK: - UITableViewDataSource 45 | extension LoadMoreDemoViewController: UITableViewDataSource { 46 | 47 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 48 | return rowsCount 49 | } 50 | 51 | func tableView(_ tableView: UITableView, 52 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 53 | let cell = tableView.dequeueReusableCell(withIdentifier: "CenteredLabelCell", for: indexPath) as! CenteredLabelCell 54 | cell.configure(with: "\(indexPath.row) row") 55 | return cell 56 | } 57 | } 58 | 59 | // MARK: - UITableViewDelegate 60 | extension LoadMoreDemoViewController: UITableViewDelegate {} 61 | -------------------------------------------------------------------------------- /Example/ExpandableCell/View Controller/PickerItemsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerItemsController.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 18/12/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PickerItemsController: NSObject, UIPickerViewDelegate, UIPickerViewDataSource { 12 | 13 | private var updateClosure: (String)->() 14 | 15 | init(updateClosure: @escaping (String)->()) { 16 | self.updateClosure = updateClosure 17 | } 18 | 19 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 20 | return 1 21 | } 22 | 23 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 24 | return 10 25 | } 26 | 27 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 28 | return "row \(row) component \(component)" 29 | } 30 | 31 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 32 | if let title = self.pickerView(pickerView, titleForRow: row, forComponent: component) { 33 | updateClosure(title) 34 | } 35 | } 36 | 37 | @objc func datePickerDidChangeValue(_ datePicker: UIDatePicker) { 38 | let title = DateFormatter.localizedString(from: datePicker.date, dateStyle: .medium, timeStyle: .medium) 39 | updateClosure(title) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Example/ExpandableCell/ViewModels/TableViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewModel.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 27/10/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ExpandingType { 12 | case date 13 | case picker 14 | case custom 15 | } 16 | 17 | struct TableViewModel { 18 | 19 | struct Section { 20 | 21 | struct Cell { 22 | var title: String 23 | var expandingType: ExpandingType 24 | } 25 | let title: String? 26 | var cells: [Cell] 27 | } 28 | 29 | static var expandable = TableViewModel(sections: []) 30 | var sections: [Section] 31 | } 32 | 33 | 34 | extension TableViewModel { 35 | 36 | func cell(for indexPath: IndexPath) -> TableViewModel.Section.Cell { 37 | return sections[indexPath.section].cells[indexPath.row] 38 | } 39 | 40 | mutating func replace(cell: TableViewModel.Section.Cell, at indexPath: IndexPath) { 41 | sections[indexPath.section].cells[indexPath.row] = cell 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example/ExpandableCell/ViewModels/TableViewModelFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewModelFactory.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 08/11/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct TableViewModelFactory { 12 | 13 | static var staticExpandableTable: TableViewModel { 14 | 15 | let helpCell1 = TableViewModel.Section.Cell(title: "Tap to reveal help1", expandingType: .custom) 16 | let helpCell2 = TableViewModel.Section.Cell(title: "Tap to reveal help2", expandingType: .custom) 17 | let helpCell3 = TableViewModel.Section.Cell(title: "Tap to reveal help3", expandingType: .custom) 18 | 19 | let dateCell1 = TableViewModel.Section.Cell(title: "Date 1", expandingType: .date) 20 | let dateCell2 = TableViewModel.Section.Cell(title: "Date 2", expandingType: .date) 21 | let dateCell3 = TableViewModel.Section.Cell(title: "Date 3", expandingType: .date) 22 | 23 | let pickerCell1 = TableViewModel.Section.Cell(title: "Picker 1", expandingType: .picker) 24 | let pickerCell2 = TableViewModel.Section.Cell(title: "Picker 2", expandingType: .picker) 25 | let pickerCell3 = TableViewModel.Section.Cell(title: "Picker 3", expandingType: .picker) 26 | 27 | let sectionHelp = TableViewModel.Section(title: "Help cells section", cells: [helpCell1, helpCell2, helpCell3]) 28 | let sectionDate = TableViewModel.Section(title: "Date cells section", cells: [dateCell1, dateCell2, dateCell3]) 29 | let sectionPicker = TableViewModel.Section(title: "Picker cells section", cells: [pickerCell1, pickerCell2, pickerCell3]) 30 | 31 | return TableViewModel(sections: [sectionHelp, sectionDate, sectionPicker]) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Example/ExpandableCell/Views/Base.lproj/Demo.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 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /Example/ExpandableCell/Views/CenteredLabelCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CenteredLabelCell.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 08/11/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CenteredLabelCell: UITableViewCell { 12 | 13 | // MARK: Private Outlets 14 | @IBOutlet private var label: UILabel! 15 | 16 | // MARK: Lifecycle 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | selectionStyle = .none 20 | } 21 | 22 | override func prepareForReuse() { 23 | super.prepareForReuse() 24 | label.text = "" 25 | backgroundColor = .white 26 | } 27 | 28 | // MARK: Public 29 | func configure(with title: String) { 30 | label.text = title 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/ExpandableCell/Views/CenteredLabelCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'ExpandableCell' do 4 | pod 'AZExpandable', :path => '../' 5 | end -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 6 | 7 | gem "cocoapods" 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.3) 11 | claide (1.0.2) 12 | cocoapods (1.7.5) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.7.5) 16 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 17 | cocoapods-downloader (>= 1.2.2, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.1, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (>= 2.3.0, < 3.0) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.6) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.4) 30 | xcodeproj (>= 1.10.0, < 2.0) 31 | cocoapods-core (1.7.5) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.4) 36 | cocoapods-downloader (1.2.2) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.1.0) 41 | cocoapods-trunk (1.3.1) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.1.5) 47 | escape (0.0.4) 48 | fourflusher (2.3.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.11.3) 54 | molinillo (0.6.6) 55 | nanaimo (0.2.6) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | ruby-macho (1.4.0) 59 | thread_safe (0.3.6) 60 | tzinfo (1.2.5) 61 | thread_safe (~> 0.1) 62 | xcodeproj (1.11.0) 63 | CFPropertyList (>= 2.3.3, < 4.0) 64 | atomos (~> 0.1.3) 65 | claide (>= 1.0.2, < 2.0) 66 | colored2 (~> 3.1) 67 | nanaimo (~> 0.2.6) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | cocoapods 74 | 75 | BUNDLED WITH 76 | 2.0.1 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrey 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![AZExpandable](https://raw.githubusercontent.com/azonov/expandableTable/master/logo_long.png) 2 | 3 | **AZExpandable** is a lightweight proxy for UITableView to expand cells. It incapsulates native NSProxy mechanism inside and gives swifty api outside. 4 |
5 |
General advantages: **No Subclassing**, **No Swizzling**, **Easy to intagrate** 6 | 7 | - [Requirements](#requirements) 8 | - [Communication](#communication) 9 | - [Installation](#installation) 10 | - [Usage](#usage) 11 | - [License](#license) 12 | 13 | ## Example 14 | 15 | ![Alt Text](https://media.giphy.com/media/l0HU17JvuEjnkLskg/giphy.gif) 16 | 17 | ## Requirements 18 | 19 | - iOS 8.0+ 20 | - Xcode 9.2+ 21 | - Swift 4.0+ 22 | 23 | ## Communication 24 | 25 | - If you'd like to **ask a general question**, use [Twitter](http://twitter.com/avzonov). 26 | - If you **found a bug**, open an issue. 27 | - If you **have a feature request**, open an issue. 28 | - If you **want to contribute**, submit a pull request. 29 | 30 | ## Installation 31 | 32 | ### CocoaPods 33 | 34 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 35 | 36 | ```bash 37 | $ gem install cocoapods 38 | ``` 39 | 40 | > CocoaPods 1.1+ is required to build AZExpandable. 41 | 42 | To integrate AZExpandable into your Xcode project using CocoaPods, specify it in your `Podfile`: 43 | 44 | ```ruby 45 | source 'https://github.com/CocoaPods/Specs.git' 46 | use_frameworks! 47 | 48 | target '' do 49 | pod 'AZExpandable' 50 | end 51 | ``` 52 | 53 | Then, run the following command: 54 | 55 | ```bash 56 | $ pod install 57 | ``` 58 | 59 | ## Usage 60 | 61 | 62 | ```swift 63 | private var expandableTable: ExpandableTable!// Expanding Table Proxy 64 | 65 | override func viewDidLoad() { 66 | super.viewDidLoad() 67 | // infoProvider - UITableViewDelegate & UITableViewDataSource 68 | expandableTable = ExpandableTable(with: tableView, infoProvider: self) 69 | } 70 | 71 | func expandCell(at indexPath: IndexPath) { 72 | let cellClosure: CellClosure = { (IndexPath) -> (UITableViewCell) in 73 | //Your custom expanding cell 74 | return self.tableView.dequeueReusableCell(withIdentifier: "Identifier", for: indexPath) 75 | } 76 | expandableTable.expandCell(ExpandedCellInfo(for: indexPath, cellType: .custom(cellClosure))) 77 | } 78 | 79 | func unexpandCell() { 80 | expandableTable.unexpandCell() 81 | } 82 | ``` 83 | 84 | 85 | ## License 86 | 87 | AZExpandable is released under the MIT license. [See LICENSE](https://github.com/azonov/expandableTable/blob/master/LICENSE) for details. 88 | -------------------------------------------------------------------------------- /Source/ExpandableCell-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "ExpandableProxy.h" 6 | -------------------------------------------------------------------------------- /Source/ExpandableTable/Cells/ActivityIndicatorCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityIndicatorCell.swift 3 | // AZExpandable 4 | // 5 | // Created by Andrey Zonov on 11/17/18. 6 | // 7 | 8 | import UIKit 9 | 10 | class ActivityIndicatorCell: UITableViewCell { 11 | 12 | // MARK: Public Properties 13 | lazy var activityIndicator: UIActivityIndicatorView = { 14 | let frame = CGRect(origin: .zero, size: CGSize(width: 50, height: 50)) 15 | let activityIndicator = UIActivityIndicatorView(frame: frame) 16 | 17 | activityIndicator.color = .black 18 | activityIndicator.hidesWhenStopped = false 19 | 20 | return activityIndicator 21 | }() 22 | 23 | // MARK: Lifecycle 24 | init() { 25 | super.init(style: .default, reuseIdentifier: String(describing: type(of: self))) 26 | 27 | addSubview(activityIndicator) 28 | activityIndicator.makeEdgesEqualToSuperView() 29 | activityIndicator.startAnimating() 30 | heightAnchor.constraint(equalToConstant: 44).isActive = true 31 | separatorInset = UIEdgeInsets(top: 0, left: CGFloat.infinity, bottom: 0, right: 0) 32 | } 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Source/ExpandableTable/Cells/DatePickerCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatePickerCell.swift 3 | // AZExpandable 4 | // 5 | // Created by Andrey Zonov on 20/10/2017. 6 | // 7 | 8 | import UIKit 9 | 10 | class DatePickerCell: UITableViewCell { 11 | 12 | // MARK: Public Properties 13 | weak var datePicker: UIDatePicker! 14 | 15 | // MARK: Lifecycle 16 | init() { 17 | let datePicker = UIDatePicker(frame: .zero) 18 | self.datePicker = datePicker 19 | 20 | super.init(style: .default, reuseIdentifier: String(describing: type(of: self))) 21 | 22 | addSubview(datePicker) 23 | datePicker.makeEdgesEqualToSuperView() 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/ExpandableTable/Cells/PickerCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerCell.swift 3 | // Pods-ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 27/10/2017. 6 | // 7 | 8 | import UIKit 9 | 10 | class PickerCell: UITableViewCell { 11 | 12 | // MARK: Public Properties 13 | weak var picker: UIPickerView! 14 | 15 | // MARK: Lifecycle 16 | init() { 17 | let picker = UIPickerView(frame: .zero) 18 | self.picker = picker 19 | 20 | super.init(style: .default, reuseIdentifier: String(describing: type(of: self))) 21 | 22 | addSubview(picker) 23 | picker.makeEdgesEqualToSuperView() 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/ExpandableTable/Cells/UIView+Autolayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Autolayout.swift 3 | // AZExpandable 4 | // 5 | // Created by Andrey Zonov on 11/16/18. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | 12 | func makeEdgesEqualToSuperView() { 13 | guard let superView = superview else { 14 | return 15 | } 16 | translatesAutoresizingMaskIntoConstraints = false 17 | 18 | NSLayoutConstraint.activate([ 19 | topAnchor.constraint(equalTo: superView.topAnchor), 20 | bottomAnchor.constraint(equalTo: superView.bottomAnchor), 21 | leftAnchor.constraint(equalTo: superView.leftAnchor), 22 | rightAnchor.constraint(equalTo: superView.rightAnchor)]) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/ExpandableTable/ExpandableCellInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandableCellInfo.swift 3 | // Pods 4 | // 5 | // Created by Andrey Zonov on 20/10/2017. 6 | // 7 | 8 | import UIKit 9 | 10 | public typealias CellClosure = ((IndexPath) -> (UITableViewCell)) 11 | public typealias DatePickerSetupClosure = ((UIDatePicker) -> ()) 12 | public typealias PickerSetupClosure = ((UIPickerView) -> ()) 13 | 14 | public struct ExpandedCellInfo: Equatable { 15 | 16 | // MARK: Private Data Structures 17 | public enum CellType { 18 | case datePicker(DatePickerSetupClosure?) 19 | case picker(PickerSetupClosure) 20 | case custom(CellClosure) 21 | } 22 | 23 | // MARK: Public Properties 24 | public var indexPath: IndexPath 25 | public var cellType: CellType 26 | 27 | // MARK: Lifecycle 28 | public init(for indexPath: IndexPath, cellType: CellType) { 29 | self.indexPath = indexPath 30 | self.cellType = cellType 31 | } 32 | 33 | // MARK: Public 34 | public static func ==(lhs: ExpandedCellInfo, rhs: ExpandedCellInfo) -> Bool { 35 | return lhs.indexPath == rhs.indexPath 36 | } 37 | 38 | func isHigherInSameSection(than hint: ExpandedCellInfo) -> Bool { 39 | return hint.indexPath.section == indexPath.section 40 | && hint.indexPath.row > indexPath.row 41 | } 42 | 43 | func computedIndexPath(from indexPath: IndexPath) -> IndexPath { 44 | var computedIndexPath = indexPath 45 | if indexPath.section == self.indexPath.section 46 | && indexPath.row > self.indexPath.row { 47 | computedIndexPath.decrementRow() 48 | } 49 | return computedIndexPath 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Source/ExpandableTable/ExpandableTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandableTable.swift 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 18/10/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias TableInfoProvider = UITableViewDelegate & UITableViewDataSource 12 | 13 | public class ExpandableTable: NSObject { 14 | 15 | // MARK: Private Properties 16 | private var proxy: ExpandableProxy? 17 | private var expandedCell: ExpandedCellInfo? 18 | private weak var tableView: UITableView! 19 | private weak var infoProvider: TableInfoProvider! 20 | 21 | // MARK: Lifecycle 22 | public init(with tableView: UITableView, infoProvider: TableInfoProvider) { 23 | self.tableView = tableView 24 | self.infoProvider = infoProvider 25 | super.init() 26 | proxy = ExpandableProxy(tableDelegate: infoProvider, proxyDelegate: self) 27 | tableView.dataSource = proxy 28 | tableView.delegate = proxy 29 | } 30 | 31 | // MARK: Public 32 | public func expandCell(_ cell: ExpandedCellInfo) { 33 | var resultCell = cell 34 | resultCell.indexPath.incrementRow() 35 | 36 | guard expandedCell != resultCell else { return } 37 | 38 | if expandedCell != nil { 39 | unexpandCell() 40 | } 41 | expandedCell = resultCell 42 | tableView.beginUpdates() 43 | tableView?.insertRows(at: [resultCell.indexPath], with: .middle) 44 | tableView.endUpdates() 45 | } 46 | 47 | public func unexpandCell() { 48 | guard let indexPath = expandedCell?.indexPath else { return } 49 | expandedCell = nil 50 | tableView.beginUpdates() 51 | tableView?.deleteRows(at: [indexPath], with: .middle) 52 | tableView.endUpdates() 53 | } 54 | } 55 | 56 | // MARK: - UITableViewDataSource 57 | extension ExpandableTable: UITableViewDataSource { 58 | 59 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 60 | let rows = infoProvider.tableView(tableView ,numberOfRowsInSection: section) 61 | guard let expandedCell = expandedCell else { 62 | return rows 63 | } 64 | return expandedCell.indexPath.section == section ? rows + 1 : rows 65 | } 66 | 67 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 68 | guard indexPath != expandedCell?.indexPath else { 69 | switch expandedCell!.cellType { 70 | case .custom(let cellClosure): 71 | return cellClosure(indexPath) 72 | 73 | case .datePicker(let setupClosure): 74 | let cell = DatePickerCell() 75 | setupClosure?(cell.datePicker) 76 | return cell 77 | 78 | case .picker(let setupClosure): 79 | let cell = PickerCell() 80 | setupClosure(cell.picker) 81 | return cell 82 | } 83 | } 84 | let computedIndexPath = expandedCell?.computedIndexPath(from: indexPath) ?? indexPath 85 | return infoProvider.tableView(tableView, cellForRowAt: computedIndexPath) 86 | } 87 | } 88 | 89 | // MARK: - UITableViewDelegate 90 | extension ExpandableTable: UITableViewDelegate { 91 | 92 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 93 | guard indexPath != expandedCell?.indexPath else { 94 | return UITableView.automaticDimension 95 | } 96 | let computedIndexPath = expandedCell?.computedIndexPath(from: indexPath) ?? indexPath 97 | return infoProvider.tableView?(tableView, heightForRowAt: computedIndexPath) ?? UITableView.automaticDimension 98 | } 99 | 100 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 101 | if indexPath != expandedCell?.indexPath { 102 | let computedIndexPath = expandedCell?.computedIndexPath(from: indexPath) ?? indexPath 103 | infoProvider.tableView?(tableView, didSelectRowAt: computedIndexPath) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Source/ExpandableTable/IndexPath+SyntaxSugar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexPath+SyntaxSugar.swift 3 | // Pods 4 | // 5 | // Created by Andrey Zonov on 20/10/2017. 6 | // 7 | 8 | import Foundation 9 | 10 | extension IndexPath { 11 | 12 | mutating func incrementRow() { 13 | row += 1 14 | } 15 | 16 | mutating func decrementRow() { 17 | row -= 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/ExpandableTable/PagingTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PagingTable.swift 3 | // AZExpandable 4 | // 5 | // Created by Andrey Zonov on 18/11/2018. 6 | // 7 | 8 | import UIKit 9 | 10 | public typealias NextPageClosure = (PagingTable) -> () 11 | 12 | public class PagingTable: NSObject { 13 | 14 | public enum PagingError: Error { 15 | case described(string: String) 16 | } 17 | 18 | // MARK: Private Properties 19 | private var proxy: ExpandableProxy? 20 | private var loadMoreIndexPath: IndexPath? 21 | private weak var tableView: UITableView! 22 | private weak var infoProvider: TableInfoProvider! 23 | private var nextPageClosure: NextPageClosure 24 | 25 | // MARK: Lifecycle 26 | public init(tableView: UITableView, 27 | infoProvider: TableInfoProvider, 28 | nextPage: @escaping NextPageClosure) { 29 | self.tableView = tableView 30 | self.infoProvider = infoProvider 31 | self.nextPageClosure = nextPage 32 | super.init() 33 | proxy = ExpandableProxy(tableDelegate: infoProvider, proxyDelegate: self) 34 | tableView.dataSource = proxy 35 | tableView.delegate = proxy 36 | } 37 | 38 | public func addPagingIndicator() throws { 39 | try makePagingIndicatorVisible(true) 40 | } 41 | 42 | public func removePagingIndicator() throws { 43 | try makePagingIndicatorVisible(false) 44 | } 45 | 46 | private func makePagingIndicatorVisible(_ isVisible: Bool) throws { 47 | let sectionsCount = tableView.numberOfSections 48 | 49 | guard sectionsCount > 0 else { 50 | throw PagingError.described(string: "numberOfSections > 0") 51 | } 52 | 53 | let rowsCount = tableView.numberOfRows(inSection: sectionsCount - 1) 54 | 55 | guard rowsCount > 0 else { 56 | throw PagingError.described(string: "rowsCount > 0") 57 | } 58 | 59 | var indexPath = IndexPath(item: rowsCount - 1, 60 | section: sectionsCount - 1) 61 | loadMoreIndexPath = isVisible ? indexPath : nil 62 | tableView.beginUpdates() 63 | if isVisible { 64 | indexPath.incrementRow() 65 | tableView?.insertRows(at: [indexPath], with: .top) 66 | } else { 67 | tableView?.deleteRows(at: [indexPath], with: .bottom) 68 | } 69 | tableView.endUpdates() 70 | } 71 | } 72 | 73 | // MARK: - UITableViewDataSource 74 | extension PagingTable: UITableViewDataSource { 75 | 76 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 77 | let rows = infoProvider.tableView(tableView ,numberOfRowsInSection: section) 78 | if loadMoreIndexPath?.section == section { 79 | loadMoreIndexPath?.row = rows 80 | return rows + 1 81 | } else { 82 | return rows 83 | } 84 | } 85 | 86 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 87 | guard loadMoreIndexPath != indexPath else { 88 | return ActivityIndicatorCell() 89 | } 90 | return infoProvider.tableView(tableView, cellForRowAt: indexPath) 91 | } 92 | } 93 | 94 | // MARK: - UITableViewDelegate 95 | extension PagingTable: UITableViewDelegate { 96 | 97 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 98 | guard loadMoreIndexPath != indexPath else { 99 | nextPageClosure(self) 100 | return 101 | } 102 | infoProvider.tableView?(tableView, willDisplay: cell, forRowAt: indexPath) 103 | } 104 | 105 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 106 | guard loadMoreIndexPath != indexPath else { 107 | return UITableView.automaticDimension 108 | } 109 | return infoProvider.tableView?(tableView, heightForRowAt: indexPath) ?? UITableView.automaticDimension 110 | } 111 | 112 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 113 | guard loadMoreIndexPath != indexPath else { 114 | return 115 | } 116 | infoProvider.tableView?(tableView, didSelectRowAt: indexPath) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Source/Proxy/ExpandableProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandableProxy.h 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 18/10/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface ExpandableProxy : NSProxy 13 | 14 | - (instancetype)initWithTableDelegate:(id )tableDelegate 15 | proxyDelegate:(id )proxyDelegate; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Source/Proxy/ExpandableProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandableProxy.m 3 | // ExpandableCell 4 | // 5 | // Created by Andrey Zonov on 18/10/2017. 6 | // Copyright © 2017 Andrey Zonov. All rights reserved. 7 | // 8 | 9 | #import "ExpandableProxy.h" 10 | 11 | @interface ExpandableProxy() 12 | 13 | @property (nonatomic, weak) id proxyDelegate; 14 | @property (nonatomic, weak) id tableDelegate; 15 | 16 | @end 17 | 18 | @implementation ExpandableProxy 19 | 20 | - (instancetype)initWithTableDelegate:(id )tableDelegate 21 | proxyDelegate:(id )proxyDelegate 22 | { 23 | self.tableDelegate = tableDelegate; 24 | self.proxyDelegate = proxyDelegate; 25 | 26 | return self; 27 | } 28 | 29 | - (id)forwardingTargetForSelector:(SEL)aSelector { 30 | if ([self.proxyDelegate respondsToSelector:aSelector]) { 31 | return self.proxyDelegate; 32 | } else if ([self.tableDelegate respondsToSelector:aSelector]) { 33 | return self.tableDelegate; 34 | } 35 | return nil; 36 | } 37 | 38 | - (BOOL)respondsToSelector:(SEL)aSelector { 39 | if ([self.proxyDelegate respondsToSelector:aSelector]) { 40 | return YES; 41 | } else if ([self.tableDelegate respondsToSelector:aSelector]) { 42 | return YES; 43 | } 44 | return NO; 45 | } 46 | 47 | - (void)forwardInvocation:(NSInvocation *)invocation { 48 | [invocation invokeWithTarget:self.tableDelegate]; 49 | } 50 | 51 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 52 | return [self.tableDelegate methodSignatureForSelector:sel]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azonov/ExpandableTable/b174053436edcb4a40b4dfb4ed8c7e5c817e6dd5/logo.png -------------------------------------------------------------------------------- /logo_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azonov/ExpandableTable/b174053436edcb4a40b4dfb4ed8c7e5c817e6dd5/logo_long.png --------------------------------------------------------------------------------