├── .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 |
--------------------------------------------------------------------------------
/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 | 
2 |
3 | 
4 | [](https://codecov.io/gh/omaralbeik/StackableTableView)
5 | [](https://cocoapods.org/pods/StackableTableView)
6 | [](https://cocoapods.org/pods/StackableTableView)
7 | [](https://github.com/Carthage/Carthage)
8 | [](https://swift.org/package-manager/)
9 | [](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 | 
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 |
--------------------------------------------------------------------------------