├── .github
└── stale.yml
├── .gitignore
├── .swiftlint.yml
├── Classes
├── GLCollectionTableViewCell.swift
├── GLIndexedCollectionViewCell.swift
├── GLIndexedCollectionViewCell.xib
└── GLTableCollectionViewController.swift
├── GLTableCollectionView.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── WorkspaceSettings.xcsettings
├── xcshareddata
│ └── xcschemes
│ │ └── GLTableCollectionView.xcscheme
└── xcuserdata
│ └── giuliolombardo.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── GLTableCollectionView
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-60@2x-1.png
│ │ ├── Icon-60@2x.png
│ │ ├── Icon-60@3x-1.png
│ │ ├── Icon-60@3x.png
│ │ ├── Icon-76.png
│ │ ├── Icon-76@2x-1.png
│ │ ├── Icon-76@2x.png
│ │ ├── Icon-77.png
│ │ ├── Icon-AppStore.png
│ │ ├── Icon-Small-1.png
│ │ ├── Icon-Small.png
│ │ ├── Icon-Small@2x-1.png
│ │ ├── Icon-Small@2x-2.png
│ │ ├── Icon-Small@2x-3.png
│ │ ├── Icon-Small@2x.png
│ │ ├── Icon-Small@3x-1.png
│ │ ├── Icon-Small@3x.png
│ │ ├── Icon-Spotlight-40.png
│ │ ├── Icon-Spotlight-40@2x-1.png
│ │ ├── Icon-Spotlight-40@2x-2.png
│ │ ├── Icon-Spotlight-40@2x-3.png
│ │ ├── Icon-Spotlight-40@2x.png
│ │ ├── Icon-Spotlight-40@3x-1.png
│ │ ├── Icon-Spotlight-40@3x.png
│ │ ├── Icon-Spotlight-41.png
│ │ ├── Icon-iPadPro@2x-1.png
│ │ └── Icon-iPadPro@2x.png
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── Info.plist
├── GLTableCollectionViewTests
├── GLTableCollectionViewTests.swift
└── Info.plist
├── GitHub Page
└── Images
│ ├── Source
│ ├── Diagram.graffle
│ ├── Icon.pxm
│ └── Logo.pxm
│ ├── demonstration.gif
│ ├── diagram.png
│ └── logo.png
├── ISSUE_TEMPLATE.md
├── LICENSE.txt
└── README.md
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - crash
8 | # Label to use when marking an issue as stale
9 | staleLabel: standby
10 | # Comment to post when marking an issue as stale. Set to `false` to disable
11 | markComment: >
12 | This issue has been automatically marked as stale because it has not had
13 | recent activity. It will be closed if no further activity occurs. Thank you
14 | for your contributions.
15 | # Comment to post when closing a stale issue. Set to `false` to disable
16 | closeComment: false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store
32 | .DS_Store?
33 | ._*
34 | .Spotlight-V100
35 | .Trashes
36 | ehthumbs.db
37 | Thumbs.db
38 |
39 | # Build generated #
40 | ###################
41 | build/
42 | DerivedData/
43 |
44 | # Various settings #
45 | ####################
46 | *.pbxuser
47 | !default.pbxuser
48 | *.mode1v3
49 | !default.mode1v3
50 | *.mode2v3
51 | !default.mode2v3
52 | *.perspectivev3
53 | !default.perspectivev3
54 | xcuserdata/
55 |
56 | # Other #
57 | #########
58 | *.moved-aside
59 | *.xccheckout
60 | *.xcscmblueprint
61 |
62 | # Cocoapods #
63 | #############
64 | Pods
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | opt_in_rules:
2 | - anyobject_protocol
3 | - closure_spacing
4 | - contains_over_first_not_nil
5 | - convenience_type
6 | - empty_count
7 | - empty_string
8 | - empty_xctest_method
9 | - explicit_init
10 | - fallthrough
11 | - fatal_error_message
12 | - file_name
13 | - first_where
14 | - force_unwrapping
15 | - implicit_return
16 | - let_var_whitespace
17 | - literal_expression_end_indentation
18 | - operator_usage_whitespace
19 | - overridden_super_call
20 | - override_in_extension
21 | - private_action
22 | - private_outlet
23 | - prohibited_super_call
24 | - single_test_class
25 | - sorted_imports
26 | - toggle_bool
27 | - unused_import
28 | - unused_private_declaration
29 |
30 | included:
31 |
32 | force_cast: warning
33 |
34 | identifier_name:
35 | min_length: 2
36 |
37 | line_length: 160
38 |
39 | disabled_rules:
40 | - nesting
41 | - custom_rules
42 |
--------------------------------------------------------------------------------
/Classes/GLCollectionTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GLCollectionTableViewCell.swift
3 | // GLTableCollectionView
4 | //
5 | // Created by Giulio Lombardo on 24/11/16.
6 | //
7 | // MIT License
8 | //
9 | // Copyright (c) 2018 Giulio Lombardo
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy
12 | // of this software and associated documentation files (the "Software"), to
13 | // deal in the Software without restriction, including without limitation the
14 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 | // sell copies of the Software, and to permit persons to whom the Software is
16 | // furnished to do so, subject to the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included in
19 | // all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 | // IN THE SOFTWARE.
28 | //
29 |
30 | import UIKit
31 |
32 | class GLIndexedCollectionViewFlowLayout: UICollectionViewFlowLayout {
33 | fileprivate var paginatedScroll: Bool?
34 |
35 | override func awakeFromNib() {
36 | super.awakeFromNib()
37 | }
38 |
39 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
40 | guard proposedContentOffset.x > 0 else {
41 | return CGPoint(x: 0, y: 0)
42 | }
43 |
44 | // If the UICollectionView has paginatedScroll set to false there is no
45 | // need to apply any pagination logic, we will return the current
46 | // proposedContentOffset coordinates.
47 | guard paginatedScroll == true else {
48 | return CGPoint(x: proposedContentOffset.x, y: 0)
49 | }
50 |
51 | // It's not a bad idea to shield us for some strange cases where the
52 | // UICollectionView won't be there for any reason, since it comes in an
53 | // Optional flavor. If it won't be available we return the current
54 | // proposedContentOffset coordinates and exit.
55 | guard let collectionView: UICollectionView = collectionView else {
56 | return CGPoint(x: proposedContentOffset.x, y: 0)
57 | }
58 |
59 | let scannerFrame: CGRect = CGRect(x: proposedContentOffset.x,
60 | y: 0,
61 | width: collectionView.bounds.width,
62 | height: collectionView.bounds.height)
63 |
64 | // If there is no UICollectionViewLayoutAttributes for the given
65 | // scannerFrame there is no reason to calculate a paginated layout for
66 | // it, so we return the current proposedContentOffset coordinates.
67 | guard let layoutAttributes: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: scannerFrame) else {
68 | return CGPoint(x: proposedContentOffset.x, y: 0)
69 | }
70 |
71 | // To make paginated scrolling work fine this CGFloat below MUST be
72 | // equal to the value set in the insetForSectionAt method of
73 | // UICollectionView's UICollectionViewDelegate Flow Layout.
74 | let collectionViewInsets: CGFloat = 10.0
75 |
76 | // Since UICollectionViewFlowLayout's proposedContentOffset coordinates
77 | // won't take count of any UICollectionView UIEdgeInsets values we need
78 | // to fix it by adding collectionViewInsets to the .x coordinate.
79 | //
80 | // Note: This will only cover horizontal scrolling and pagination, if
81 | // you need vertical pagination replace the .x coordinate with .y and
82 | // update collectionViewInsets value with the appropriate one.
83 | let proposedXCoordWithInsets: CGFloat = proposedContentOffset.x + collectionViewInsets
84 |
85 | // We now create a variable and we assign a very high CGFloat to it (a
86 | // to cover very large UICollectionViewContentSize cases).
87 | // This var will hold the needed horizontal adjustment to make the
88 | // UICollectionView paginate scroll.
89 | var offsetCorrection: CGFloat = .greatestFiniteMagnitude
90 |
91 | // UICollectionViewLayoutAttributes may contain all sort of layout
92 | // attributes so we need to check if it belongs to a
93 | // UICollectionViewCell, otherwise logic won't work.
94 | layoutAttributes.filter { layoutAttribute -> Bool in
95 |
96 | layoutAttribute.representedElementCategory == .cell
97 | }.forEach { cellLayoutAttribute in
98 |
99 | // Now we loop through all the different layout attributes of the
100 | // UICollectionViewCells contained between the .x value of the
101 | // proposedContentOffset and collectionView's width, looking for the
102 | // cell which needs the least offsetCorrection value: it will mean
103 | // that it's the first cell on the left of the screen which will
104 | // give pagination.
105 | // To accurately calculate the offsetCorrection we will check only
106 | // the cells contained in one half of UICollectionView's width,
107 | // following the scrolling direction. The check will be done by the
108 | // if statement below. This will fix the "last cell" issue.
109 | let discardableScrollingElementsFrame: CGFloat = collectionView.contentOffset.x + (collectionView.frame.size.width / 2)
110 |
111 | if (cellLayoutAttribute.center.x <= discardableScrollingElementsFrame && velocity.x > 0) ||
112 | (cellLayoutAttribute.center.x >= discardableScrollingElementsFrame && velocity.x < 0) {
113 | return
114 | }
115 |
116 | if abs(cellLayoutAttribute.frame.origin.x - proposedXCoordWithInsets) < abs(offsetCorrection) {
117 | offsetCorrection = cellLayoutAttribute.frame.origin.x - proposedXCoordWithInsets
118 | }
119 | }
120 |
121 | return CGPoint(x: proposedContentOffset.x + offsetCorrection, y: 0)
122 | }
123 | }
124 |
125 | class GLIndexedCollectionView: UICollectionView {
126 | /// The inner-`indexPath` of the GLIndexedCollectionView.
127 | ///
128 | /// Use it to discriminate between all the possible GLIndexedCollectionViews
129 | /// inside `UICollectionView`'s `dataSource` and `delegate` methods.
130 | ///
131 | /// This should be set and updated only through GLCollectionTableViewCell's
132 | /// `setCollectionViewDataSourceDelegate` func to avoid strange behaviors.
133 | var indexPath: IndexPath!
134 | }
135 |
136 | class GLCollectionTableViewCell: UITableViewCell {
137 | /// The `UICollectionView`-inside-a-`UITableViewCell` itself.
138 | ///
139 | /// Keep the variable `public` so it would be easier to access later in the
140 | /// code, for example in UITableView's `dataSource` and
141 | /// `delegate` methods.
142 | ///
143 | /// GLIndexedCollectionView requires a `strong` ARC reference, do not assign
144 | /// a `weak` reference to it otherwise it could be released unexpectedly,
145 | /// causing a crash.
146 | var collectionView: GLIndexedCollectionView!
147 | var collectionFlowLayout: GLIndexedCollectionViewFlowLayout!
148 |
149 | /// A Boolean value that controls whether the `UICollectionViewFlowLayout`
150 | /// of the GLIndexedCollectionView will paginate scroll or not.
151 | ///
152 | /// Set [true]() to make the UICollectionView paginate scroll based on it's
153 | /// `itemSize`, set to [false]() for regular scrolling. The
154 | /// `UICollectionViewFlowLayout` will deduct the right scrolling offset
155 | /// values automatically so you should not set the `itemSize` value
156 | /// directly.
157 | ///
158 | /// Default value is `nil`, since `Bool` is `Optional`.
159 | var collectionViewPaginatedScroll: Bool?
160 |
161 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
162 | super.init(style: style, reuseIdentifier: reuseIdentifier)
163 |
164 | collectionFlowLayout = GLIndexedCollectionViewFlowLayout()
165 | collectionFlowLayout.scrollDirection = .horizontal
166 |
167 | collectionView = GLIndexedCollectionView(frame: .zero, collectionViewLayout: collectionFlowLayout)
168 | collectionView.register(UINib(nibName: "GLIndexedCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: GLIndexedCollectionViewCell.identifier)
169 | collectionView.backgroundColor = .white
170 | collectionView.showsHorizontalScrollIndicator = false
171 | collectionView.showsVerticalScrollIndicator = false
172 | collectionView.bounces = true
173 | collectionView.isDirectionalLockEnabled = true
174 | collectionView.isMultipleTouchEnabled = false
175 | collectionView.isOpaque = true
176 |
177 | contentView.addSubview(collectionView)
178 | }
179 |
180 | required init?(coder _: NSCoder) {
181 | fatalError("init(coder:) has not been implemented")
182 | }
183 |
184 | override func awakeFromNib() {
185 | super.awakeFromNib()
186 | // Initialization code
187 | }
188 |
189 | final override func layoutSubviews() {
190 | super.layoutSubviews()
191 |
192 | collectionFlowLayout.paginatedScroll = collectionViewPaginatedScroll
193 |
194 | if collectionViewPaginatedScroll == true {
195 | collectionView.isPagingEnabled = false
196 | }
197 |
198 | guard collectionView.frame != contentView.bounds else {
199 | return
200 | }
201 |
202 | collectionView.frame = contentView.bounds
203 | }
204 |
205 | override func setSelected(_ selected: Bool, animated: Bool) {
206 | super.setSelected(selected, animated: animated)
207 |
208 | // Configure the view for the selected state
209 | }
210 |
211 | /// Re-assigns `dataSource` and `delegate` classes back to the
212 | /// GLIndexedCollectionView inside GLCollectionTableViewCell.
213 | ///
214 | /// Call this `func` in your [tableView(_:willDisplay:forRowAt:)](apple-reference-documentation://hs3G9NleF7)
215 | /// method of GLTableCollectionViewController so the UITableView will
216 | /// re-assign it automatically following the regular UITableViewCells reuse
217 | /// logic.
218 | ///
219 | /// This method will also check if the re-assignment are needed or not.
220 | ///
221 | /// - Parameter dataSource: The `dataSource` class for the
222 | /// GLIndexedCollectionView in the GLCollectionTableViewCell, responsible
223 | /// for the UICollectionView's `dataSource` methods.
224 | ///
225 | /// - Parameter delegate: The `delegate class` for the
226 | /// GLIndexedCollectionView in the GLCollectionTableViewCell, responsible
227 | /// for the UICollectionView's delegation methods.
228 | ///
229 | /// - Parameter indexPath: The inner-`indexPath` of the
230 | /// GLIndexedCollectionView, it's recommended to pass the same `indexPath`
231 | /// of the UITableViewCell to the GLIndexedCollectionView so they will share
232 | /// the same `indexPath.section` making easier to understand from which
233 | /// UITableViewCell the UICollectionView will come from.
234 | final func setCollectionView(dataSource: UICollectionViewDataSource, delegate: UICollectionViewDelegate, indexPath: IndexPath) {
235 | collectionView.indexPath = indexPath
236 |
237 | if collectionView.dataSource == nil {
238 | collectionView.dataSource = dataSource
239 | }
240 |
241 | if collectionView.delegate == nil {
242 | collectionView.delegate = delegate
243 | }
244 |
245 | collectionView.reloadData()
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/Classes/GLIndexedCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GLIndexedCollectionViewCell.swift
3 | // GLTableCollectionView
4 | //
5 | // Created by Giulio Lombardo on 24/11/16.
6 | //
7 | // MIT License
8 | //
9 | // Copyright (c) 2018 Giulio Lombardo
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy
12 | // of this software and associated documentation files (the "Software"), to
13 | // deal in the Software without restriction, including without limitation the
14 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 | // sell copies of the Software, and to permit persons to whom the Software is
16 | // furnished to do so, subject to the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included in
19 | // all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 | // IN THE SOFTWARE.
28 | //
29 |
30 | import UIKit
31 |
32 | class GLIndexedCollectionViewCell: UICollectionViewCell {
33 | static let identifier: String = "collectionViewCellID"
34 |
35 | override func awakeFromNib() {
36 | super.awakeFromNib()
37 | // Initialization code
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Classes/GLIndexedCollectionViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Classes/GLTableCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GLTableCollectionViewController.swift
3 | // GLTableCollectionView
4 | //
5 | // Created by Giulio Lombardo on 24/11/16.
6 | //
7 | // MIT License
8 | //
9 | // Copyright (c) 2018 Giulio Lombardo
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy
12 | // of this software and associated documentation files (the "Software"), to
13 | // deal in the Software without restriction, including without limitation the
14 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 | // sell copies of the Software, and to permit persons to whom the Software is
16 | // furnished to do so, subject to the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included in
19 | // all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 | // IN THE SOFTWARE.
28 | //
29 |
30 | import UIKit
31 |
32 | final class GLTableCollectionViewController: UITableViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
33 | // This static string constant will be the cellIdentifier for the
34 | // UITableViewCells holding the UICollectionView, it's important to append
35 | // "_section#" to it so we can understand which cell is the one we are
36 | // looking for in the debugger. Look in UITableView's data source
37 | // cellForRowAt method for more explanations about the UITableViewCell reuse
38 | // handling.
39 | static let tableCellID: String = "tableViewCellID_section_#"
40 |
41 | let numberOfSections: Int = 20
42 | let numberOfCollectionsForRow: Int = 1
43 | let numberOfCollectionItems: Int = 20
44 |
45 | var colorsDict: [Int: [UIColor]] = [:]
46 |
47 | /// Set true to enable UICollectionViews scroll pagination
48 | var paginationEnabled: Bool = true
49 |
50 | override func viewDidLoad() {
51 | super.viewDidLoad()
52 |
53 | // Uncomment the following line to preserve selection between
54 | // presentations
55 | // self.clearsSelectionOnViewWillAppear = false
56 |
57 | // Uncomment the following line to display an Edit button in the
58 | // navigation bar for this view controller.
59 | // self.navigationItem.rightBarButtonItem = self.editButtonItem()
60 |
61 | (0 ... numberOfSections - 1).forEach { section in
62 | colorsDict[section] = randomRowColors()
63 | }
64 | }
65 |
66 | private final func randomRowColors() -> [UIColor] {
67 | let colors: [UIColor] = (0 ... numberOfCollectionItems - 1).map({ _ -> UIColor in
68 | var randomRed: CGFloat = CGFloat(arc4random_uniform(256))
69 | let randomGreen: CGFloat = CGFloat(arc4random_uniform(256))
70 | let randomBlue: CGFloat = CGFloat(arc4random_uniform(256))
71 |
72 | if randomRed == 255.0 && randomGreen == 255.0 && randomBlue == 255.0 {
73 | randomRed = CGFloat(arc4random_uniform(128))
74 | }
75 |
76 | let color: UIColor
77 |
78 | if #available(iOS 10.0, *) {
79 | if traitCollection.displayGamut == .P3 {
80 | color = UIColor(displayP3Red: randomRed / 255.0, green: randomGreen / 255.0, blue: randomBlue / 255.0, alpha: 1.0)
81 | } else {
82 | color = UIColor(red: randomRed / 255.0, green: randomGreen / 255.0, blue: randomBlue / 255.0, alpha: 1.0)
83 | }
84 | } else {
85 | color = UIColor(red: randomRed / 255.0, green: randomGreen / 255.0, blue: randomBlue / 255.0, alpha: 1.0)
86 | }
87 |
88 | return color
89 | })
90 |
91 | return colors
92 | }
93 |
94 | override func didReceiveMemoryWarning() {
95 | super.didReceiveMemoryWarning()
96 | // Dispose of any resources that can be recreated.
97 | }
98 |
99 | // MARK:
100 |
101 | override func numberOfSections(in _: UITableView) -> Int {
102 | return numberOfSections
103 | }
104 |
105 | override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
106 | return numberOfCollectionsForRow
107 | }
108 |
109 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
110 | // Instead of having a single cellIdentifier for each type of
111 | // UITableViewCells, like in a regular implementation, we have multiple
112 | // cellIDs, each related to a indexPath section. By Doing so the
113 | // UITableViewCells will still be recycled but only with
114 | // dequeueReusableCell of that section.
115 | //
116 | // For example the cellIdentifier for section 4 cells will be:
117 | //
118 | // "tableViewCellID_section_#3"
119 | //
120 | // dequeueReusableCell will only reuse previous UITableViewCells with
121 | // the same cellIdentifier instead of using any UITableViewCell as a
122 | // regular UITableView would do, this is necessary because every cell
123 | // will have a different UICollectionView with UICollectionViewCells in
124 | // it and UITableView reuse won't work as expected giving back wrong
125 | // cells.
126 |
127 | var cell: GLCollectionTableViewCell? = tableView.dequeueReusableCell(withIdentifier: GLTableCollectionViewController.tableCellID + indexPath.section.description) as? GLCollectionTableViewCell
128 |
129 | if cell == nil {
130 | cell = GLCollectionTableViewCell(style: .default, reuseIdentifier: GLTableCollectionViewController.tableCellID + indexPath.section.description)
131 |
132 | // Configure the cell...
133 | cell?.selectionStyle = .none
134 | cell?.collectionViewPaginatedScroll = paginationEnabled
135 | }
136 |
137 | return cell!
138 | }
139 |
140 | override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
141 | return "Section: " + section.description
142 | }
143 |
144 | // MARK:
145 |
146 | override func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat {
147 | return 88
148 | }
149 |
150 | override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
151 | return 28
152 | }
153 |
154 | override func tableView(_: UITableView, heightForFooterInSection _: Int) -> CGFloat {
155 | return 0.0001
156 | }
157 |
158 | override func tableView(_: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
159 | guard let cell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
160 | return
161 | }
162 |
163 | cell.setCollectionView(dataSource: self, delegate: self, indexPath: indexPath)
164 | }
165 |
166 | // MARK:
167 |
168 | func numberOfSections(in _: UICollectionView) -> Int {
169 | return 1
170 | }
171 |
172 | func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
173 | return numberOfCollectionItems
174 | }
175 |
176 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
177 | guard let cell: GLIndexedCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: GLIndexedCollectionViewCell.identifier, for: indexPath) as? GLIndexedCollectionViewCell else {
178 | fatalError("UICollectionViewCell must be of GLIndexedCollectionViewCell type")
179 | }
180 |
181 | guard let indexedCollectionView: GLIndexedCollectionView = collectionView as? GLIndexedCollectionView else {
182 | fatalError("UICollectionView must be of GLIndexedCollectionView type")
183 | }
184 |
185 | // Configure the cell...
186 | cell.backgroundColor = colorsDict[indexedCollectionView.indexPath.section]?[indexPath.row]
187 |
188 | return cell
189 | }
190 |
191 | // MARK:
192 |
193 | let collectionTopInset: CGFloat = 0
194 | let collectionBottomInset: CGFloat = 0
195 | let collectionLeftInset: CGFloat = 10
196 | let collectionRightInset: CGFloat = 10
197 |
198 | func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, insetForSectionAt _: Int) -> UIEdgeInsets {
199 | return UIEdgeInsets(top: collectionTopInset, left: collectionLeftInset, bottom: collectionBottomInset, right: collectionRightInset)
200 | }
201 |
202 | func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, sizeForItemAt _: IndexPath) -> CGSize {
203 | let tableViewCellHeight: CGFloat = tableView.rowHeight
204 | let collectionItemWidth: CGFloat = tableViewCellHeight - (collectionLeftInset + collectionRightInset)
205 | let collectionViewHeight: CGFloat = collectionItemWidth
206 |
207 | return CGSize(width: collectionItemWidth, height: collectionViewHeight)
208 | }
209 |
210 | func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, minimumLineSpacingForSectionAt _: Int) -> CGFloat {
211 | return 10
212 | }
213 |
214 | func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, minimumInteritemSpacingForSectionAt _: Int) -> CGFloat {
215 | return 0
216 | }
217 |
218 | // MARK:
219 |
220 | func collectionView(_: UICollectionView, didSelectItemAt _: IndexPath) {
221 | }
222 |
223 | /*
224 | // MARK:
225 |
226 | // In a storyboard-based application, you will often want to do a little
227 | // preparation before navigation
228 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
229 | // Get the new view controller using segue.destinationViewController.
230 | // Pass the selected object to the new view controller.
231 | }
232 | */
233 | }
234 |
--------------------------------------------------------------------------------
/GLTableCollectionView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 742CE82E1E099064000ACFB6 /* GLTableCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742CE82D1E099064000ACFB6 /* GLTableCollectionViewController.swift */; };
11 | 742CE8301E099070000ACFB6 /* GLCollectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742CE82F1E099070000ACFB6 /* GLCollectionTableViewCell.swift */; };
12 | 742CE8321E099077000ACFB6 /* GLIndexedCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742CE8311E099077000ACFB6 /* GLIndexedCollectionViewCell.swift */; };
13 | 742CE8341E09907D000ACFB6 /* GLIndexedCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 742CE8331E09907D000ACFB6 /* GLIndexedCollectionViewCell.xib */; };
14 | 747CF8FD1DE6EB010051A5FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 747CF8FC1DE6EB010051A5FF /* AppDelegate.swift */; };
15 | 747CF9021DE6EB010051A5FF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 747CF9001DE6EB010051A5FF /* Main.storyboard */; };
16 | 747CF9041DE6EB010051A5FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 747CF9031DE6EB010051A5FF /* Assets.xcassets */; };
17 | 747CF9071DE6EB010051A5FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 747CF9051DE6EB010051A5FF /* LaunchScreen.storyboard */; };
18 | 747CF9121DE6EB010051A5FF /* GLTableCollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 747CF9111DE6EB010051A5FF /* GLTableCollectionViewTests.swift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXContainerItemProxy section */
22 | 747CF90E1DE6EB010051A5FF /* PBXContainerItemProxy */ = {
23 | isa = PBXContainerItemProxy;
24 | containerPortal = 747CF8F11DE6EB010051A5FF /* Project object */;
25 | proxyType = 1;
26 | remoteGlobalIDString = 747CF8F81DE6EB010051A5FF;
27 | remoteInfo = GLTableCollectionView;
28 | };
29 | /* End PBXContainerItemProxy section */
30 |
31 | /* Begin PBXFileReference section */
32 | 742CE82D1E099064000ACFB6 /* GLTableCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GLTableCollectionViewController.swift; path = Classes/GLTableCollectionViewController.swift; sourceTree = SOURCE_ROOT; };
33 | 742CE82F1E099070000ACFB6 /* GLCollectionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GLCollectionTableViewCell.swift; path = Classes/GLCollectionTableViewCell.swift; sourceTree = SOURCE_ROOT; };
34 | 742CE8311E099077000ACFB6 /* GLIndexedCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GLIndexedCollectionViewCell.swift; path = Classes/GLIndexedCollectionViewCell.swift; sourceTree = SOURCE_ROOT; };
35 | 742CE8331E09907D000ACFB6 /* GLIndexedCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = GLIndexedCollectionViewCell.xib; path = Classes/GLIndexedCollectionViewCell.xib; sourceTree = SOURCE_ROOT; };
36 | 747CF8F91DE6EB010051A5FF /* GLTableCollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GLTableCollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; };
37 | 747CF8FC1DE6EB010051A5FF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
38 | 747CF9011DE6EB010051A5FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
39 | 747CF9031DE6EB010051A5FF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
40 | 747CF9061DE6EB010051A5FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
41 | 747CF9081DE6EB010051A5FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
42 | 747CF90D1DE6EB010051A5FF /* GLTableCollectionViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GLTableCollectionViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
43 | 747CF9111DE6EB010051A5FF /* GLTableCollectionViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLTableCollectionViewTests.swift; sourceTree = ""; };
44 | 747CF9131DE6EB010051A5FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 747CF8F61DE6EB010051A5FF /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | 747CF90A1DE6EB010051A5FF /* Frameworks */ = {
56 | isa = PBXFrameworksBuildPhase;
57 | buildActionMask = 2147483647;
58 | files = (
59 | );
60 | runOnlyForDeploymentPostprocessing = 0;
61 | };
62 | /* End PBXFrameworksBuildPhase section */
63 |
64 | /* Begin PBXGroup section */
65 | 747CF8F01DE6EB010051A5FF = {
66 | isa = PBXGroup;
67 | children = (
68 | 747CF8FB1DE6EB010051A5FF /* GLTableCollectionView */,
69 | 747CF9101DE6EB010051A5FF /* GLTableCollectionViewTests */,
70 | 747CF8FA1DE6EB010051A5FF /* Products */,
71 | );
72 | sourceTree = "";
73 | };
74 | 747CF8FA1DE6EB010051A5FF /* Products */ = {
75 | isa = PBXGroup;
76 | children = (
77 | 747CF8F91DE6EB010051A5FF /* GLTableCollectionView.app */,
78 | 747CF90D1DE6EB010051A5FF /* GLTableCollectionViewTests.xctest */,
79 | );
80 | name = Products;
81 | sourceTree = "";
82 | };
83 | 747CF8FB1DE6EB010051A5FF /* GLTableCollectionView */ = {
84 | isa = PBXGroup;
85 | children = (
86 | 747CF8FC1DE6EB010051A5FF /* AppDelegate.swift */,
87 | 742CE82D1E099064000ACFB6 /* GLTableCollectionViewController.swift */,
88 | 742CE82F1E099070000ACFB6 /* GLCollectionTableViewCell.swift */,
89 | 742CE8311E099077000ACFB6 /* GLIndexedCollectionViewCell.swift */,
90 | 742CE8331E09907D000ACFB6 /* GLIndexedCollectionViewCell.xib */,
91 | 747CF9001DE6EB010051A5FF /* Main.storyboard */,
92 | 747CF9051DE6EB010051A5FF /* LaunchScreen.storyboard */,
93 | 747CF9031DE6EB010051A5FF /* Assets.xcassets */,
94 | 747CF9081DE6EB010051A5FF /* Info.plist */,
95 | );
96 | path = GLTableCollectionView;
97 | sourceTree = "";
98 | };
99 | 747CF9101DE6EB010051A5FF /* GLTableCollectionViewTests */ = {
100 | isa = PBXGroup;
101 | children = (
102 | 747CF9111DE6EB010051A5FF /* GLTableCollectionViewTests.swift */,
103 | 747CF9131DE6EB010051A5FF /* Info.plist */,
104 | );
105 | path = GLTableCollectionViewTests;
106 | sourceTree = "";
107 | };
108 | /* End PBXGroup section */
109 |
110 | /* Begin PBXNativeTarget section */
111 | 747CF8F81DE6EB010051A5FF /* GLTableCollectionView */ = {
112 | isa = PBXNativeTarget;
113 | buildConfigurationList = 747CF9161DE6EB010051A5FF /* Build configuration list for PBXNativeTarget "GLTableCollectionView" */;
114 | buildPhases = (
115 | EF73511D1E8E9B5800F73EDB /* SwiftLint */,
116 | 747CF8F51DE6EB010051A5FF /* Sources */,
117 | 747CF8F61DE6EB010051A5FF /* Frameworks */,
118 | 747CF8F71DE6EB010051A5FF /* Resources */,
119 | );
120 | buildRules = (
121 | );
122 | dependencies = (
123 | );
124 | name = GLTableCollectionView;
125 | productName = GLTableCollectionView;
126 | productReference = 747CF8F91DE6EB010051A5FF /* GLTableCollectionView.app */;
127 | productType = "com.apple.product-type.application";
128 | };
129 | 747CF90C1DE6EB010051A5FF /* GLTableCollectionViewTests */ = {
130 | isa = PBXNativeTarget;
131 | buildConfigurationList = 747CF9191DE6EB010051A5FF /* Build configuration list for PBXNativeTarget "GLTableCollectionViewTests" */;
132 | buildPhases = (
133 | 747CF9091DE6EB010051A5FF /* Sources */,
134 | 747CF90A1DE6EB010051A5FF /* Frameworks */,
135 | 747CF90B1DE6EB010051A5FF /* Resources */,
136 | );
137 | buildRules = (
138 | );
139 | dependencies = (
140 | 747CF90F1DE6EB010051A5FF /* PBXTargetDependency */,
141 | );
142 | name = GLTableCollectionViewTests;
143 | productName = GLTableCollectionViewTests;
144 | productReference = 747CF90D1DE6EB010051A5FF /* GLTableCollectionViewTests.xctest */;
145 | productType = "com.apple.product-type.bundle.unit-test";
146 | };
147 | /* End PBXNativeTarget section */
148 |
149 | /* Begin PBXProject section */
150 | 747CF8F11DE6EB010051A5FF /* Project object */ = {
151 | isa = PBXProject;
152 | attributes = {
153 | CLASSPREFIX = GL;
154 | LastSwiftUpdateCheck = 0800;
155 | LastUpgradeCheck = 0930;
156 | ORGANIZATIONNAME = "Giulio Lombardo";
157 | TargetAttributes = {
158 | 747CF8F81DE6EB010051A5FF = {
159 | CreatedOnToolsVersion = 8.0;
160 | LastSwiftMigration = 1010;
161 | ProvisioningStyle = Manual;
162 | };
163 | 747CF90C1DE6EB010051A5FF = {
164 | CreatedOnToolsVersion = 8.0;
165 | LastSwiftMigration = 1010;
166 | ProvisioningStyle = Manual;
167 | TestTargetID = 747CF8F81DE6EB010051A5FF;
168 | };
169 | };
170 | };
171 | buildConfigurationList = 747CF8F41DE6EB010051A5FF /* Build configuration list for PBXProject "GLTableCollectionView" */;
172 | compatibilityVersion = "Xcode 8.0";
173 | developmentRegion = English;
174 | hasScannedForEncodings = 0;
175 | knownRegions = (
176 | en,
177 | Base,
178 | );
179 | mainGroup = 747CF8F01DE6EB010051A5FF;
180 | productRefGroup = 747CF8FA1DE6EB010051A5FF /* Products */;
181 | projectDirPath = "";
182 | projectRoot = "";
183 | targets = (
184 | 747CF8F81DE6EB010051A5FF /* GLTableCollectionView */,
185 | 747CF90C1DE6EB010051A5FF /* GLTableCollectionViewTests */,
186 | );
187 | };
188 | /* End PBXProject section */
189 |
190 | /* Begin PBXResourcesBuildPhase section */
191 | 747CF8F71DE6EB010051A5FF /* Resources */ = {
192 | isa = PBXResourcesBuildPhase;
193 | buildActionMask = 2147483647;
194 | files = (
195 | 747CF9071DE6EB010051A5FF /* LaunchScreen.storyboard in Resources */,
196 | 747CF9041DE6EB010051A5FF /* Assets.xcassets in Resources */,
197 | 742CE8341E09907D000ACFB6 /* GLIndexedCollectionViewCell.xib in Resources */,
198 | 747CF9021DE6EB010051A5FF /* Main.storyboard in Resources */,
199 | );
200 | runOnlyForDeploymentPostprocessing = 0;
201 | };
202 | 747CF90B1DE6EB010051A5FF /* Resources */ = {
203 | isa = PBXResourcesBuildPhase;
204 | buildActionMask = 2147483647;
205 | files = (
206 | );
207 | runOnlyForDeploymentPostprocessing = 0;
208 | };
209 | /* End PBXResourcesBuildPhase section */
210 |
211 | /* Begin PBXShellScriptBuildPhase section */
212 | EF73511D1E8E9B5800F73EDB /* SwiftLint */ = {
213 | isa = PBXShellScriptBuildPhase;
214 | buildActionMask = 2147483647;
215 | files = (
216 | );
217 | inputPaths = (
218 | );
219 | name = SwiftLint;
220 | outputPaths = (
221 | );
222 | runOnlyForDeploymentPostprocessing = 0;
223 | shellPath = /bin/sh;
224 | 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";
225 | };
226 | /* End PBXShellScriptBuildPhase section */
227 |
228 | /* Begin PBXSourcesBuildPhase section */
229 | 747CF8F51DE6EB010051A5FF /* Sources */ = {
230 | isa = PBXSourcesBuildPhase;
231 | buildActionMask = 2147483647;
232 | files = (
233 | 742CE8301E099070000ACFB6 /* GLCollectionTableViewCell.swift in Sources */,
234 | 742CE82E1E099064000ACFB6 /* GLTableCollectionViewController.swift in Sources */,
235 | 747CF8FD1DE6EB010051A5FF /* AppDelegate.swift in Sources */,
236 | 742CE8321E099077000ACFB6 /* GLIndexedCollectionViewCell.swift in Sources */,
237 | );
238 | runOnlyForDeploymentPostprocessing = 0;
239 | };
240 | 747CF9091DE6EB010051A5FF /* Sources */ = {
241 | isa = PBXSourcesBuildPhase;
242 | buildActionMask = 2147483647;
243 | files = (
244 | 747CF9121DE6EB010051A5FF /* GLTableCollectionViewTests.swift in Sources */,
245 | );
246 | runOnlyForDeploymentPostprocessing = 0;
247 | };
248 | /* End PBXSourcesBuildPhase section */
249 |
250 | /* Begin PBXTargetDependency section */
251 | 747CF90F1DE6EB010051A5FF /* PBXTargetDependency */ = {
252 | isa = PBXTargetDependency;
253 | target = 747CF8F81DE6EB010051A5FF /* GLTableCollectionView */;
254 | targetProxy = 747CF90E1DE6EB010051A5FF /* PBXContainerItemProxy */;
255 | };
256 | /* End PBXTargetDependency section */
257 |
258 | /* Begin PBXVariantGroup section */
259 | 747CF9001DE6EB010051A5FF /* Main.storyboard */ = {
260 | isa = PBXVariantGroup;
261 | children = (
262 | 747CF9011DE6EB010051A5FF /* Base */,
263 | );
264 | name = Main.storyboard;
265 | sourceTree = "";
266 | };
267 | 747CF9051DE6EB010051A5FF /* LaunchScreen.storyboard */ = {
268 | isa = PBXVariantGroup;
269 | children = (
270 | 747CF9061DE6EB010051A5FF /* Base */,
271 | );
272 | name = LaunchScreen.storyboard;
273 | sourceTree = "";
274 | };
275 | /* End PBXVariantGroup section */
276 |
277 | /* Begin XCBuildConfiguration section */
278 | 747CF9141DE6EB010051A5FF /* Debug */ = {
279 | isa = XCBuildConfiguration;
280 | buildSettings = {
281 | ALWAYS_SEARCH_USER_PATHS = NO;
282 | CLANG_ANALYZER_NONNULL = YES;
283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
284 | CLANG_CXX_LIBRARY = "libc++";
285 | CLANG_ENABLE_MODULES = YES;
286 | CLANG_ENABLE_OBJC_ARC = YES;
287 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
288 | CLANG_WARN_BOOL_CONVERSION = YES;
289 | CLANG_WARN_COMMA = YES;
290 | CLANG_WARN_CONSTANT_CONVERSION = YES;
291 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
293 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
294 | CLANG_WARN_EMPTY_BODY = YES;
295 | CLANG_WARN_ENUM_CONVERSION = YES;
296 | CLANG_WARN_INFINITE_RECURSION = YES;
297 | CLANG_WARN_INT_CONVERSION = YES;
298 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
299 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
300 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
301 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
302 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
303 | CLANG_WARN_STRICT_PROTOTYPES = YES;
304 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
305 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
306 | CLANG_WARN_UNREACHABLE_CODE = YES;
307 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
308 | CODE_SIGN_IDENTITY = "";
309 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
310 | COPY_PHASE_STRIP = NO;
311 | DEBUG_INFORMATION_FORMAT = dwarf;
312 | ENABLE_STRICT_OBJC_MSGSEND = YES;
313 | ENABLE_TESTABILITY = YES;
314 | GCC_C_LANGUAGE_STANDARD = gnu99;
315 | GCC_DYNAMIC_NO_PIC = NO;
316 | GCC_NO_COMMON_BLOCKS = YES;
317 | GCC_OPTIMIZATION_LEVEL = 0;
318 | GCC_PREPROCESSOR_DEFINITIONS = (
319 | "DEBUG=1",
320 | "$(inherited)",
321 | );
322 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
323 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
324 | GCC_WARN_UNDECLARED_SELECTOR = YES;
325 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
326 | GCC_WARN_UNUSED_FUNCTION = YES;
327 | GCC_WARN_UNUSED_VARIABLE = YES;
328 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
329 | MTL_ENABLE_DEBUG_INFO = YES;
330 | ONLY_ACTIVE_ARCH = YES;
331 | SDKROOT = iphoneos;
332 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
333 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
334 | SWIFT_VERSION = 4.0;
335 | TARGETED_DEVICE_FAMILY = "1,2";
336 | };
337 | name = Debug;
338 | };
339 | 747CF9151DE6EB010051A5FF /* Release */ = {
340 | isa = XCBuildConfiguration;
341 | buildSettings = {
342 | ALWAYS_SEARCH_USER_PATHS = NO;
343 | CLANG_ANALYZER_NONNULL = YES;
344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
345 | CLANG_CXX_LIBRARY = "libc++";
346 | CLANG_ENABLE_MODULES = YES;
347 | CLANG_ENABLE_OBJC_ARC = YES;
348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
349 | CLANG_WARN_BOOL_CONVERSION = YES;
350 | CLANG_WARN_COMMA = YES;
351 | CLANG_WARN_CONSTANT_CONVERSION = YES;
352 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
355 | CLANG_WARN_EMPTY_BODY = YES;
356 | CLANG_WARN_ENUM_CONVERSION = YES;
357 | CLANG_WARN_INFINITE_RECURSION = YES;
358 | CLANG_WARN_INT_CONVERSION = YES;
359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
363 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
364 | CLANG_WARN_STRICT_PROTOTYPES = YES;
365 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
366 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
367 | CLANG_WARN_UNREACHABLE_CODE = YES;
368 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
369 | CODE_SIGN_IDENTITY = "";
370 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
371 | COPY_PHASE_STRIP = NO;
372 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
373 | ENABLE_NS_ASSERTIONS = NO;
374 | ENABLE_STRICT_OBJC_MSGSEND = YES;
375 | GCC_C_LANGUAGE_STANDARD = gnu99;
376 | GCC_NO_COMMON_BLOCKS = YES;
377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
379 | GCC_WARN_UNDECLARED_SELECTOR = YES;
380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
381 | GCC_WARN_UNUSED_FUNCTION = YES;
382 | GCC_WARN_UNUSED_VARIABLE = YES;
383 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
384 | MTL_ENABLE_DEBUG_INFO = NO;
385 | SDKROOT = iphoneos;
386 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
387 | SWIFT_VERSION = 4.0;
388 | TARGETED_DEVICE_FAMILY = "1,2";
389 | VALIDATE_PRODUCT = YES;
390 | };
391 | name = Release;
392 | };
393 | 747CF9171DE6EB010051A5FF /* Debug */ = {
394 | isa = XCBuildConfiguration;
395 | buildSettings = {
396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
397 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
398 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
399 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
400 | CLANG_ENABLE_OBJC_WEAK = YES;
401 | CLANG_WARN_ASSIGN_ENUM = YES;
402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
403 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
404 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
405 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
406 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
407 | CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
408 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
409 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
410 | CODE_SIGN_IDENTITY = "";
411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
412 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
413 | DEVELOPMENT_TEAM = "";
414 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
415 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
416 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
417 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
418 | GCC_WARN_SIGN_COMPARE = YES;
419 | GCC_WARN_UNKNOWN_PRAGMAS = YES;
420 | GCC_WARN_UNUSED_LABEL = YES;
421 | GCC_WARN_UNUSED_PARAMETER = YES;
422 | INFOPLIST_FILE = GLTableCollectionView/Info.plist;
423 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
424 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
425 | PRODUCT_BUNDLE_IDENTIFIER = giuliolombardo.GLTableCollectionView;
426 | PRODUCT_NAME = "$(TARGET_NAME)";
427 | PROVISIONING_PROFILE = "";
428 | PROVISIONING_PROFILE_SPECIFIER = "";
429 | SWIFT_VERSION = 4.2;
430 | };
431 | name = Debug;
432 | };
433 | 747CF9181DE6EB010051A5FF /* Release */ = {
434 | isa = XCBuildConfiguration;
435 | buildSettings = {
436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
437 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
438 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
439 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
440 | CLANG_ENABLE_OBJC_WEAK = YES;
441 | CLANG_WARN_ASSIGN_ENUM = YES;
442 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
443 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
444 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
445 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
446 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
447 | CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
448 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
449 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
450 | CODE_SIGN_IDENTITY = "";
451 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
452 | DEVELOPMENT_TEAM = "";
453 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
454 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
455 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
456 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
457 | GCC_WARN_SIGN_COMPARE = YES;
458 | GCC_WARN_UNKNOWN_PRAGMAS = YES;
459 | GCC_WARN_UNUSED_LABEL = YES;
460 | GCC_WARN_UNUSED_PARAMETER = YES;
461 | INFOPLIST_FILE = GLTableCollectionView/Info.plist;
462 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
464 | PRODUCT_BUNDLE_IDENTIFIER = giuliolombardo.GLTableCollectionView;
465 | PRODUCT_NAME = "$(TARGET_NAME)";
466 | SWIFT_VERSION = 4.2;
467 | };
468 | name = Release;
469 | };
470 | 747CF91A1DE6EB010051A5FF /* Debug */ = {
471 | isa = XCBuildConfiguration;
472 | buildSettings = {
473 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
474 | BUNDLE_LOADER = "$(TEST_HOST)";
475 | CODE_SIGN_IDENTITY = "";
476 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
477 | DEVELOPMENT_TEAM = "";
478 | INFOPLIST_FILE = GLTableCollectionViewTests/Info.plist;
479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
480 | PRODUCT_BUNDLE_IDENTIFIER = giuliolombardo.GLTableCollectionViewTests;
481 | PRODUCT_NAME = "$(TARGET_NAME)";
482 | SWIFT_VERSION = 4.2;
483 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GLTableCollectionView.app/GLTableCollectionView";
484 | };
485 | name = Debug;
486 | };
487 | 747CF91B1DE6EB010051A5FF /* Release */ = {
488 | isa = XCBuildConfiguration;
489 | buildSettings = {
490 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
491 | BUNDLE_LOADER = "$(TEST_HOST)";
492 | CODE_SIGN_IDENTITY = "";
493 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
494 | DEVELOPMENT_TEAM = "";
495 | INFOPLIST_FILE = GLTableCollectionViewTests/Info.plist;
496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
497 | PRODUCT_BUNDLE_IDENTIFIER = giuliolombardo.GLTableCollectionViewTests;
498 | PRODUCT_NAME = "$(TARGET_NAME)";
499 | SWIFT_VERSION = 4.2;
500 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GLTableCollectionView.app/GLTableCollectionView";
501 | };
502 | name = Release;
503 | };
504 | /* End XCBuildConfiguration section */
505 |
506 | /* Begin XCConfigurationList section */
507 | 747CF8F41DE6EB010051A5FF /* Build configuration list for PBXProject "GLTableCollectionView" */ = {
508 | isa = XCConfigurationList;
509 | buildConfigurations = (
510 | 747CF9141DE6EB010051A5FF /* Debug */,
511 | 747CF9151DE6EB010051A5FF /* Release */,
512 | );
513 | defaultConfigurationIsVisible = 0;
514 | defaultConfigurationName = Release;
515 | };
516 | 747CF9161DE6EB010051A5FF /* Build configuration list for PBXNativeTarget "GLTableCollectionView" */ = {
517 | isa = XCConfigurationList;
518 | buildConfigurations = (
519 | 747CF9171DE6EB010051A5FF /* Debug */,
520 | 747CF9181DE6EB010051A5FF /* Release */,
521 | );
522 | defaultConfigurationIsVisible = 0;
523 | defaultConfigurationName = Release;
524 | };
525 | 747CF9191DE6EB010051A5FF /* Build configuration list for PBXNativeTarget "GLTableCollectionViewTests" */ = {
526 | isa = XCConfigurationList;
527 | buildConfigurations = (
528 | 747CF91A1DE6EB010051A5FF /* Debug */,
529 | 747CF91B1DE6EB010051A5FF /* Release */,
530 | );
531 | defaultConfigurationIsVisible = 0;
532 | defaultConfigurationName = Release;
533 | };
534 | /* End XCConfigurationList section */
535 | };
536 | rootObject = 747CF8F11DE6EB010051A5FF /* Project object */;
537 | }
538 |
--------------------------------------------------------------------------------
/GLTableCollectionView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GLTableCollectionView.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/GLTableCollectionView.xcodeproj/xcshareddata/xcschemes/GLTableCollectionView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
97 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/GLTableCollectionView.xcodeproj/xcuserdata/giuliolombardo.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | GLTableCollectionView.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 747CF8F81DE6EB010051A5FF
16 |
17 | primary
18 |
19 |
20 | 747CF90C1DE6EB010051A5FF
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/GLTableCollectionView/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // GLTableCollectionView
4 | //
5 | // Created by Giulio Lombardo on 24/11/16.
6 | //
7 | // MIT License
8 | //
9 | // Copyright (c) 2018 Giulio Lombardo
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy
12 | // of this software and associated documentation files (the "Software"), to
13 | // deal in the Software without restriction, including without limitation the
14 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 | // sell copies of the Software, and to permit persons to whom the Software is
16 | // furnished to do so, subject to the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included in
19 | // all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 | // IN THE SOFTWARE.
28 | //
29 |
30 | import UIKit
31 |
32 | @UIApplicationMain
33 | final class AppDelegate: UIResponder, UIApplicationDelegate {
34 | var window: UIWindow?
35 |
36 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
37 | // Override point for customization after application launch.
38 | return true
39 | }
40 |
41 | func applicationWillResignActive(_: UIApplication) {
42 | // Sent when the application is about to move from active to inactive
43 | // state. This can occur for certain types of temporary interruptions
44 | // (such as an incoming phone call or SMS message) or when the user
45 | // quits the application and it begins the transition to the background
46 | // state. Use this method to pause ongoing tasks, disable timers, and
47 | // invalidate graphics rendering callbacks. Games should use this method
48 | // to pause the game.
49 | }
50 |
51 | func applicationDidEnterBackground(_: UIApplication) {
52 | // Use this method to release shared resources, save user data,
53 | // invalidate timers, and store enough application state information to
54 | // restore your application to its current state in case it is
55 | // terminated later. If your application supports background execution,
56 | // this method is called instead of applicationWillTerminate: when the
57 | // user quits.
58 | }
59 |
60 | func applicationWillEnterForeground(_: UIApplication) {
61 | // Called as part of the transition from the background to the active
62 | // state; here you can undo many of the changes made on entering the
63 | // background.
64 | }
65 |
66 | func applicationDidBecomeActive(_: UIApplication) {
67 | // Restart any tasks that were paused (or not yet started) while the
68 | // application was inactive. If the application was previously in the
69 | // background, optionally refresh the user interface.
70 | }
71 |
72 | func applicationWillTerminate(_: UIApplication) {
73 | // Called when the application is about to terminate. Save data if
74 | // appropriate. See also applicationDidEnterBackground:.
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "display-gamut" : "sRGB",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "display-gamut" : "display-P3",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "20x20",
17 | "idiom" : "iphone",
18 | "display-gamut" : "sRGB",
19 | "scale" : "3x"
20 | },
21 | {
22 | "size" : "20x20",
23 | "idiom" : "iphone",
24 | "display-gamut" : "display-P3",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-Small@2x.png",
31 | "display-gamut" : "sRGB",
32 | "scale" : "2x"
33 | },
34 | {
35 | "size" : "29x29",
36 | "idiom" : "iphone",
37 | "filename" : "Icon-Small@2x-2.png",
38 | "display-gamut" : "display-P3",
39 | "scale" : "2x"
40 | },
41 | {
42 | "size" : "29x29",
43 | "idiom" : "iphone",
44 | "filename" : "Icon-Small@3x.png",
45 | "display-gamut" : "sRGB",
46 | "scale" : "3x"
47 | },
48 | {
49 | "size" : "29x29",
50 | "idiom" : "iphone",
51 | "filename" : "Icon-Small@3x-1.png",
52 | "display-gamut" : "display-P3",
53 | "scale" : "3x"
54 | },
55 | {
56 | "size" : "40x40",
57 | "idiom" : "iphone",
58 | "filename" : "Icon-Spotlight-40@2x-1.png",
59 | "display-gamut" : "sRGB",
60 | "scale" : "2x"
61 | },
62 | {
63 | "size" : "40x40",
64 | "idiom" : "iphone",
65 | "filename" : "Icon-Spotlight-40@2x-2.png",
66 | "display-gamut" : "display-P3",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "40x40",
71 | "idiom" : "iphone",
72 | "filename" : "Icon-Spotlight-40@3x.png",
73 | "display-gamut" : "sRGB",
74 | "scale" : "3x"
75 | },
76 | {
77 | "size" : "40x40",
78 | "idiom" : "iphone",
79 | "filename" : "Icon-Spotlight-40@3x-1.png",
80 | "display-gamut" : "display-P3",
81 | "scale" : "3x"
82 | },
83 | {
84 | "size" : "60x60",
85 | "idiom" : "iphone",
86 | "filename" : "Icon-60@2x.png",
87 | "display-gamut" : "sRGB",
88 | "scale" : "2x"
89 | },
90 | {
91 | "size" : "60x60",
92 | "idiom" : "iphone",
93 | "filename" : "Icon-60@2x-1.png",
94 | "display-gamut" : "display-P3",
95 | "scale" : "2x"
96 | },
97 | {
98 | "size" : "60x60",
99 | "idiom" : "iphone",
100 | "filename" : "Icon-60@3x.png",
101 | "display-gamut" : "sRGB",
102 | "scale" : "3x"
103 | },
104 | {
105 | "size" : "60x60",
106 | "idiom" : "iphone",
107 | "filename" : "Icon-60@3x-1.png",
108 | "display-gamut" : "display-P3",
109 | "scale" : "3x"
110 | },
111 | {
112 | "size" : "20x20",
113 | "idiom" : "ipad",
114 | "display-gamut" : "sRGB",
115 | "scale" : "1x"
116 | },
117 | {
118 | "size" : "20x20",
119 | "idiom" : "ipad",
120 | "display-gamut" : "display-P3",
121 | "scale" : "1x"
122 | },
123 | {
124 | "size" : "20x20",
125 | "idiom" : "ipad",
126 | "display-gamut" : "sRGB",
127 | "scale" : "2x"
128 | },
129 | {
130 | "size" : "20x20",
131 | "idiom" : "ipad",
132 | "display-gamut" : "display-P3",
133 | "scale" : "2x"
134 | },
135 | {
136 | "size" : "29x29",
137 | "idiom" : "ipad",
138 | "filename" : "Icon-Small.png",
139 | "display-gamut" : "sRGB",
140 | "scale" : "1x"
141 | },
142 | {
143 | "size" : "29x29",
144 | "idiom" : "ipad",
145 | "filename" : "Icon-Small-1.png",
146 | "display-gamut" : "display-P3",
147 | "scale" : "1x"
148 | },
149 | {
150 | "size" : "29x29",
151 | "idiom" : "ipad",
152 | "filename" : "Icon-Small@2x-1.png",
153 | "display-gamut" : "sRGB",
154 | "scale" : "2x"
155 | },
156 | {
157 | "size" : "29x29",
158 | "idiom" : "ipad",
159 | "filename" : "Icon-Small@2x-3.png",
160 | "display-gamut" : "display-P3",
161 | "scale" : "2x"
162 | },
163 | {
164 | "size" : "40x40",
165 | "idiom" : "ipad",
166 | "filename" : "Icon-Spotlight-40.png",
167 | "display-gamut" : "sRGB",
168 | "scale" : "1x"
169 | },
170 | {
171 | "size" : "40x40",
172 | "idiom" : "ipad",
173 | "filename" : "Icon-Spotlight-41.png",
174 | "display-gamut" : "display-P3",
175 | "scale" : "1x"
176 | },
177 | {
178 | "size" : "40x40",
179 | "idiom" : "ipad",
180 | "filename" : "Icon-Spotlight-40@2x.png",
181 | "display-gamut" : "sRGB",
182 | "scale" : "2x"
183 | },
184 | {
185 | "size" : "40x40",
186 | "idiom" : "ipad",
187 | "filename" : "Icon-Spotlight-40@2x-3.png",
188 | "display-gamut" : "display-P3",
189 | "scale" : "2x"
190 | },
191 | {
192 | "size" : "76x76",
193 | "idiom" : "ipad",
194 | "filename" : "Icon-76.png",
195 | "display-gamut" : "sRGB",
196 | "scale" : "1x"
197 | },
198 | {
199 | "size" : "76x76",
200 | "idiom" : "ipad",
201 | "filename" : "Icon-77.png",
202 | "display-gamut" : "display-P3",
203 | "scale" : "1x"
204 | },
205 | {
206 | "size" : "76x76",
207 | "idiom" : "ipad",
208 | "filename" : "Icon-76@2x.png",
209 | "display-gamut" : "sRGB",
210 | "scale" : "2x"
211 | },
212 | {
213 | "size" : "76x76",
214 | "idiom" : "ipad",
215 | "filename" : "Icon-76@2x-1.png",
216 | "display-gamut" : "display-P3",
217 | "scale" : "2x"
218 | },
219 | {
220 | "size" : "83.5x83.5",
221 | "idiom" : "ipad",
222 | "filename" : "Icon-iPadPro@2x.png",
223 | "display-gamut" : "sRGB",
224 | "scale" : "2x"
225 | },
226 | {
227 | "size" : "83.5x83.5",
228 | "idiom" : "ipad",
229 | "filename" : "Icon-iPadPro@2x-1.png",
230 | "display-gamut" : "display-P3",
231 | "scale" : "2x"
232 | },
233 | {
234 | "size" : "1024x1024",
235 | "idiom" : "ios-marketing",
236 | "filename" : "Icon-AppStore.png",
237 | "scale" : "1x"
238 | }
239 | ],
240 | "info" : {
241 | "version" : 1,
242 | "author" : "xcode"
243 | }
244 | }
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@2x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@3x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@3x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-76@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-76@2x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-77.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-77.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-AppStore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-AppStore.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-2.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-3.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-2.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-3.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x-1.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GLTableCollectionView/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png
--------------------------------------------------------------------------------
/GLTableCollectionView/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/GLTableCollectionView/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/GLTableCollectionView/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.81
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationPortraitUpsideDown
35 | UIInterfaceOrientationLandscapeLeft
36 | UIInterfaceOrientationLandscapeRight
37 |
38 | UISupportedInterfaceOrientations~ipad
39 |
40 | UIInterfaceOrientationPortrait
41 | UIInterfaceOrientationPortraitUpsideDown
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/GLTableCollectionViewTests/GLTableCollectionViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GLTableCollectionViewTests.swift
3 | // GLTableCollectionViewTests
4 | //
5 | // Created by Giulio Lombardo on 24/11/16.
6 | //
7 | // MIT License
8 | //
9 | // Copyright (c) 2018 Giulio Lombardo
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy
12 | // of this software and associated documentation files (the "Software"), to
13 | // deal in the Software without restriction, including without limitation the
14 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 | // sell copies of the Software, and to permit persons to whom the Software is
16 | // furnished to do so, subject to the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included in
19 | // all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 | // IN THE SOFTWARE.
28 | //
29 |
30 | @testable import GLTableCollectionView
31 | import XCTest
32 |
33 | final class GLTableCollectionViewTests: XCTestCase {
34 | private var tableCollectionView: GLTableCollectionViewController!
35 | private var visibleCells: [UITableViewCell]!
36 |
37 | override func setUp() {
38 | super.setUp()
39 | // Put setup code here. This method is called before the invocation of
40 | // each test method in the class.
41 |
42 | let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
43 | tableCollectionView = storyboard.instantiateViewController(withIdentifier: "tableCollectionView") as! GLTableCollectionViewController
44 | tableCollectionView.beginAppearanceTransition(true, animated: false)
45 |
46 | visibleCells = tableCollectionView.tableView.visibleCells
47 | }
48 |
49 | // MARK:
50 |
51 | func testInstanceVariables() {
52 | XCTAssertGreaterThan(tableCollectionView.numberOfSections, 0,
53 | "numberOfSections was: \(tableCollectionView.numberOfSections)\nUITableView must have at least one section")
54 |
55 | XCTAssertGreaterThan(tableCollectionView.numberOfCollectionsForRow, 0,
56 | "numberOfCollectionsForRow was: \(tableCollectionView.numberOfCollectionsForRow)\nThere must be at least a GLIndexedCollectionView per UITableViewCell")
57 |
58 | XCTAssertGreaterThan(tableCollectionView.numberOfCollectionItems, 0,
59 | "numberOfCollectionItems was: \(tableCollectionView.numberOfCollectionItems)\nThere must be at least one GLIndexedCollectionViewCell")
60 |
61 | XCTAssertNotEqual(GLTableCollectionViewController.tableCellID, "",
62 | "tableCellID was: \(GLTableCollectionViewController.tableCellID)\nUITableViewCell's cellIdentifier must not be empty")
63 |
64 | XCTAssertTrue(GLTableCollectionViewController.tableCellID.hasSuffix("_section_#"),
65 | "tableCellID was: \(GLTableCollectionViewController.tableCellID)\nUITableViewCell's cellIdentifier must end with section number suffix")
66 |
67 | XCTAssertTrue((GLTableCollectionViewController.tableCellID.components(separatedBy: "#").count - 1) == 1,
68 | "tableCellID was: \(GLTableCollectionViewController.tableCellID)\nUITableViewCell's cellIdentifier must contain only one # in it")
69 | }
70 |
71 | func testRandomColorsGeneration() {
72 | let colorsDictionary: [Int: [UIColor]] = tableCollectionView.colorsDict
73 |
74 | XCTAssertNotNil(colorsDictionary, "Colors dictionary is nil")
75 | XCTAssertNotEqual(colorsDictionary.count, 0, "Colors dictionary is empty")
76 | XCTAssertEqual(colorsDictionary.count, tableCollectionView.numberOfSections,
77 | "The number of keys in the colors dictionary must match the number of UITableView sections")
78 |
79 | (0 ... tableCollectionView.numberOfSections - 1).forEach { colorSection in
80 | XCTAssertEqual(colorsDictionary[colorSection]!.count, tableCollectionView.numberOfCollectionItems,
81 | "The number of colors for section must match the number of UICollectionCells")
82 | }
83 | }
84 |
85 | func testTableViewDataSource() {
86 | XCTAssertNotNil(tableCollectionView.tableView.dataSource, "UITableView dataSource is nil")
87 | }
88 |
89 | func testTableViewDelegate() {
90 | XCTAssertNotNil(tableCollectionView.tableView.delegate, "UITableView delegate is nil")
91 | }
92 |
93 | func testUITableViewCellClasses() {
94 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
95 |
96 | visibleCells.forEach { cell in
97 | if cell is GLCollectionTableViewCell {
98 | return
99 | } else {
100 | XCTAssertTrue(cell is GLCollectionTableViewCell, "UITableViewCells must be GLCollectionTableViewCell")
101 | }
102 | }
103 | }
104 |
105 | func testTableViewCellIdentifiers() {
106 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
107 |
108 | visibleCells.forEach { cell in
109 | guard let tableViewCell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
110 | fatalError("UITableViewCells must be GLCollectionTableViewCell")
111 | }
112 |
113 | guard let reuseID: String = tableViewCell.reuseIdentifier else {
114 | fatalError("UITableViewCells must have a reuse identifier")
115 | }
116 |
117 | XCTAssertTrue(Int(reuseID.components(separatedBy: "#").last!)! >= 0,
118 | "GLCollectionTableViewCell cellIdentifier was: \(String(describing: reuseID))\nGLCollectionTableViewCell's cellIdentifier must end with a positive integer")
119 | }
120 | }
121 |
122 | // MARK:
123 |
124 | func testCollectionViewsDelegateAndDataSource() {
125 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
126 |
127 | visibleCells.forEach { cell in
128 | guard let collectionTableCell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
129 | fatalError("UITableViewCells must be GLCollectionTableViewCell")
130 | }
131 |
132 | XCTAssertNotNil(collectionTableCell.collectionView.dataSource, "GLCollectionTableViewCell dataSource is nil")
133 | XCTAssertNotNil(collectionTableCell.collectionView.delegate, "GLCollectionTableViewCell delegate is nil")
134 | }
135 | }
136 |
137 | func testCollectionNativePaginationFlag() {
138 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
139 |
140 | visibleCells.forEach { cell in
141 | guard let collectionTableCell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
142 | fatalError("UITableViewCells must be GLCollectionTableViewCell")
143 | }
144 |
145 | if collectionTableCell.collectionViewPaginatedScroll == true {
146 | XCTAssertFalse(collectionTableCell.collectionView.isPagingEnabled,
147 | "Custom scrolling pagination and native UICollectionView pagination can't be enabled at the same time")
148 | }
149 | }
150 | }
151 |
152 | func testCollectionViewPaginationConsistency() {
153 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
154 |
155 | visibleCells.forEach { cell in
156 | guard let collectionTableCell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
157 | fatalError("UITableViewCells must be GLCollectionTableViewCell")
158 | }
159 |
160 | if collectionTableCell.collectionViewPaginatedScroll == true {
161 | XCTAssertTrue(collectionTableCell.collectionView.isScrollEnabled,
162 | "If custom paginated scroll is enabled the UICollectionView should be scrollable")
163 |
164 | XCTAssertTrue(collectionTableCell.collectionView.isUserInteractionEnabled,
165 | "If custom paginated scroll is enabled the UICollectionView should be user interactive")
166 | }
167 | }
168 | }
169 |
170 | func testCollectionViewCellHeights() {
171 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
172 |
173 | let tableViewCellHeight: CGFloat = tableCollectionView.tableView.rowHeight
174 |
175 | XCTAssertLessThanOrEqual(tableViewCellHeight + (tableCollectionView.collectionTopInset + tableCollectionView.collectionBottomInset), tableViewCellHeight,
176 | "UITableView rowHeight was: \(tableViewCellHeight)\nUICollectionViewCells height should be lower than rowHeight + (collectionTopInset + collectionBottomInset)")
177 | }
178 |
179 | // MARK:
180 |
181 | func testOpaqueFlag() {
182 | XCTAssertTrue(tableCollectionView.tableView.isOpaque, "The UITableView should be opaque for increased performances")
183 |
184 | XCTAssertGreaterThan(visibleCells.count, 0, "UITableView visible cells must be greater than 0")
185 |
186 | visibleCells.forEach { cell in
187 | guard let collectionTableCell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
188 | fatalError("UITableViewCells must be GLCollectionTableViewCell")
189 | }
190 |
191 | XCTAssertTrue(collectionTableCell.collectionView.isOpaque, "The UICollectionView should be opaque for increased performances")
192 | }
193 | }
194 |
195 | override func tearDown() {
196 | // Put teardown code here. This method is called after the invocation of
197 | // each test method in the class.
198 | super.tearDown()
199 |
200 | tableCollectionView.endAppearanceTransition()
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/GLTableCollectionViewTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/GitHub Page/Images/Source/Diagram.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GitHub Page/Images/Source/Diagram.graffle
--------------------------------------------------------------------------------
/GitHub Page/Images/Source/Icon.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GitHub Page/Images/Source/Icon.pxm
--------------------------------------------------------------------------------
/GitHub Page/Images/Source/Logo.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GitHub Page/Images/Source/Logo.pxm
--------------------------------------------------------------------------------
/GitHub Page/Images/demonstration.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GitHub Page/Images/demonstration.gif
--------------------------------------------------------------------------------
/GitHub Page/Images/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GitHub Page/Images/diagram.png
--------------------------------------------------------------------------------
/GitHub Page/Images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giulio92/GLTableCollectionView/e187baab7d7dc116bcafda476592667b519e8a57/GitHub Page/Images/logo.png
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Description:
4 |
6 |
7 | ### Expected Behavior:
8 |
9 |
10 | ### Actual Behavior:
11 |
12 |
13 | ### Steps to Reproduce:
14 |
15 |
16 | 1.
17 | 2.
18 | 3.
19 | 4.
20 |
21 | ### Your Environment:
22 |
23 | * GLTableCollectionView version:
24 | * Device:
25 | * iOS version:
26 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Giulio Lombardo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GLTableCollectionView
2 |
3 |
4 |
5 |
6 |
7 | |**Branch**|**Status**|
8 | |:--------:|:--------:|
9 | |`master`|[](https://dashboard.buddybuild.com/apps/592889ed482e8d00016f99eb/build/latest?branch=master)|
10 | |`develop`|[](https://dashboard.buddybuild.com/apps/592889ed482e8d00016f99eb/build/latest?branch=develop)|
11 |
12 | 
13 | 
14 | [](https://codebeat.co/projects/github-com-giulio92-gltablecollectionview)
15 | [](https://github.com/giulio92/GLTableCollectionView/blob/master/LICENSE.txt)
16 |
17 | ## What it is
18 | GLTableCollectionView is a ready to use `UITableViewController` with a `UICollectionView` for each `UITableViewCell`, something like Netflix, Airbnb or the Apple's App Store are doing in their iOS apps. GLTableCollectionView is completely customizable in both its `UITableView` and `UICollectionView` parts since it has been made on the same Data Source and Delegate methods with no complicated additions.
19 |
20 | | | GLTableCollectionView |
21 | |----------|-------------------------------|
22 | 🔄|The **same** `UITableView` reusable cells logic provided from Apple's implementation
23 | ♻️|`UICollectionView` cell recycle
24 | 🆒|Both `UITableView` & `UICollectionView` can have their own sections and/or headers
25 | 🎨|Customization of `UICollectionViewCell`s using the same `UICollectionViewDelegate Flow Layout` you already know
26 | ✨|Previous `UICollectionView` **.contentOffset** value restoration after scroll
27 | ↔️|`UICollectionView` cell-size-based scroll pagination, see below for instructions
28 | 📐|Storyboard and Auto Layout compatibility
29 | 💎|Clean architecture
30 | 🔧|Unit Tests
31 |
32 | ## Enable/disable scroll pagination
33 | Set `paginationEnabled` variable `true` in GLTableCollectionViewController class, `false` to disable. Default value is `true`.
34 | ```
35 | /// Set true to enable UICollectionViews scroll pagination
36 | var paginationEnabled: Bool = true
37 | ```
38 |
39 | ## Demo
40 |
41 |
42 |
43 |
44 | ## How it works
45 |
46 |
47 |
48 |
49 | ## Requirements
50 | - Xcode 10.0+
51 | - Swift 4.2+
52 | - iOS 9.0+
53 | - [SwiftLint](https://github.com/realm/SwiftLint) (Optional, but _highly_ suggested)
54 |
55 | ## Donations
56 | - [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=giulio%2elombardo%40gmail%2ecom&lc=IT¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted)
57 |
58 | - BTC: `3Mc25tFtxxwD9mXqtxFn5Qvkbndg3NhvXi`
59 |
60 | - LTC: `MUoZzdDqD2BkWsVpcSv1pQVHhCcUuiADCL`
61 |
--------------------------------------------------------------------------------