Skip to content
Merged
21 changes: 21 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# rule identifiers to exclude from running
disabled_rules:
- line_length

# some rules are only opt-in
opt_in_rules:
# Find all the available rules by running:
# swiftlint rules


# paths to include during linting. `--path` is ignored if present.
included:


# paths to ignore during linting. Takes precedence over `included`.
excluded:


# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning
7 changes: 0 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,4 @@ before_install:
- xcodebuild -version
- xcodebuild -showsdks

# This command will fix Travis CI duplicated simulators build issue
# happening while using Xcode 8.2 + iOS simulator 10.2 configuration. Be
# sure to check GitHub issue #7031
# (https://github.com/travis-ci/travis-ci/issues/7031) for updates and, as
# soon as the Travis team fix the issue, remove this line.
- xcrun simctl delete D0257C83-DB81-4567-93EC-4C6DF23DC24C

script: xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug test | xcpretty -f `xcpretty-travis-formatter` && exit ${PIPESTATUS[0]}
58 changes: 35 additions & 23 deletions Classes/GLCollectionTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,58 @@
import UIKit

class GLIndexedCollectionViewFlowLayout: UICollectionViewFlowLayout {
var paginatedScroll: Bool?
fileprivate var paginatedScroll: Bool?

override func awakeFromNib() {
super.awakeFromNib()
}

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// If the UICollectionView has paginatedScroll set to false there is no
// need to apply any pagination logic, so we simply return the current
// proposedContentOffset coordinates.
guard paginatedScroll == true else {
return CGPoint(x: proposedContentOffset.x, y: 0)
}

// It's not a bad idea to shield us for some strange cases where the
// UICollectionView won't be there for any reason, since it comes in an
// Optional flavor. If the UICollectionView won't be available we return
// the current proposedContentOffset coordinates and exit.
guard let collectionView: UICollectionView = collectionView else {
return CGPoint(x: proposedContentOffset.x, y: 0)
}

let scannerFrame: CGRect = CGRect(x: proposedContentOffset.x,
y: 0,
width: collectionView.bounds.width,
height: collectionView.bounds.height)

// If there is no UICollectionViewLayoutAttributes for the given
// scannerFrame there is no reason to calculate a paginated layout for
// it, so we the current proposedContentOffset coordinates.
guard let layoutAttributes: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: scannerFrame) else {
return CGPoint(x: proposedContentOffset.x, y: 0)
}

// To make paginated scrolling work fine this CGFloat below MUST be
// equal to the value set in the insetForSectionAt method of
// UICollectionView's UICollectionViewDelegate Flow Layout.
let collectionViewInsets: CGFloat = 10.0

// Since UICollectionViewFlowLayout proposedContentOffset coordinates
// Since UICollectionViewFlowLayout's proposedContentOffset coordinates
// won't take count of any UICollectionView UIEdgeInsets values we need
// to fix it by adding collectionViewInsets to the .x coordinate.
//
// Note: This will only cover horizontal scrolling and pagination, if you
// need vertical pagination replace the .x coordinate with .y and update
// collectionViewInsets value with the appropriate one.
// Note: This will only cover horizontal scrolling and pagination, if
// you need vertical pagination replace the .x coordinate with .y and
// update collectionViewInsets value with the appropriate one.
let proposedXCoordWithInsets: CGFloat = proposedContentOffset.x + collectionViewInsets

// We now create a variable and we assign a very high CGFloat to it (a
// big number here is needed to cover very large
// UICollectionViewContentSize cases). This var will hold the needed
// horizontal adjustment to make the UICollectionView paginate scroll.
// to cover very large UICollectionViewContentSize cases).
// This var will hold the needed horizontal adjustment to make the
// UICollectionView paginate scroll.
var offsetCorrection: CGFloat = .greatestFiniteMagnitude

// Now we loop through all the different layout attributes of the
Expand All @@ -67,22 +90,11 @@ class GLIndexedCollectionViewFlowLayout: UICollectionViewFlowLayout {
// cell which needs the least offsetCorrection value: it will mean that
// it's the first cell on the left of the screen which will give
// pagination.
for layoutAttribute: UICollectionViewLayoutAttributes in super.layoutAttributesForElements(in: CGRect(x: proposedContentOffset.x, y: 0, width: collectionView!.bounds.width, height: collectionView!.bounds.height))! {
// layoutAttributesForElements may contain all sort of layout
layoutAttributes.forEach { (layoutAttribute) in
// UICollectionViewLayoutAttributes may contain all sort of layout
// attributes so we need to check if it belongs to a
// UICollectionViewCell, otherwise logic won't work.
if layoutAttribute.representedElementCategory == .cell {
// Since UICollectionViewFlowLayout's UICollectionView comes in
// an Optional flavour and we will need to get it's frame size
// down below, it's not a bad idea to shield us from those very
// strange cases in which the UICollectionView won't be there
// (for whatever reason) and exit the loop since it is useless
// to calculate an offsetCorrection for a non-existent
// UICollectionView.
guard let collectionView = collectionView else {
break
}

// To accurately calculate the offsetCorrection we will check
// only the cells contained in one half of UICollectionView's
// width, following the scrolling direction. The check will be
Expand All @@ -91,7 +103,7 @@ class GLIndexedCollectionViewFlowLayout: UICollectionViewFlowLayout {
let discardableScrollingElementsFrame: CGFloat = collectionView.contentOffset.x + (collectionView.frame.size.width / 2)

if (layoutAttribute.center.x <= discardableScrollingElementsFrame && velocity.x > 0) || (layoutAttribute.center.x >= discardableScrollingElementsFrame && velocity.x < 0) {
continue
return
}

if abs(layoutAttribute.frame.origin.x - proposedXCoordWithInsets) < abs(offsetCorrection) {
Expand Down Expand Up @@ -160,7 +172,7 @@ class GLCollectionTableViewCell: UITableViewCell {
collectionFlowLayout.scrollDirection = .horizontal

collectionView = GLIndexedCollectionView(frame: .zero, collectionViewLayout: collectionFlowLayout)
collectionView.register(UINib(nibName: "GLIndexedCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "collectionViewCellID")
collectionView.register(UINib(nibName: "GLIndexedCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: GLIndexedCollectionViewCell.identifier)
collectionView.backgroundColor = .white
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
Expand Down
2 changes: 2 additions & 0 deletions Classes/GLIndexedCollectionViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import UIKit

class GLIndexedCollectionViewCell: UICollectionViewCell {
static let identifier: String = "collectionViewCellID"

override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
Expand Down
56 changes: 31 additions & 25 deletions Classes/GLTableCollectionViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@

import UIKit

class GLTableCollectionViewController: UITableViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// This string constant will be the cellIdentifier for the UITableViewCells
// holding the UICollectionView, it's important to append "_section#" to it
// so we can understand which cell is the one we are looking for in the
// debugger. Look in UITableView's data source cellForRowAt method for more
// explanations about the UITableViewCell reuse handling.
let tableCellID: String = "tableViewCellID_section_#"
let collectionCellID: String = "collectionViewCellID"
final class GLTableCollectionViewController: UITableViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// This static string constant will be the cellIdentifier for the
// UITableViewCells holding the UICollectionView, it's important to append
// "_section#" to it so we can understand which cell is the one we are
// looking for in the debugger. Look in UITableView's data source
// cellForRowAt method for more explanations about the UITableViewCell reuse
// handling.
static let tableCellID: String = "tableViewCellID_section_#"

let numberOfSections: Int = 20
let numberOfCollectionsForRow: Int = 1
Expand Down Expand Up @@ -66,7 +66,7 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
let randomGreen: CGFloat = CGFloat(arc4random_uniform(256))
let randomBlue: CGFloat = CGFloat(arc4random_uniform(256))

if (randomRed == 255.0 && randomGreen == 255.0 && randomBlue == 255.0) {
if randomRed == 255.0 && randomGreen == 255.0 && randomBlue == 255.0 {
randomRed = CGFloat(arc4random_uniform(128))
}

Expand Down Expand Up @@ -109,10 +109,10 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
// will have a different UICollectionView with UICollectionViewCells in
// it and UITableView reuse won't work as expected giving back wrong
// cells.
var cell: GLCollectionTableViewCell? = tableView.dequeueReusableCell(withIdentifier: tableCellID + indexPath.section.description) as? GLCollectionTableViewCell
var cell: GLCollectionTableViewCell? = tableView.dequeueReusableCell(withIdentifier: GLTableCollectionViewController.tableCellID + indexPath.section.description) as? GLCollectionTableViewCell

if cell == nil {
cell = GLCollectionTableViewCell(style: .default, reuseIdentifier: tableCellID + indexPath.section.description)
cell = GLCollectionTableViewCell(style: .default, reuseIdentifier: GLTableCollectionViewController.tableCellID + indexPath.section.description)

// Configure the cell...
cell!.selectionStyle = .none
Expand All @@ -126,6 +126,8 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
return "Section: " + section.description
}

// MARK: <UITableView Delegate>

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 88
}
Expand All @@ -138,8 +140,6 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
return 0.0001
}

// MARK: <UITableView Delegate>

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell: GLCollectionTableViewCell = cell as? GLCollectionTableViewCell else {
return
Expand All @@ -159,10 +159,16 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: GLIndexedCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionCellID, for: indexPath) as! GLIndexedCollectionViewCell
guard let cell: GLIndexedCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: GLIndexedCollectionViewCell.identifier, for: indexPath) as? GLIndexedCollectionViewCell else {
fatalError("UICollectionViewCell must be of GLIndexedCollectionViewCell type")
}

guard let indexedCollectionView: GLIndexedCollectionView = collectionView as? GLIndexedCollectionView else {
fatalError("UICollectionView must be of GLIndexedCollectionView type")
}

// Configure the cell...
cell.backgroundColor = colorsDict[(collectionView as! GLIndexedCollectionView).indexPath.section]?[indexPath.row]
cell.backgroundColor = colorsDict[indexedCollectionView.indexPath.section]?[indexPath.row]

return cell
}
Expand All @@ -175,7 +181,7 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
let collectionRightInset: CGFloat = 10

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(collectionTopInset, collectionLeftInset, collectionBottomInset, collectionRightInset)
return UIEdgeInsets(top: collectionTopInset, left: collectionLeftInset, bottom: collectionBottomInset, right: collectionRightInset)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
Expand All @@ -197,17 +203,17 @@ class GLTableCollectionViewController: UITableViewController, UICollectionViewDa
// MARK: <UICollectionView Delegate>

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

}

/*
// MARK: <Navigation>
/*
// MARK: <Navigation>

// In a storyboard-based application, you will often want to do a little
// In a storyboard-based application, you will often want to do a little
// preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
18 changes: 18 additions & 0 deletions GLTableCollectionView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
747CF8F51DE6EB010051A5FF /* Sources */,
747CF8F61DE6EB010051A5FF /* Frameworks */,
747CF8F71DE6EB010051A5FF /* Resources */,
EF73511D1E8E9B5800F73EDB /* SwiftLint */,
);
buildRules = (
);
Expand Down Expand Up @@ -205,6 +206,23 @@
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
EF73511D1E8E9B5800F73EDB /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = SwiftLint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
747CF8F51DE6EB010051A5FF /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand Down
2 changes: 1 addition & 1 deletion GLTableCollectionView/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion GLTableCollectionView/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.69</string>
<string>1.70</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
Expand Down
17 changes: 7 additions & 10 deletions GLTableCollectionViewTests/GLTableCollectionViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,14 @@ class GLTableCollectionViewTests: XCTestCase {
XCTAssertGreaterThan(tableCollectionView.numberOfCollectionItems, 0,
"numberOfCollectionItems was: \(tableCollectionView.numberOfCollectionItems)\nThere must be at least one GLIndexedCollectionViewCell")

XCTAssertNotEqual(tableCollectionView.tableCellID, "",
"tableCellID was: \(tableCollectionView.tableCellID)\nUITableViewCell's cellIdentifier must not be empty")
XCTAssertNotEqual(GLTableCollectionViewController.tableCellID, "",
"tableCellID was: \(GLTableCollectionViewController.tableCellID)\nUITableViewCell's cellIdentifier must not be empty")

XCTAssertTrue(tableCollectionView.tableCellID.hasSuffix("_section_#"),
"tableCellID was: \(tableCollectionView.tableCellID)\nUITableViewCell's cellIdentifier must end with section number suffix")
XCTAssertTrue(GLTableCollectionViewController.tableCellID.hasSuffix("_section_#"),
"tableCellID was: \(GLTableCollectionViewController.tableCellID)\nUITableViewCell's cellIdentifier must end with section number suffix")

XCTAssertTrue((tableCollectionView.tableCellID.components(separatedBy: "#").count - 1) == 1,
"tableCellID was: \(tableCollectionView.tableCellID)\nUITableViewCell's cellIdentifier must contain only one # in it")

XCTAssertNotEqual(tableCollectionView.collectionCellID, "",
"The cellIdentifier for the UICollectionCells should not be empty")
XCTAssertTrue((GLTableCollectionViewController.tableCellID.components(separatedBy: "#").count - 1) == 1,
"tableCellID was: \(GLTableCollectionViewController.tableCellID)\nUITableViewCell's cellIdentifier must contain only one # in it")
}

func testRandomColorsGeneration() {
Expand Down Expand Up @@ -106,7 +103,7 @@ class GLTableCollectionViewTests: XCTestCase {

for tableViewCell: GLCollectionTableViewCell in visibleCells as! [GLCollectionTableViewCell] {
XCTAssertTrue(Int(tableViewCell.reuseIdentifier!.components(separatedBy: "#").last!)! >= 0,
"GLCollectionTableViewCell cellIdentifier was: \(tableViewCell.reuseIdentifier)\nGLCollectionTableViewCell's cellIdentifier must end with a positive integer")
"GLCollectionTableViewCell cellIdentifier was: \(String(describing: tableViewCell.reuseIdentifier))\nGLCollectionTableViewCell's cellIdentifier must end with a positive integer")
}
}

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var paginationEnabled: Bool = true
- Xcode 8.0+
- Swift 3.0+
- iOS 8.0+
- [SwiftLint](https://github.com/realm/SwiftLint) (Optional, but _highly_ suggested)

## Note
GLTableCollectionView is written using Swift 3.0 so it would only support iOS 8.0+ due to Swift 3 language compatibility, if you use Swift 2.0 in your project or you need iOS 7.0+ compatibility GLTableCollectionView will work too, but you **must** convert `UITableView` and `UICollectionView` Data Source and Delegate methods signatures before building your code or Xcode won't compile.