├── .codecov.yml ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .swiftlint.yml ├── Assets ├── demo.gif ├── logo.sketch └── logo.svg ├── Example ├── StackableTableViewExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── StackableTableViewExample │ ├── AppDelegate.swift │ ├── Supporting │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Extensions.swift │ └── Info.plist │ ├── ViewController.swift │ └── Views │ ├── LoremIpsumView.swift │ ├── SliderView.swift │ ├── TagsView.swift │ └── WebView.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Info.plist ├── StackableTableView.h └── StackableTableView.swift ├── StackableTableView.podspec ├── StackableTableView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── StackableTableView.xcscheme └── Tests ├── Info.plist ├── StackableTableViewTests+Helpers.swift └── StackableTableViewTests.swift /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: 70...100 5 | 6 | status: 7 | project: true 8 | patch: true 9 | changes: true -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: StackableTableView 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | Darwin: 13 | name: Darwin 14 | runs-on: macos-latest 15 | env: 16 | PROJECT: StackableTableView.xcodeproj 17 | DEVELOPER_DIR: /Applications/Xcode_11.4.app/Contents/Developer 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Bundle Install 21 | run: bundle install 22 | - name: Brew Upgrade 23 | run: | 24 | brew update 25 | brew outdated xctool || brew upgrade xctool 26 | brew install swiftlint 27 | - name: Test iOS 28 | run: | 29 | xcodebuild clean build test -project $PROJECT -scheme $SCHEME -destination "$DESTINATION" | XCPRETTY_JSON_FILE_OUTPUT="xcodebuild-ios.json" xcpretty -f `xcpretty-json-formatter` 30 | bash <(curl -s https://codecov.io/bash) -cF ios -J 'StackableTableView' 31 | env: 32 | SCHEME: StackableTableView 33 | DESTINATION: platform=iOS Simulator,name=iPhone 11 34 | 35 | CocoaPods: 36 | name: CocoaPods 37 | runs-on: macos-latest 38 | env: 39 | DEVELOPER_DIR: /Applications/Xcode_11.4.app/Contents/Developer 40 | steps: 41 | - uses: actions/checkout@v1 42 | - name: Bundle Install 43 | run: bundle install 44 | - name: CocoaPods 45 | run: bundle exec pod lib lint --skip-tests --allow-warnings --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Xcode 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData/ 7 | build.log 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 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | ## Swift Package Manager 35 | .build/ 36 | .swiftpm/ 37 | 38 | ## CocoaPods 39 | Pods/ 40 | 41 | ## Carthage 42 | Carthage/Build -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length -------------------------------------------------------------------------------- /Assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omaralbeik/StackableTableView/e60b032a453acfc274f0174ac1673b4ef1717d04/Assets/demo.gif -------------------------------------------------------------------------------- /Assets/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omaralbeik/StackableTableView/e60b032a453acfc274f0174ac1673b4ef1717d04/Assets/logo.sketch -------------------------------------------------------------------------------- /Assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo 5 | Created with Sketch. 6 | 9 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 070826C723B28DC3003AD70D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 070826C523B28DC3003AD70D /* LaunchScreen.storyboard */; }; 11 | 0781B29A23B287D40029BA57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0781B29923B287D40029BA57 /* Assets.xcassets */; }; 12 | 0781B2AC23B288880029BA57 /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B2A723B288880029BA57 /* SliderView.swift */; }; 13 | 0781B2AD23B288880029BA57 /* LoremIpsumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B2A823B288880029BA57 /* LoremIpsumView.swift */; }; 14 | 0781B2AE23B288880029BA57 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B2A923B288880029BA57 /* WebView.swift */; }; 15 | 0781B2AF23B288880029BA57 /* TagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B2AA23B288880029BA57 /* TagsView.swift */; }; 16 | 0781B2B023B288880029BA57 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B2AB23B288880029BA57 /* Extensions.swift */; }; 17 | 0781B2B323B2892D0029BA57 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B29023B287D30029BA57 /* AppDelegate.swift */; }; 18 | 0781B2B423B2892D0029BA57 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0781B29423B287D30029BA57 /* ViewController.swift */; }; 19 | 0781B2BA23B28CE10029BA57 /* StackableTableView in Frameworks */ = {isa = PBXBuildFile; productRef = 0781B2B923B28CE10029BA57 /* StackableTableView */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 070826C623B28DC3003AD70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | 0781B28D23B287D30029BA57 /* StackableTableViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StackableTableViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 0781B29023B287D30029BA57 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | 0781B29423B287D30029BA57 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 27 | 0781B29923B287D40029BA57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 0781B29E23B287D40029BA57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 0781B2A723B288880029BA57 /* SliderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; 30 | 0781B2A823B288880029BA57 /* LoremIpsumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoremIpsumView.swift; sourceTree = ""; }; 31 | 0781B2A923B288880029BA57 /* WebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; 32 | 0781B2AA23B288880029BA57 /* TagsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagsView.swift; sourceTree = ""; }; 33 | 0781B2AB23B288880029BA57 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 0781B28A23B287D30029BA57 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | 0781B2BA23B28CE10029BA57 /* StackableTableView in Frameworks */, 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 0781B28423B287D30029BA57 = { 49 | isa = PBXGroup; 50 | children = ( 51 | 0781B28F23B287D30029BA57 /* StackableTableViewExample */, 52 | 0781B28E23B287D30029BA57 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 0781B28E23B287D30029BA57 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 0781B28D23B287D30029BA57 /* StackableTableViewExample.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 0781B28F23B287D30029BA57 /* StackableTableViewExample */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 0781B29023B287D30029BA57 /* AppDelegate.swift */, 68 | 0781B29423B287D30029BA57 /* ViewController.swift */, 69 | 0781B2B123B288F60029BA57 /* Views */, 70 | 0781B2B223B289010029BA57 /* Supporting */, 71 | ); 72 | path = StackableTableViewExample; 73 | sourceTree = ""; 74 | }; 75 | 0781B2B123B288F60029BA57 /* Views */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 0781B2A823B288880029BA57 /* LoremIpsumView.swift */, 79 | 0781B2A723B288880029BA57 /* SliderView.swift */, 80 | 0781B2AA23B288880029BA57 /* TagsView.swift */, 81 | 0781B2A923B288880029BA57 /* WebView.swift */, 82 | ); 83 | path = Views; 84 | sourceTree = ""; 85 | }; 86 | 0781B2B223B289010029BA57 /* Supporting */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 070826C523B28DC3003AD70D /* LaunchScreen.storyboard */, 90 | 0781B2AB23B288880029BA57 /* Extensions.swift */, 91 | 0781B29923B287D40029BA57 /* Assets.xcassets */, 92 | 0781B29E23B287D40029BA57 /* Info.plist */, 93 | ); 94 | path = Supporting; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXNativeTarget section */ 100 | 0781B28C23B287D30029BA57 /* StackableTableViewExample */ = { 101 | isa = PBXNativeTarget; 102 | buildConfigurationList = 0781B2A123B287D40029BA57 /* Build configuration list for PBXNativeTarget "StackableTableViewExample" */; 103 | buildPhases = ( 104 | 0781B28923B287D30029BA57 /* Sources */, 105 | 0781B28A23B287D30029BA57 /* Frameworks */, 106 | 0781B28B23B287D30029BA57 /* Resources */, 107 | ); 108 | buildRules = ( 109 | ); 110 | dependencies = ( 111 | ); 112 | name = StackableTableViewExample; 113 | packageProductDependencies = ( 114 | 0781B2B923B28CE10029BA57 /* StackableTableView */, 115 | ); 116 | productName = StackableTableViewExample; 117 | productReference = 0781B28D23B287D30029BA57 /* StackableTableViewExample.app */; 118 | productType = "com.apple.product-type.application"; 119 | }; 120 | /* End PBXNativeTarget section */ 121 | 122 | /* Begin PBXProject section */ 123 | 0781B28523B287D30029BA57 /* Project object */ = { 124 | isa = PBXProject; 125 | attributes = { 126 | LastSwiftUpdateCheck = 1130; 127 | LastUpgradeCheck = 1130; 128 | ORGANIZATIONNAME = "Omar Albeik"; 129 | TargetAttributes = { 130 | 0781B28C23B287D30029BA57 = { 131 | CreatedOnToolsVersion = 11.3; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = 0781B28823B287D30029BA57 /* Build configuration list for PBXProject "StackableTableViewExample" */; 136 | compatibilityVersion = "Xcode 9.3"; 137 | developmentRegion = en; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = 0781B28423B287D30029BA57; 144 | packageReferences = ( 145 | 0781B2B823B28CE10029BA57 /* XCRemoteSwiftPackageReference "StackableTableView" */, 146 | ); 147 | productRefGroup = 0781B28E23B287D30029BA57 /* Products */; 148 | projectDirPath = ""; 149 | projectRoot = ""; 150 | targets = ( 151 | 0781B28C23B287D30029BA57 /* StackableTableViewExample */, 152 | ); 153 | }; 154 | /* End PBXProject section */ 155 | 156 | /* Begin PBXResourcesBuildPhase section */ 157 | 0781B28B23B287D30029BA57 /* Resources */ = { 158 | isa = PBXResourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 0781B29A23B287D40029BA57 /* Assets.xcassets in Resources */, 162 | 070826C723B28DC3003AD70D /* LaunchScreen.storyboard in Resources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXResourcesBuildPhase section */ 167 | 168 | /* Begin PBXSourcesBuildPhase section */ 169 | 0781B28923B287D30029BA57 /* Sources */ = { 170 | isa = PBXSourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 0781B2B323B2892D0029BA57 /* AppDelegate.swift in Sources */, 174 | 0781B2AC23B288880029BA57 /* SliderView.swift in Sources */, 175 | 0781B2AE23B288880029BA57 /* WebView.swift in Sources */, 176 | 0781B2AD23B288880029BA57 /* LoremIpsumView.swift in Sources */, 177 | 0781B2B423B2892D0029BA57 /* ViewController.swift in Sources */, 178 | 0781B2AF23B288880029BA57 /* TagsView.swift in Sources */, 179 | 0781B2B023B288880029BA57 /* Extensions.swift in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXVariantGroup section */ 186 | 070826C523B28DC3003AD70D /* LaunchScreen.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | 070826C623B28DC3003AD70D /* Base */, 190 | ); 191 | name = LaunchScreen.storyboard; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXVariantGroup section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 0781B29F23B287D40029BA57 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_ENABLE_OBJC_WEAK = YES; 208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 209 | CLANG_WARN_BOOL_CONVERSION = YES; 210 | CLANG_WARN_COMMA = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 224 | CLANG_WARN_STRICT_PROTOTYPES = YES; 225 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 226 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 227 | CLANG_WARN_UNREACHABLE_CODE = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | COPY_PHASE_STRIP = NO; 230 | DEBUG_INFORMATION_FORMAT = dwarf; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | ENABLE_TESTABILITY = YES; 233 | GCC_C_LANGUAGE_STANDARD = gnu11; 234 | GCC_DYNAMIC_NO_PIC = NO; 235 | GCC_NO_COMMON_BLOCKS = YES; 236 | GCC_OPTIMIZATION_LEVEL = 0; 237 | GCC_PREPROCESSOR_DEFINITIONS = ( 238 | "DEBUG=1", 239 | "$(inherited)", 240 | ); 241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 243 | GCC_WARN_UNDECLARED_SELECTOR = YES; 244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 245 | GCC_WARN_UNUSED_FUNCTION = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 248 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 249 | MTL_FAST_MATH = YES; 250 | ONLY_ACTIVE_ARCH = YES; 251 | SDKROOT = iphoneos; 252 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 253 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 254 | }; 255 | name = Debug; 256 | }; 257 | 0781B2A023B287D40029BA57 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 264 | CLANG_CXX_LIBRARY = "libc++"; 265 | CLANG_ENABLE_MODULES = YES; 266 | CLANG_ENABLE_OBJC_ARC = YES; 267 | CLANG_ENABLE_OBJC_WEAK = YES; 268 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_COMMA = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 284 | CLANG_WARN_STRICT_PROTOTYPES = YES; 285 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 286 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | COPY_PHASE_STRIP = NO; 290 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 291 | ENABLE_NS_ASSERTIONS = NO; 292 | ENABLE_STRICT_OBJC_MSGSEND = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu11; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 302 | MTL_ENABLE_DEBUG_INFO = NO; 303 | MTL_FAST_MATH = YES; 304 | SDKROOT = iphoneos; 305 | SWIFT_COMPILATION_MODE = wholemodule; 306 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 307 | VALIDATE_PRODUCT = YES; 308 | }; 309 | name = Release; 310 | }; 311 | 0781B2A223B287D40029BA57 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | CODE_SIGN_STYLE = Automatic; 316 | DEVELOPMENT_TEAM = C3VKVFB3SA; 317 | INFOPLIST_FILE = StackableTableViewExample/Supporting/Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/Frameworks", 321 | ); 322 | MARKETING_VERSION = 1.0.0; 323 | PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.stackabletableviewexample; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_VERSION = 5.0; 326 | TARGETED_DEVICE_FAMILY = 1; 327 | }; 328 | name = Debug; 329 | }; 330 | 0781B2A323B287D40029BA57 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | CODE_SIGN_STYLE = Automatic; 335 | DEVELOPMENT_TEAM = C3VKVFB3SA; 336 | INFOPLIST_FILE = StackableTableViewExample/Supporting/Info.plist; 337 | LD_RUNPATH_SEARCH_PATHS = ( 338 | "$(inherited)", 339 | "@executable_path/Frameworks", 340 | ); 341 | MARKETING_VERSION = 1.0.0; 342 | PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.stackabletableviewexample; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SWIFT_VERSION = 5.0; 345 | TARGETED_DEVICE_FAMILY = 1; 346 | }; 347 | name = Release; 348 | }; 349 | /* End XCBuildConfiguration section */ 350 | 351 | /* Begin XCConfigurationList section */ 352 | 0781B28823B287D30029BA57 /* Build configuration list for PBXProject "StackableTableViewExample" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | 0781B29F23B287D40029BA57 /* Debug */, 356 | 0781B2A023B287D40029BA57 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | 0781B2A123B287D40029BA57 /* Build configuration list for PBXNativeTarget "StackableTableViewExample" */ = { 362 | isa = XCConfigurationList; 363 | buildConfigurations = ( 364 | 0781B2A223B287D40029BA57 /* Debug */, 365 | 0781B2A323B287D40029BA57 /* Release */, 366 | ); 367 | defaultConfigurationIsVisible = 0; 368 | defaultConfigurationName = Release; 369 | }; 370 | /* End XCConfigurationList section */ 371 | 372 | /* Begin XCRemoteSwiftPackageReference section */ 373 | 0781B2B823B28CE10029BA57 /* XCRemoteSwiftPackageReference "StackableTableView" */ = { 374 | isa = XCRemoteSwiftPackageReference; 375 | repositoryURL = "https://github.com/omaralbeik/StackableTableView"; 376 | requirement = { 377 | branch = master; 378 | kind = branch; 379 | }; 380 | }; 381 | /* End XCRemoteSwiftPackageReference section */ 382 | 383 | /* Begin XCSwiftPackageProductDependency section */ 384 | 0781B2B923B28CE10029BA57 /* StackableTableView */ = { 385 | isa = XCSwiftPackageProductDependency; 386 | package = 0781B2B823B28CE10029BA57 /* XCRemoteSwiftPackageReference "StackableTableView" */; 387 | productName = StackableTableView; 388 | }; 389 | /* End XCSwiftPackageProductDependency section */ 390 | }; 391 | rootObject = 0781B28523B287D30029BA57 /* Project object */; 392 | } 393 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "StackableTableView", 6 | "repositoryURL": "https://github.com/omaralbeik/StackableTableView", 7 | "state": { 8 | "branch": "master", 9 | "revision": "464ed2b4432d3148736e9261eb9dbd728faa8d90", 10 | "version": null 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | import UIKit 21 | 22 | @UIApplicationMain 23 | class AppDelegate: UIResponder, UIApplicationDelegate { 24 | var window: UIWindow? 25 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 26 | window = UIWindow() 27 | window?.rootViewController = ViewController() 28 | window?.makeKeyAndVisible() 29 | return true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/Supporting/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/StackableTableViewExample/Supporting/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/StackableTableViewExample/Supporting/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/StackableTableViewExample/Supporting/Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | extension String { 21 | static func loremIpsum(ofLength length: Int = 445) -> String { 22 | guard length > 0 else { return "" } 23 | 24 | // https://www.lipsum.com/ 25 | let loremIpsum = """ 26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 27 | """ 28 | if loremIpsum.count > length { 29 | return String(loremIpsum[loremIpsum.startIndex.. 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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | import UIKit 21 | import StackableTableView 22 | 23 | enum DataSource: Int, CustomStringConvertible, CaseIterable { 24 | case toggleSlider 25 | case toggleLoremIpsum 26 | case toggleTags 27 | case toggleWeb 28 | 29 | var description: String { 30 | switch self { 31 | case .toggleSlider: 32 | return "Toggle Slider Header View" 33 | case .toggleLoremIpsum: 34 | return "Toggle Lorem Ipsum Header View" 35 | case .toggleTags: 36 | return "Toggle Tags Footer View" 37 | case .toggleWeb: 38 | return "Toggle Web Footer View" 39 | } 40 | } 41 | } 42 | 43 | class ViewController: UIViewController { 44 | override func loadView() { 45 | view = StackableTableView(frame: .zero, style: .grouped) 46 | } 47 | 48 | var tableView: StackableTableView { 49 | guard let stackableView = view as? StackableTableView else { 50 | fatalError() 51 | } 52 | return stackableView 53 | } 54 | 55 | lazy var sliderView = SliderView() 56 | lazy var loremIpsumView = LoremIpsumView() 57 | lazy var tagsView = TagsView() 58 | lazy var webView = WebView() 59 | 60 | override func viewDidLoad() { 61 | super.viewDidLoad() 62 | 63 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 64 | tableView.dataSource = self 65 | tableView.delegate = self 66 | 67 | webView.load(URLRequest(url: URL(string: "https://google.com")!)) 68 | 69 | tableView.headerViews = [sliderView, loremIpsumView] 70 | tableView.footerViews = [tagsView, webView] 71 | } 72 | } 73 | 74 | extension ViewController: UITableViewDataSource, UITableViewDelegate { 75 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 76 | return DataSource.allCases.count 77 | } 78 | 79 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 80 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell() 81 | cell.selectionStyle = .none 82 | cell.textLabel?.text = DataSource.allCases[indexPath.row].description 83 | return cell 84 | } 85 | 86 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 87 | guard let item = DataSource.init(rawValue: indexPath.row) else { return } 88 | 89 | switch item { 90 | case .toggleSlider: 91 | sliderView.isHidden.toggle() 92 | case .toggleLoremIpsum: 93 | loremIpsumView.isHidden.toggle() 94 | case .toggleTags: 95 | tagsView.isHidden.toggle() 96 | case .toggleWeb: 97 | webView.isHidden.toggle() 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/Views/LoremIpsumView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | import UIKit 21 | 22 | final class LoremIpsumView: UIView { 23 | 24 | private lazy var label: UILabel = { 25 | let label = UILabel() 26 | label.translatesAutoresizingMaskIntoConstraints = false 27 | label.textAlignment = .center 28 | label.numberOfLines = 0 29 | label.textColor = .white 30 | label.preferredMaxLayoutWidth = UIScreen.main.bounds.width 31 | label.text = String.loremIpsum(ofLength: 11) 32 | return label 33 | }() 34 | 35 | private lazy var button: UIButton = { 36 | let button = UIButton(type: .system) 37 | button.backgroundColor = .white 38 | button.contentEdgeInsets = .init(top: 0, left: 20, bottom: 0, right: 20) 39 | button.layer.cornerRadius = 8 40 | button.translatesAutoresizingMaskIntoConstraints = false 41 | button.setTitle("Randomize Text", for: .normal) 42 | button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) 43 | return button 44 | }() 45 | 46 | private lazy var stackView: UIStackView = { 47 | let view = UIStackView(arrangedSubviews: [label, button]) 48 | view.translatesAutoresizingMaskIntoConstraints = false 49 | view.axis = .vertical 50 | view.alignment = .center 51 | view.distribution = .fill 52 | view.spacing = 8 53 | return view 54 | }() 55 | 56 | override init(frame: CGRect) { 57 | super.init(frame: frame) 58 | 59 | backgroundColor = .systemBlue 60 | 61 | addSubview(stackView) 62 | 63 | NSLayoutConstraint.activate([ 64 | button.heightAnchor.constraint(equalToConstant: 32) 65 | ]) 66 | 67 | NSLayoutConstraint.activate([ 68 | stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), 69 | stackView.topAnchor.constraint(equalTo: topAnchor, constant: 16), 70 | stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), 71 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16) 72 | ]) 73 | } 74 | 75 | required init?(coder: NSCoder) { 76 | fatalError("init(coder:) has not been implemented") 77 | } 78 | 79 | @objc 80 | private func didTapButton() { 81 | label.text = String.loremIpsum(ofLength: .random(in: 1...445)) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/Views/SliderView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | import UIKit 21 | 22 | final class SliderView: UIView { 23 | 24 | private var minHeight: Float = 25 25 | private var maxHeight: Float = 225 26 | 27 | lazy var heightConstraint = label.heightAnchor.constraint(equalToConstant: CGFloat(minHeight)) 28 | 29 | private lazy var slider: UISlider = { 30 | let slider = UISlider() 31 | slider.minimumValue = minHeight 32 | slider.maximumValue = maxHeight 33 | slider.addTarget(self, action: #selector(didChangeSliderValue(_:)), for: .valueChanged) 34 | return slider 35 | }() 36 | 37 | private lazy var label: UILabel = { 38 | let label = UILabel() 39 | label.translatesAutoresizingMaskIntoConstraints = false 40 | label.textAlignment = .center 41 | label.textColor = .white 42 | label.text = Int(minHeight).description 43 | return label 44 | }() 45 | 46 | private lazy var stackView: UIStackView = { 47 | let view = UIStackView(arrangedSubviews: [slider, label]) 48 | view.translatesAutoresizingMaskIntoConstraints = false 49 | view.axis = .vertical 50 | view.alignment = .fill 51 | view.distribution = .fill 52 | return view 53 | }() 54 | 55 | override init(frame: CGRect) { 56 | super.init(frame: frame) 57 | 58 | backgroundColor = .black 59 | 60 | addSubview(stackView) 61 | heightConstraint.isActive = true 62 | 63 | NSLayoutConstraint.activate([ 64 | stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), 65 | stackView.topAnchor.constraint(equalTo: topAnchor, constant: 24), 66 | stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), 67 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16) 68 | ]) 69 | } 70 | 71 | required init?(coder: NSCoder) { 72 | fatalError("init(coder:) has not been implemented") 73 | } 74 | 75 | override var backgroundColor: UIColor? { 76 | didSet { 77 | slider.backgroundColor = backgroundColor 78 | } 79 | } 80 | 81 | @objc func didChangeSliderValue(_ slider: UISlider) { 82 | heightConstraint.constant = CGFloat(slider.value) 83 | label.text = Int(slider.value).description 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/Views/TagsView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | import UIKit 21 | 22 | private final class TagCell: UICollectionViewCell { 23 | 24 | private lazy var label: UILabel = { 25 | let label = UILabel() 26 | label.translatesAutoresizingMaskIntoConstraints = false 27 | label.textAlignment = .center 28 | label.textColor = .white 29 | return label 30 | }() 31 | 32 | override init(frame: CGRect) { 33 | super.init(frame: frame) 34 | 35 | backgroundColor = .darkGray 36 | layer.cornerRadius = 16 37 | 38 | addSubview(label) 39 | 40 | NSLayoutConstraint.activate([ 41 | label.centerXAnchor.constraint(equalTo: centerXAnchor), 42 | label.centerYAnchor.constraint(equalTo: centerYAnchor) 43 | ]) 44 | } 45 | 46 | required init?(coder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | 50 | override var backgroundColor: UIColor? { 51 | didSet { 52 | label.backgroundColor = backgroundColor 53 | } 54 | } 55 | 56 | func configure(for indexPath: IndexPath) { 57 | label.text = (indexPath.row + 1).description 58 | } 59 | 60 | } 61 | 62 | final class TagsView: UIView { 63 | 64 | private lazy var collectionView: UICollectionView = { 65 | let layout = UICollectionViewFlowLayout() 66 | layout.scrollDirection = .horizontal 67 | layout.itemSize = .init(width: 64, height: 32) 68 | layout.minimumLineSpacing = 16 69 | layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16) 70 | 71 | let view = UICollectionView(frame: frame, collectionViewLayout: layout) 72 | view.translatesAutoresizingMaskIntoConstraints = false 73 | view.showsHorizontalScrollIndicator = false 74 | view.register(TagCell.self, forCellWithReuseIdentifier: "cell") 75 | view.dataSource = self 76 | 77 | return view 78 | }() 79 | 80 | override init(frame: CGRect) { 81 | super.init(frame: frame) 82 | 83 | backgroundColor = .lightGray 84 | addSubview(collectionView) 85 | 86 | NSLayoutConstraint.activate([ 87 | collectionView.heightAnchor.constraint(equalToConstant: 32), 88 | collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), 89 | collectionView.topAnchor.constraint(equalTo: topAnchor, constant: 16), 90 | collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), 91 | collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16) 92 | ]) 93 | } 94 | 95 | required init?(coder: NSCoder) { 96 | fatalError("init(coder:) has not been implemented") 97 | } 98 | 99 | override var backgroundColor: UIColor? { 100 | didSet { 101 | collectionView.backgroundColor = backgroundColor 102 | } 103 | } 104 | 105 | } 106 | 107 | extension TagsView: UICollectionViewDataSource { 108 | 109 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 110 | return 50 111 | } 112 | 113 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 114 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? TagCell else { 115 | fatalError("Unable to dequeu cell") 116 | } 117 | cell.configure(for: indexPath) 118 | return cell 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Example/StackableTableViewExample/Views/WebView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | */ 19 | 20 | import WebKit 21 | 22 | final class WebView: WKWebView { 23 | 24 | override init(frame: CGRect, configuration: WKWebViewConfiguration) { 25 | super.init(frame: frame, configuration: configuration) 26 | 27 | scrollView.isScrollEnabled = false 28 | } 29 | 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | override var intrinsicContentSize: CGSize { 35 | var size = super.intrinsicContentSize 36 | size.height = 600 37 | return size 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'xcpretty' 4 | gem 'xcpretty-json-formatter' 5 | gem 'cocoapods' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.3) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.2) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | cocoapods (1.9.2) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.9.2) 19 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 20 | cocoapods-downloader (>= 1.2.2, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.4.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (>= 2.3.0, < 3.0) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.6) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.4) 33 | xcodeproj (>= 1.14.0, < 2.0) 34 | cocoapods-core (1.9.2) 35 | activesupport (>= 4.0.2, < 6) 36 | algoliasearch (~> 1.0) 37 | concurrent-ruby (~> 1.1) 38 | fuzzy_match (~> 2.0.4) 39 | nap (~> 1.0) 40 | netrc (~> 0.11) 41 | typhoeus (~> 1.0) 42 | cocoapods-deintegrate (1.0.4) 43 | cocoapods-downloader (1.3.0) 44 | cocoapods-plugins (1.0.0) 45 | nap 46 | cocoapods-search (1.0.0) 47 | cocoapods-stats (1.1.0) 48 | cocoapods-trunk (1.5.0) 49 | nap (>= 0.8, < 2.0) 50 | netrc (~> 0.11) 51 | cocoapods-try (1.2.0) 52 | colored2 (3.1.2) 53 | concurrent-ruby (1.1.6) 54 | escape (0.0.4) 55 | ethon (0.12.0) 56 | ffi (>= 1.3.0) 57 | ffi (1.12.2) 58 | fourflusher (2.3.1) 59 | fuzzy_match (2.0.4) 60 | gh_inspector (1.1.3) 61 | httpclient (2.8.3) 62 | i18n (0.9.5) 63 | concurrent-ruby (~> 1.0) 64 | json (2.3.0) 65 | minitest (5.14.1) 66 | molinillo (0.6.6) 67 | nanaimo (0.2.6) 68 | nap (1.1.0) 69 | netrc (0.11.0) 70 | rouge (2.0.7) 71 | ruby-macho (1.4.0) 72 | thread_safe (0.3.6) 73 | typhoeus (1.4.0) 74 | ethon (>= 0.9.0) 75 | tzinfo (1.2.7) 76 | thread_safe (~> 0.1) 77 | xcodeproj (1.16.0) 78 | CFPropertyList (>= 2.3.3, < 4.0) 79 | atomos (~> 0.1.3) 80 | claide (>= 1.0.2, < 2.0) 81 | colored2 (~> 3.1) 82 | nanaimo (~> 0.2.6) 83 | xcpretty (0.3.0) 84 | rouge (~> 2.0.7) 85 | xcpretty-json-formatter (0.1.1) 86 | xcpretty (~> 0.2, >= 0.0.7) 87 | 88 | PLATFORMS 89 | ruby 90 | 91 | DEPENDENCIES 92 | cocoapods 93 | xcpretty 94 | xcpretty-json-formatter 95 | 96 | BUNDLED WITH 97 | 2.1.2 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Omar Albeik 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "StackableTableView", 8 | platforms: [ 9 | .iOS(.v11) 10 | ], 11 | products: [ 12 | .library(name: "StackableTableView", targets: ["StackableTableView"]) 13 | ], 14 | dependencies: [], 15 | targets: [ 16 | .target(name: "StackableTableView", path: "Sources") 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![StackableTableView](Assets/logo.svg) 2 | 3 | ![CI](https://github.com/omaralbeik/StackableTableView/workflows/StackableTableView/badge.svg) 4 | [![codecov](https://codecov.io/gh/omaralbeik/StackableTableView/branch/master/graph/badge.svg)](https://codecov.io/gh/omaralbeik/StackableTableView) 5 | [![Platform](https://img.shields.io/cocoapods/p/StackableTableView.svg?style=flat)](https://cocoapods.org/pods/StackableTableView) 6 | [![Version](https://img.shields.io/cocoapods/v/StackableTableView.svg?style=flat)](https://cocoapods.org/pods/StackableTableView) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-Compatible-brightgreen.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | [![SPM compatible](https://img.shields.io/badge/SPM-Compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager/) 9 | [![License](https://img.shields.io/cocoapods/l/StackableTableView.svg?style=flat)](https://cocoapods.org/pods/StackableTableView) 10 | 11 | A `UITableView` subclass that enables setting an array of views for both headers and footers utilizing `UIStackView` 12 | 13 | --- 14 | 15 | ![Demo](https://github.com/omaralbeik/StackableTableView/blob/master/Assets/demo.gif) 16 | 17 | ## Requirements 18 | 19 | - iOS 11.0+ 20 | 21 | ## Installation 22 | 23 | ### Swift Package Manager (Recommended) 24 | 25 | Once you have your Swift package set up, adding `StackableTableView` as a dependency is as easy as adding it to the dependencies value of your `Package.swift`. 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/omaralbeik/StackableTableView.git", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | ### CocoaPods 34 | 35 | To integrate `StackableTableView` into your Xcode project using [CocoaPods](http://cocoapods.org), specify it in your `Podfile` 36 | 37 | ```ruby 38 | pod 'StackableTableView' 39 | ``` 40 | 41 | ### Carthage 42 | 43 | To integrate StackableTableView into your Xcode project using [Carthage](https://github.com/Carthage/Carthage), specify it in your `Cartfile` 44 | 45 | ```ruby 46 | github "omaralbeik/StackableTableView" ~> 1.0.0 47 | ``` 48 | 49 | ### Manually 50 | 51 | Add the [Sources]() folder to your Xcode project. 52 | 53 | ## License 54 | 55 | StackableTableView is available under the MIT license. See the [LICENSE](LICENSE) file for more info. 56 | -------------------------------------------------------------------------------- /Sources/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/StackableTableView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #import 24 | 25 | //! Project version number for StackableTableView. 26 | FOUNDATION_EXPORT double StackableTableViewVersionNumber; 27 | 28 | //! Project version string for StackableTableView. 29 | FOUNDATION_EXPORT const unsigned char StackableTableViewVersionString[]; 30 | 31 | // In this header, you should import all the public headers of your framework using statements like #import 32 | 33 | 34 | -------------------------------------------------------------------------------- /Sources/StackableTableView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | /// A `UITableView` subclass that enables setting an array of views for both headers and footers utilizing `UIStackView` 26 | /// 27 | /// **Warning**: Do not set `tableHeaderView` or `tableFooterView` directly, use `headerViews` or `footerViews`. 28 | /// 29 | open class StackableTableView: UITableView { 30 | /// Array of views to set as table view headers. 31 | public var headerViews: [UIView] = [] { 32 | didSet(oldViews) { 33 | headerViews.isEmpty ? removeStackView(for: .header) : attachStackView(for: .header) 34 | updateArrangedViews(remove: oldViews, add: headerViews, in: headerStackView) 35 | } 36 | } 37 | 38 | /// Array of views to set as table view footers. 39 | public var footerViews: [UIView] = [] { 40 | didSet(oldViews) { 41 | footerViews.isEmpty ? removeStackView(for: .footer) : attachStackView(for: .footer) 42 | updateArrangedViews(remove: oldViews, add: footerViews, in: footerStackView) 43 | } 44 | } 45 | 46 | /// **Warning**: Do not set `tableHeaderView` directly, add your view to `headerViews` instead. 47 | public override var tableHeaderView: UIView? { 48 | didSet { 49 | if !isTableHeaderViewUsedInternally { 50 | print("Warning: Do not set `tableHeaderView` directly, add your view to `headerViews` instead.") 51 | } 52 | } 53 | } 54 | 55 | /// **Warning**: Do not set `tableFooterView` directly, add your view to `footerViews` instead. 56 | public override var tableFooterView: UIView? { 57 | didSet { 58 | if !isTableFooterViewUsedInternally { 59 | print("Warning: Do not set `tableFooterView` directly, add your view to `footerViews` instead.") 60 | } 61 | } 62 | } 63 | 64 | /// The default implementation uses any constraints you have set to determine the size and position of any subviews. 65 | open override func layoutSubviews() { 66 | super.layoutSubviews() 67 | 68 | if let view = tableHeaderView { 69 | layoutView(view, position: .header) 70 | } 71 | 72 | if let view = tableFooterView { 73 | layoutView(view, position: .footer) 74 | } 75 | } 76 | 77 | // MARK: - Private 78 | 79 | private var isTableHeaderViewUsedInternally = false 80 | private lazy var headerStackView = createStackView() 81 | 82 | private var isTableFooterViewUsedInternally = false 83 | private lazy var footerStackView = createStackView() 84 | 85 | internal var lastPrintedMessage: String? 86 | } 87 | 88 | // MARK: - Private Helpers 89 | 90 | private extension StackableTableView { 91 | 92 | /// Used to differentiate between setting header and footer views. 93 | enum Position { 94 | case header 95 | case footer 96 | } 97 | 98 | /// Removes view for a position. 99 | /// - Parameter position: position. 100 | func removeStackView(for position: Position) { 101 | switch position { 102 | case .header: 103 | isTableHeaderViewUsedInternally = true 104 | tableHeaderView = nil 105 | isTableHeaderViewUsedInternally = false 106 | case .footer: 107 | isTableFooterViewUsedInternally = true 108 | tableFooterView = nil 109 | isTableFooterViewUsedInternally = false 110 | } 111 | } 112 | 113 | /// Attaches a view for a position. 114 | /// - Parameter position: position. 115 | func attachStackView(for position: Position) { 116 | switch position { 117 | case .header: 118 | isTableHeaderViewUsedInternally = true 119 | tableHeaderView = headerStackView 120 | isTableHeaderViewUsedInternally = false 121 | case .footer: 122 | isTableFooterViewUsedInternally = true 123 | tableFooterView = footerStackView 124 | isTableFooterViewUsedInternally = false 125 | } 126 | } 127 | 128 | /// Layout a view based on its position. 129 | /// - Parameters: 130 | /// - view: view to layout. 131 | /// - position: position. 132 | func layoutView(_ view: UIView, position: Position) { 133 | let height = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height 134 | var viewFrame = view.frame 135 | 136 | if height != viewFrame.size.height { 137 | viewFrame.size.height = height 138 | view.frame = viewFrame 139 | switch position { 140 | case .header: 141 | isTableHeaderViewUsedInternally = true 142 | tableHeaderView = view 143 | isTableHeaderViewUsedInternally = false 144 | case .footer: 145 | isTableFooterViewUsedInternally = true 146 | tableFooterView = view 147 | isTableFooterViewUsedInternally = false 148 | } 149 | } 150 | } 151 | 152 | /// Creates a plain `UIStackView`. 153 | func createStackView() -> UIStackView { 154 | let view = SuperLayoutingStackView() 155 | view.axis = .vertical 156 | view.distribution = .fill 157 | view.alignment = .fill 158 | return view 159 | } 160 | 161 | /// Update views in a `UIStackView`. 162 | /// - Parameters: 163 | /// - viewsToRemove: views to be removed from the stack view. 164 | /// - viewsToAdd: new views to be added to the stack view. 165 | /// - stackView: `UIStackView`. 166 | func updateArrangedViews(remove viewsToRemove: [UIView], add viewsToAdd: [UIView], in stackView: UIStackView) { 167 | removeArrangedSubviews(viewsToRemove, from: stackView) 168 | addArrangedSubviews(viewsToAdd, to: stackView) 169 | } 170 | 171 | /// Remove an array of views -if they exist- from a `UIStackView`. 172 | /// - Parameters: 173 | /// - views: view to remove. 174 | /// - stackView: `UIStackView`. 175 | func removeArrangedSubviews(_ views: [UIView], from stackView: UIStackView) { 176 | stackView.arrangedSubviews.forEach { view in 177 | guard views.contains(view) else { return } 178 | view.removeFromSuperview() 179 | stackView.removeArrangedSubview(view) 180 | } 181 | } 182 | 183 | /// Add an array of views to a `UIStackView`. 184 | /// - Parameters: 185 | /// - views: views to add. 186 | /// - stackView: `UIStackView`. 187 | func addArrangedSubviews(_ views: [UIView], to stackView: UIStackView) { 188 | views.forEach { view in 189 | if stackView.arrangedSubviews.contains(view) { return } 190 | stackView.addArrangedSubview(view) 191 | } 192 | } 193 | 194 | /// Prints a message to console. 195 | /// - Parameter message: message. 196 | private func print(_ message: String) { 197 | Swift.print(message) 198 | lastPrintedMessage = message 199 | } 200 | } 201 | 202 | // MARK: - Private Subclasses 203 | 204 | internal extension StackableTableView { 205 | /// A subclass of `UIStackView` that calls its superview's `setNeedsLayout` when layouted. 206 | final class SuperLayoutingStackView: UIStackView { 207 | override func layoutSubviews() { 208 | super.layoutSubviews() 209 | superview?.setNeedsLayout() 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /StackableTableView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'StackableTableView' 3 | s.version = '1.0.0' 4 | s.summary = 'UITableView with stacked views for header and footer' 5 | s.description = <<-DESC 6 | StackableTableView utilizes UITableView's tableHeaderView, tableFooterView, and UIStackView 7 | to set an array of views for headers and footers. 8 | DESC 9 | 10 | s.homepage = 'https://github.com/omaralbeik/StackableTableView' 11 | s.license = { :type => 'MIT', :file => 'LICENSE' } 12 | s.author = { 'Omar Albeik' => 'https://omaralbeik.com' } 13 | s.source = { :git => 'https://github.com/omaralbeik/StackableTableView.git', :tag => s.version.to_s } 14 | s.social_media_url = 'https://twitter.com/omaralbeik' 15 | 16 | s.ios.deployment_target = '10.0' 17 | s.swift_version = '5.1' 18 | 19 | s.source_files = "Sources/*.swift" 20 | s.frameworks = 'UIKit' 21 | end -------------------------------------------------------------------------------- /StackableTableView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 070BF3EF23B16DD500F57FC0 /* StackableTableView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 070BF3E523B16DD500F57FC0 /* StackableTableView.framework */; }; 11 | 070BF3F423B16DD500F57FC0 /* StackableTableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070BF3F323B16DD500F57FC0 /* StackableTableViewTests.swift */; }; 12 | 070BF3F623B16DD500F57FC0 /* StackableTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 070BF3E823B16DD500F57FC0 /* StackableTableView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 070BF40223B16EC000F57FC0 /* StackableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070BF3FF23B16E5E00F57FC0 /* StackableTableView.swift */; }; 14 | 07CE6D1D247C70AD008FF0B9 /* StackableTableViewTests+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CE6D1B247C7090008FF0B9 /* StackableTableViewTests+Helpers.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 070BF3F023B16DD500F57FC0 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 070BF3DC23B16DD400F57FC0 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 070BF3E423B16DD500F57FC0; 23 | remoteInfo = StackableTableView; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 070BF3E523B16DD500F57FC0 /* StackableTableView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StackableTableView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 070BF3E823B16DD500F57FC0 /* StackableTableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StackableTableView.h; sourceTree = ""; }; 30 | 070BF3E923B16DD500F57FC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 070BF3EE23B16DD500F57FC0 /* StackableTableViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StackableTableViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 070BF3F323B16DD500F57FC0 /* StackableTableViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackableTableViewTests.swift; sourceTree = ""; }; 33 | 070BF3F523B16DD500F57FC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 070BF3FF23B16E5E00F57FC0 /* StackableTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackableTableView.swift; sourceTree = ""; }; 35 | 07CE6D1B247C7090008FF0B9 /* StackableTableViewTests+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StackableTableViewTests+Helpers.swift"; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 070BF3E223B16DD500F57FC0 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | 070BF3EB23B16DD500F57FC0 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 070BF3EF23B16DD500F57FC0 /* StackableTableView.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 070BF3DB23B16DD400F57FC0 = { 58 | isa = PBXGroup; 59 | children = ( 60 | 070BF3E723B16DD500F57FC0 /* Sources */, 61 | 070BF3F223B16DD500F57FC0 /* Tests */, 62 | 070BF3E623B16DD500F57FC0 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 070BF3E623B16DD500F57FC0 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 070BF3E523B16DD500F57FC0 /* StackableTableView.framework */, 70 | 070BF3EE23B16DD500F57FC0 /* StackableTableViewTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 070BF3E723B16DD500F57FC0 /* Sources */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 070BF3FF23B16E5E00F57FC0 /* StackableTableView.swift */, 79 | 070BF3E823B16DD500F57FC0 /* StackableTableView.h */, 80 | 070BF3E923B16DD500F57FC0 /* Info.plist */, 81 | ); 82 | path = Sources; 83 | sourceTree = ""; 84 | }; 85 | 070BF3F223B16DD500F57FC0 /* Tests */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 070BF3F323B16DD500F57FC0 /* StackableTableViewTests.swift */, 89 | 07CE6D1B247C7090008FF0B9 /* StackableTableViewTests+Helpers.swift */, 90 | 070BF3F523B16DD500F57FC0 /* Info.plist */, 91 | ); 92 | path = Tests; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXHeadersBuildPhase section */ 98 | 070BF3E023B16DD500F57FC0 /* Headers */ = { 99 | isa = PBXHeadersBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | 070BF3F623B16DD500F57FC0 /* StackableTableView.h in Headers */, 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXHeadersBuildPhase section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 070BF3E423B16DD500F57FC0 /* StackableTableView */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 070BF3F923B16DD500F57FC0 /* Build configuration list for PBXNativeTarget "StackableTableView" */; 112 | buildPhases = ( 113 | 070BF3E023B16DD500F57FC0 /* Headers */, 114 | 070BF3E123B16DD500F57FC0 /* Sources */, 115 | 070BF3E223B16DD500F57FC0 /* Frameworks */, 116 | 070BF3E323B16DD500F57FC0 /* Resources */, 117 | 07CE6D1A247C5DE3008FF0B9 /* SwiftLint */, 118 | ); 119 | buildRules = ( 120 | ); 121 | dependencies = ( 122 | ); 123 | name = StackableTableView; 124 | productName = StackableTableView; 125 | productReference = 070BF3E523B16DD500F57FC0 /* StackableTableView.framework */; 126 | productType = "com.apple.product-type.framework"; 127 | }; 128 | 070BF3ED23B16DD500F57FC0 /* StackableTableViewTests */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = 070BF3FC23B16DD500F57FC0 /* Build configuration list for PBXNativeTarget "StackableTableViewTests" */; 131 | buildPhases = ( 132 | 070BF3EA23B16DD500F57FC0 /* Sources */, 133 | 070BF3EB23B16DD500F57FC0 /* Frameworks */, 134 | 070BF3EC23B16DD500F57FC0 /* Resources */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | 070BF3F123B16DD500F57FC0 /* PBXTargetDependency */, 140 | ); 141 | name = StackableTableViewTests; 142 | productName = StackableTableViewTests; 143 | productReference = 070BF3EE23B16DD500F57FC0 /* StackableTableViewTests.xctest */; 144 | productType = "com.apple.product-type.bundle.unit-test"; 145 | }; 146 | /* End PBXNativeTarget section */ 147 | 148 | /* Begin PBXProject section */ 149 | 070BF3DC23B16DD400F57FC0 /* Project object */ = { 150 | isa = PBXProject; 151 | attributes = { 152 | LastSwiftUpdateCheck = 1130; 153 | LastUpgradeCheck = 1130; 154 | ORGANIZATIONNAME = "Omar Albeik"; 155 | TargetAttributes = { 156 | 070BF3E423B16DD500F57FC0 = { 157 | CreatedOnToolsVersion = 11.3; 158 | LastSwiftMigration = 1130; 159 | }; 160 | 070BF3ED23B16DD500F57FC0 = { 161 | CreatedOnToolsVersion = 11.3; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = 070BF3DF23B16DD400F57FC0 /* Build configuration list for PBXProject "StackableTableView" */; 166 | compatibilityVersion = "Xcode 9.3"; 167 | developmentRegion = en; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | Base, 172 | ); 173 | mainGroup = 070BF3DB23B16DD400F57FC0; 174 | productRefGroup = 070BF3E623B16DD500F57FC0 /* Products */; 175 | projectDirPath = ""; 176 | projectRoot = ""; 177 | targets = ( 178 | 070BF3E423B16DD500F57FC0 /* StackableTableView */, 179 | 070BF3ED23B16DD500F57FC0 /* StackableTableViewTests */, 180 | ); 181 | }; 182 | /* End PBXProject section */ 183 | 184 | /* Begin PBXResourcesBuildPhase section */ 185 | 070BF3E323B16DD500F57FC0 /* Resources */ = { 186 | isa = PBXResourcesBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | 070BF3EC23B16DD500F57FC0 /* Resources */ = { 193 | isa = PBXResourcesBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXResourcesBuildPhase section */ 200 | 201 | /* Begin PBXShellScriptBuildPhase section */ 202 | 07CE6D1A247C5DE3008FF0B9 /* SwiftLint */ = { 203 | isa = PBXShellScriptBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | ); 207 | inputFileListPaths = ( 208 | ); 209 | inputPaths = ( 210 | ); 211 | name = SwiftLint; 212 | outputFileListPaths = ( 213 | ); 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 219 | }; 220 | /* End PBXShellScriptBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | 070BF3E123B16DD500F57FC0 /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 070BF40223B16EC000F57FC0 /* StackableTableView.swift in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | 070BF3EA23B16DD500F57FC0 /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 07CE6D1D247C70AD008FF0B9 /* StackableTableViewTests+Helpers.swift in Sources */, 236 | 070BF3F423B16DD500F57FC0 /* StackableTableViewTests.swift in Sources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXSourcesBuildPhase section */ 241 | 242 | /* Begin PBXTargetDependency section */ 243 | 070BF3F123B16DD500F57FC0 /* PBXTargetDependency */ = { 244 | isa = PBXTargetDependency; 245 | target = 070BF3E423B16DD500F57FC0 /* StackableTableView */; 246 | targetProxy = 070BF3F023B16DD500F57FC0 /* PBXContainerItemProxy */; 247 | }; 248 | /* End PBXTargetDependency section */ 249 | 250 | /* Begin XCBuildConfiguration section */ 251 | 070BF3F723B16DD500F57FC0 /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_ENABLE_OBJC_WEAK = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | COPY_PHASE_STRIP = NO; 284 | CURRENT_PROJECT_VERSION = 1; 285 | DEBUG_INFORMATION_FORMAT = dwarf; 286 | ENABLE_STRICT_OBJC_MSGSEND = YES; 287 | ENABLE_TESTABILITY = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu11; 289 | GCC_DYNAMIC_NO_PIC = NO; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_OPTIMIZATION_LEVEL = 0; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "DEBUG=1", 294 | "$(inherited)", 295 | ); 296 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 297 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 298 | GCC_WARN_UNDECLARED_SELECTOR = YES; 299 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 300 | GCC_WARN_UNUSED_FUNCTION = YES; 301 | GCC_WARN_UNUSED_VARIABLE = YES; 302 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 303 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 304 | MTL_FAST_MATH = YES; 305 | ONLY_ACTIVE_ARCH = YES; 306 | SDKROOT = iphoneos; 307 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 308 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 309 | VERSIONING_SYSTEM = "apple-generic"; 310 | VERSION_INFO_PREFIX = ""; 311 | }; 312 | name = Debug; 313 | }; 314 | 070BF3F823B16DD500F57FC0 /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ALWAYS_SEARCH_USER_PATHS = NO; 318 | CLANG_ANALYZER_NONNULL = YES; 319 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 320 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 321 | CLANG_CXX_LIBRARY = "libc++"; 322 | CLANG_ENABLE_MODULES = YES; 323 | CLANG_ENABLE_OBJC_ARC = YES; 324 | CLANG_ENABLE_OBJC_WEAK = YES; 325 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_COMMA = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INFINITE_RECURSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 341 | CLANG_WARN_STRICT_PROTOTYPES = YES; 342 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 343 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | COPY_PHASE_STRIP = NO; 347 | CURRENT_PROJECT_VERSION = 1; 348 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 349 | ENABLE_NS_ASSERTIONS = NO; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu11; 352 | GCC_NO_COMMON_BLOCKS = YES; 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 360 | MTL_ENABLE_DEBUG_INFO = NO; 361 | MTL_FAST_MATH = YES; 362 | SDKROOT = iphoneos; 363 | SWIFT_COMPILATION_MODE = wholemodule; 364 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 365 | VALIDATE_PRODUCT = YES; 366 | VERSIONING_SYSTEM = "apple-generic"; 367 | VERSION_INFO_PREFIX = ""; 368 | }; 369 | name = Release; 370 | }; 371 | 070BF3FA23B16DD500F57FC0 /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | CLANG_ENABLE_MODULES = YES; 375 | CODE_SIGN_STYLE = Automatic; 376 | DEFINES_MODULE = YES; 377 | DEVELOPMENT_TEAM = C3VKVFB3SA; 378 | DYLIB_COMPATIBILITY_VERSION = 1; 379 | DYLIB_CURRENT_VERSION = 1; 380 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 381 | INFOPLIST_FILE = Sources/Info.plist; 382 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 383 | LD_RUNPATH_SEARCH_PATHS = ( 384 | "$(inherited)", 385 | "@executable_path/Frameworks", 386 | "@loader_path/Frameworks", 387 | ); 388 | MARKETING_VERSION = 1.0.0; 389 | PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.stackabletableview; 390 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 391 | SKIP_INSTALL = YES; 392 | SUPPORTS_MACCATALYST = NO; 393 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 394 | SWIFT_VERSION = 5.0; 395 | TARGETED_DEVICE_FAMILY = "1,2"; 396 | }; 397 | name = Debug; 398 | }; 399 | 070BF3FB23B16DD500F57FC0 /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | CLANG_ENABLE_MODULES = YES; 403 | CODE_SIGN_STYLE = Automatic; 404 | DEFINES_MODULE = YES; 405 | DEVELOPMENT_TEAM = C3VKVFB3SA; 406 | DYLIB_COMPATIBILITY_VERSION = 1; 407 | DYLIB_CURRENT_VERSION = 1; 408 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 409 | INFOPLIST_FILE = Sources/Info.plist; 410 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 411 | LD_RUNPATH_SEARCH_PATHS = ( 412 | "$(inherited)", 413 | "@executable_path/Frameworks", 414 | "@loader_path/Frameworks", 415 | ); 416 | MARKETING_VERSION = 1.0.0; 417 | PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.stackabletableview; 418 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 419 | SKIP_INSTALL = YES; 420 | SUPPORTS_MACCATALYST = NO; 421 | SWIFT_VERSION = 5.0; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | }; 424 | name = Release; 425 | }; 426 | 070BF3FD23B16DD500F57FC0 /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 430 | CODE_SIGN_STYLE = Automatic; 431 | DEVELOPMENT_TEAM = C3VKVFB3SA; 432 | INFOPLIST_FILE = Tests/Info.plist; 433 | LD_RUNPATH_SEARCH_PATHS = ( 434 | "$(inherited)", 435 | "@executable_path/Frameworks", 436 | "@loader_path/Frameworks", 437 | ); 438 | PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.StackableTableViewTests; 439 | PRODUCT_NAME = "$(TARGET_NAME)"; 440 | SWIFT_VERSION = 5.0; 441 | TARGETED_DEVICE_FAMILY = "1,2"; 442 | }; 443 | name = Debug; 444 | }; 445 | 070BF3FE23B16DD500F57FC0 /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 449 | CODE_SIGN_STYLE = Automatic; 450 | DEVELOPMENT_TEAM = C3VKVFB3SA; 451 | INFOPLIST_FILE = Tests/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = ( 453 | "$(inherited)", 454 | "@executable_path/Frameworks", 455 | "@loader_path/Frameworks", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.StackableTableViewTests; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Release; 463 | }; 464 | /* End XCBuildConfiguration section */ 465 | 466 | /* Begin XCConfigurationList section */ 467 | 070BF3DF23B16DD400F57FC0 /* Build configuration list for PBXProject "StackableTableView" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 070BF3F723B16DD500F57FC0 /* Debug */, 471 | 070BF3F823B16DD500F57FC0 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | 070BF3F923B16DD500F57FC0 /* Build configuration list for PBXNativeTarget "StackableTableView" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 070BF3FA23B16DD500F57FC0 /* Debug */, 480 | 070BF3FB23B16DD500F57FC0 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 070BF3FC23B16DD500F57FC0 /* Build configuration list for PBXNativeTarget "StackableTableViewTests" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 070BF3FD23B16DD500F57FC0 /* Debug */, 489 | 070BF3FE23B16DD500F57FC0 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | /* End XCConfigurationList section */ 495 | }; 496 | rootObject = 070BF3DC23B16DD400F57FC0 /* Project object */; 497 | } 498 | -------------------------------------------------------------------------------- /StackableTableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StackableTableView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /StackableTableView.xcodeproj/xcshareddata/xcschemes/StackableTableView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Tests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/StackableTableViewTests+Helpers.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | import XCTest 24 | @testable import StackableTableView 25 | 26 | extension StackableTableViewTests { 27 | func createTableView() -> StackableTableView { 28 | let frame = CGRect(x: 0, y: 0, width: 100, height: 100) 29 | return StackableTableView(frame: frame) 30 | } 31 | 32 | class SuperView: UIView { 33 | var didCallSetNeedsLayout = false 34 | override func setNeedsLayout() { 35 | super.setNeedsLayout() 36 | didCallSetNeedsLayout = true 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/StackableTableViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Omar Albeik 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | import XCTest 24 | @testable import StackableTableView 25 | 26 | final class StackableTableViewTests: XCTestCase { 27 | func testHeaderViews() { 28 | let view = createTableView() 29 | XCTAssert(view.headerViews.isEmpty) 30 | XCTAssertNil(view.tableHeaderView) 31 | 32 | let label = UILabel() 33 | label.text = "Hello World!" 34 | 35 | view.headerViews = [label] 36 | XCTAssertEqual(view.headerViews, [label]) 37 | XCTAssertNotNil(view.tableHeaderView) 38 | 39 | let headerStackView = view.tableHeaderView as? UIStackView 40 | XCTAssertNotNil(headerStackView) 41 | XCTAssertEqual(headerStackView?.arrangedSubviews, [label]) 42 | 43 | view.headerViews = [] 44 | XCTAssert(view.headerViews.isEmpty) 45 | XCTAssertNil(view.tableHeaderView) 46 | 47 | XCTAssertNil(view.lastPrintedMessage) 48 | } 49 | 50 | func testFooterViews() { 51 | let view = createTableView() 52 | XCTAssert(view.footerViews.isEmpty) 53 | XCTAssertNil(view.tableFooterView) 54 | 55 | let label = UILabel() 56 | label.text = "Hello World!" 57 | 58 | view.footerViews = [label] 59 | XCTAssertEqual(view.footerViews, [label]) 60 | XCTAssertNotNil(view.tableFooterView) 61 | 62 | let footerStackView = view.tableFooterView as? UIStackView 63 | XCTAssertNotNil(footerStackView) 64 | XCTAssertEqual(footerStackView?.arrangedSubviews, [label]) 65 | 66 | view.footerViews = [] 67 | XCTAssert(view.footerViews.isEmpty) 68 | XCTAssertNil(view.tableFooterView) 69 | 70 | XCTAssertNil(view.lastPrintedMessage) 71 | } 72 | 73 | func testHeaderWarningMessage() { 74 | let view = createTableView() 75 | XCTAssertNil(view.lastPrintedMessage) 76 | view.tableHeaderView = UIView() 77 | XCTAssertEqual(view.lastPrintedMessage, "Warning: Do not set `tableHeaderView` directly, add your view to `headerViews` instead.") 78 | } 79 | 80 | func testFooterWarningMessage() { 81 | let view = createTableView() 82 | XCTAssertNil(view.lastPrintedMessage) 83 | view.tableFooterView = UIView() 84 | XCTAssertEqual(view.lastPrintedMessage, "Warning: Do not set `tableFooterView` directly, add your view to `footerViews` instead.") 85 | } 86 | 87 | func testLayoutSubviews() { 88 | let view = createTableView() 89 | view.layoutSubviews() 90 | XCTAssertNil(view.tableHeaderView) 91 | XCTAssertNil(view.tableFooterView) 92 | 93 | let headerLabel = UILabel() 94 | headerLabel.text = "Hello world" 95 | headerLabel.sizeToFit() 96 | 97 | let footerLabel = UILabel() 98 | footerLabel.text = "Hello world" 99 | footerLabel.sizeToFit() 100 | 101 | view.headerViews = [headerLabel] 102 | view.footerViews = [footerLabel] 103 | view.layoutSubviews() 104 | 105 | XCTAssertEqual(view.tableHeaderView?.frame.size.height, headerLabel.frame.size.height) 106 | XCTAssertEqual(view.tableFooterView?.frame.size.height, footerLabel.frame.size.height) 107 | } 108 | 109 | func testSuperLayoutingStackView() { 110 | let superview = SuperView() 111 | let subview = StackableTableView.SuperLayoutingStackView() 112 | superview.addSubview(subview) 113 | XCTAssertFalse(superview.didCallSetNeedsLayout) 114 | subview.layoutSubviews() 115 | XCTAssert(superview.didCallSetNeedsLayout) 116 | } 117 | } 118 | --------------------------------------------------------------------------------