Skip to content

Commit

Permalink
Add CaseIterable table/collectionview helpers (#107)
Browse files Browse the repository at this point in the history
* Add a bunch of convenience methods on `CaseIterable` + tests

* Add demo VC for CaseIterable stuff

* Document CaseIterable helpers

* Bump podspec

* `in: indexPath -> `at: indexPath`

* clarify caseNames so they don't look like array methods
  • Loading branch information
designatednerd authored Oct 2, 2018
1 parent 1fadaae commit 48f8e51
Show file tree
Hide file tree
Showing 8 changed files with 575 additions and 19 deletions.
30 changes: 23 additions & 7 deletions Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
2B3FEEE91EAA04BB00EF8D20 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3FEEE81EAA04BB00EF8D20 /* DemoViewController.swift */; };
2B440A6B1EA9EB0A00AC33F8 /* SearchableCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B440A6A1EA9EB0A00AC33F8 /* SearchableCollectionViewController.swift */; };
334F248E20889A330048EC4F /* SweetUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149D816C1DBD2AFB00A9EB1A /* SweetUIKit.framework */; };
33BFD2A42155538500963663 /* CaseIterable+Sweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BFD2A32155538500963663 /* CaseIterable+Sweet.swift */; };
33BFD2A52155538500963663 /* CaseIterable+Sweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BFD2A32155538500963663 /* CaseIterable+Sweet.swift */; };
33BFD2A92155540300963663 /* CaseIterableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BFD2A6215553F800963663 /* CaseIterableTests.swift */; };
33BFD2AA2155540400963663 /* CaseIterableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BFD2A6215553F800963663 /* CaseIterableTests.swift */; };
33BFD2AC215564F900963663 /* CaseIterableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BFD2AB215564F900963663 /* CaseIterableViewController.swift */; };
440998811DC0B1C600C11852 /* UICollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440998801DC0B1C600C11852 /* UICollectionViewTests.swift */; };
440998821DC0B1C600C11852 /* UICollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440998801DC0B1C600C11852 /* UICollectionViewTests.swift */; };
4479760F1DC0A42100A1F577 /* UITableView+Sweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4479760E1DC0A42100A1F577 /* UITableView+Sweet.swift */; };
Expand Down Expand Up @@ -221,6 +226,9 @@
14F393961CC6517E00616696 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
2B3FEEE81EAA04BB00EF8D20 /* DemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = "<group>"; };
2B440A6A1EA9EB0A00AC33F8 /* SearchableCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchableCollectionViewController.swift; sourceTree = "<group>"; };
33BFD2A32155538500963663 /* CaseIterable+Sweet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Sweet.swift"; sourceTree = "<group>"; };
33BFD2A6215553F800963663 /* CaseIterableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseIterableTests.swift; sourceTree = "<group>"; };
33BFD2AB215564F900963663 /* CaseIterableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseIterableViewController.swift; sourceTree = "<group>"; };
33CCF0F8215521CB0015417E /* SweetUIKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SweetUIKit.podspec; sourceTree = "<group>"; };
33CCF0F9215522530015417E /* config.yml */ = {isa = PBXFileReference; lastKnownFileType = text; name = config.yml; path = .circleci/config.yml; sourceTree = "<group>"; };
440998801DC0B1C600C11852 /* UICollectionViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -368,6 +376,7 @@
146D72AF1AB782920058798C /* Tests */ = {
isa = PBXGroup;
children = (
33BFD2A6215553F800963663 /* CaseIterableTests.swift */,
9F4C5B6C1DC0DE1A00F2A77B /* IndexPathTests.swift */,
140124B41DBF57FC00EA92FB /* StringTests.swift */,
440998801DC0B1C600C11852 /* UICollectionViewTests.swift */,
Expand Down Expand Up @@ -415,24 +424,25 @@
isa = PBXGroup;
children = (
146436361FB1122500E2E8AD /* iOS */,
149D81811DBD2E8000A9EB1A /* SweetUIKit.h */,
33BFD2A32155538500963663 /* CaseIterable+Sweet.swift */,
140124621DBF57BA00EA92FB /* Identifiable.swift */,
140124631DBF57BA00EA92FB /* IndexPath+Sweet.swift */,
9FC8B9E81DC74D0500A68185 /* Jiggly.swift */,
9F2BDE191E09286500E32CAD /* KeyboardAwareInputViewProtocol.swift */,
9F6BE7111EC1B88700A954A1 /* SearchBarContainerView.swift */,
140124651DBF57BA00EA92FB /* String+Sweet.swift */,
140124661DBF57BA00EA92FB /* SweetCollectionController.swift */,
140124671DBF57BA00EA92FB /* SweetTableController.swift */,
149D81811DBD2E8000A9EB1A /* SweetUIKit.h */,
140124681DBF57BA00EA92FB /* UIAlertController+Sweet.swift */,
447976131DC0A42C00A1F577 /* UICollectionView+Sweet.swift */,
142565771E09641400184D47 /* UIColor+Sweet.swift */,
140124691DBF57BA00EA92FB /* UIImage+Sweet.swift */,
1401246A1DBF57BA00EA92FB /* UILabel+Sweet.swift */,
9F6BE7111EC1B88700A954A1 /* SearchBarContainerView.swift */,
1401246B1DBF57BA00EA92FB /* UIScrollView+Sweet.swift */,
4479760E1DC0A42100A1F577 /* UITableView+Sweet.swift */,
1401246C1DBF57BA00EA92FB /* UIView+Sweet.swift */,
1401246D1DBF57BA00EA92FB /* UIViewController+Sweet.swift */,
4479760E1DC0A42100A1F577 /* UITableView+Sweet.swift */,
447976131DC0A42C00A1F577 /* UICollectionView+Sweet.swift */,
9FC8B9E81DC74D0500A68185 /* Jiggly.swift */,
142565771E09641400184D47 /* UIColor+Sweet.swift */,
9F2BDE191E09286500E32CAD /* KeyboardAwareInputViewProtocol.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand All @@ -452,6 +462,7 @@
9F2BDE1C1E092B0C00E32CAD /* EditViewController.swift */,
2B440A6A1EA9EB0A00AC33F8 /* SearchableCollectionViewController.swift */,
14D986661DBF58A900D7842C /* TableController.swift */,
33BFD2AB215564F900963663 /* CaseIterableViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -874,6 +885,7 @@
2B3FEEE91EAA04BB00EF8D20 /* DemoViewController.swift in Sources */,
14D986691DBF58A900D7842C /* CollectionViewCell.swift in Sources */,
2B440A6B1EA9EB0A00AC33F8 /* SearchableCollectionViewController.swift in Sources */,
33BFD2AC215564F900963663 /* CaseIterableViewController.swift in Sources */,
9F2BDE1D1E092B0C00E32CAD /* EditViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -904,6 +916,7 @@
9F4C5B6D1DC0DE1A00F2A77B /* IndexPathTests.swift in Sources */,
140124B71DBF57FC00EA92FB /* StringTests.swift in Sources */,
140124BB1DBF57FC00EA92FB /* UIScrollViewTests.swift in Sources */,
33BFD2A92155540300963663 /* CaseIterableTests.swift in Sources */,
440998811DC0B1C600C11852 /* UICollectionViewTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -917,6 +930,7 @@
140124B81DBF57FC00EA92FB /* StringTests.swift in Sources */,
140124BA1DBF57FC00EA92FB /* UILabelTests.swift in Sources */,
440998821DC0B1C600C11852 /* UICollectionViewTests.swift in Sources */,
33BFD2AA2155540400963663 /* CaseIterableTests.swift in Sources */,
142565801E09643100184D47 /* UIColorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -933,6 +947,7 @@
140124981DBF57BA00EA92FB /* UILabel+Sweet.swift in Sources */,
1401247F1DBF57BA00EA92FB /* String+Sweet.swift in Sources */,
447976141DC0A42C00A1F577 /* UICollectionView+Sweet.swift in Sources */,
33BFD2A42155538500963663 /* CaseIterable+Sweet.swift in Sources */,
140124841DBF57BA00EA92FB /* SweetCollectionController.swift in Sources */,
1464363A1FB1122500E2E8AD /* OpenInSafariActivity.swift in Sources */,
1425657A1E09641400184D47 /* UIColor+Sweet.swift in Sources */,
Expand Down Expand Up @@ -960,6 +975,7 @@
140124811DBF57BA00EA92FB /* String+Sweet.swift in Sources */,
447976161DC0A42C00A1F577 /* UICollectionView+Sweet.swift in Sources */,
146436351FB1111D00E2E8AD /* SearchBarContainerView.swift in Sources */,
33BFD2A52155538500963663 /* CaseIterable+Sweet.swift in Sources */,
140124861DBF57BA00EA92FB /* SweetCollectionController.swift in Sources */,
1425657C1E09641400184D47 /* UIColor+Sweet.swift in Sources */,
140124901DBF57BA00EA92FB /* UIAlertController+Sweet.swift in Sources */,
Expand Down
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,50 @@ inputView(_:shouldUpdatePosition:)
```


## `CaseIterable` helpers

A bunch of syntactic sugar to help with using `CaseIterable` enums to manage sections and rows in static tableviews.

This allows you to use something like

```swift
let item = Item.forRow(at: indexPath)
```

in order to get a known item from a list. Please see the sample app's [`CaseIterableViewController`](iOSDemo/ViewControllers/CaseIterableViewController.swift) for an example.


### Array Converter

```swift
/// Returns the `allCases` static var as an array so it can be accessed based on index.
public static var allCasesArray: [Self]
```

### Non-optional helpers

Should be used when the number of of cases in `allCases` is predictable - usually when automatically generated by the compiler.
These methods will `fatalError` if you try to access a case which does not exist.

```swift
public static func forIndex(_ index: Int) -> Self
public static func forSection(at indexPath: IndexPath) -> Self
public static func forRow(at indexPath: IndexPath) -> Self
public static func forItem(at indexPath: IndexPath) -> Self
```

### Optional Helpers

Should be used when the number of cases in `allCases` is variable - usually when this var is manually overridden to allow showing/hiding of a section or row.

```swift
public static func optionalForIndex(_ index: Int) -> Self?
public static func optionalForSection(at indexPath: IndexPath) -> Self?
public static func optionalForRow(at indexPath: IndexPath) -> Self?
public static func optionalForItem(at indexPath: IndexPath) -> Self?
```


## Installation

**SweetUIKit** is available through [CocoaPods](http://cocoapods.org). To install
Expand Down
117 changes: 117 additions & 0 deletions Sources/CaseIterable+Sweet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import Foundation

public extension CaseIterable {

/// Returns the `allCases` static var as an array so it can be accessed based on index.
public static var allCasesArray: [Self] {
if let asArray = self.allCases as? [Self] {
return asArray
} else {
// The above cast works a lot of the time, but is not guaranteed since it's considered
// an implementation detail despite the fact that the collection is guaranteed to be in
// the order of declaration. If it doesn't work, fall back to mapping `allCases` into an array.
return allCases.map { $0 }
}
}

// MARK: - Non-optional helpers

/// Gets the enum value at the given index in `allCases`
///
/// NOTE: This method will puke up a fatal error if the given `CaseIterable`
/// enum does not contain the given index. use `optionalForIndex(_:)`
/// to avoid crashes if the number of items in `allCases` may not be
/// known at runtime due to overriding the `allCases` property.
///
/// - Parameter index: The index in the list of cases for which an enum value is desired.
/// - Returns: The enum value at the given index.
public static func forIndex(_ index: Int) -> Self {
guard let item = self.optionalForIndex(index) else {
fatalError("SweetUIKit: Enum \(Self.self) does not contain index \(index)")
}

return item
}

/// Gets the enum at the index in `allCases` of the `section` of the passed-in `IndexPath`
///
/// NOTE: This method will puke up a fatal error if the given `CaseIterable`
/// enum does not contain the given index. use `optionalForSection(at:)` to
/// avoid crashes if the number of items in `allCases` may not be
/// known at runtime due to overriding the `allCases` property.
///
/// - Parameter index: The indexPath for which you want the `section` property to be used to retrieve the enum value from `allCases`.
/// - Returns: The enum value at the given index.
public static func forSection(at indexPath: IndexPath) -> Self {
return forIndex(indexPath.section)
}

/// Gets the enum at the index in `allCases` of the `row` of the passed-in `IndexPath`
///
/// NOTE: This method will puke up a fatal error if the given `CaseIterable`
/// enum does not contain the given index. use `optionalForItem(at:)` to
/// avoid crashes if the number of items in `allCases` may not be
/// known at runtime due to overriding the `allCases` property.
///
/// - Parameter index: The indexPath for which you want the `section` property to be used to retrieve the enum value from `allCases`.
/// - Returns: The enum value at the given index.
public static func forRow(at indexPath: IndexPath) -> Self {
return forIndex(indexPath.row)
}

/// Gets the enum at the index in `allCases` of the `item` of the passed-in `IndexPath`
///
/// NOTE: This method will puke up a fatal error if the given `CaseIterable`
/// enum does not contain the given index. use `optionalForItem(at:)` to
/// avoid crashes if the number of items in `allCases` may not be
/// known at runtime due to overriding the `allCases` property.
///
/// - Parameter index: The indexPath for which you want the `section` property to be used to retrieve the enum value from `allCases`.
/// - Returns: The enum value at the given index.
public static func forItem(in indexPath: IndexPath) -> Self {
return forIndex(indexPath.item)
}

// MARK: - Optional Helpers

/// Gets the enum value at the given index in `allCases`
///
/// - Parameter index: The index in the list of cases for which an enum value is desired.
/// - Returns: The enum value at the given index, or nil if `allCases` does not contain the given index.
public static func optionalForIndex(_ index: Int) -> Self? {
guard allCasesArray.indices.contains(index) else {
return nil
}

return allCasesArray[index]
}

/// Gets the enum at the index in `allCases` of the `section` of the passed-in `IndexPath`
///
/// NOTE: This method will puke up a fatal error if the given `CaseIterable`
/// enum does not contain the given index. use `optionalForSection(at:)` to
/// avoid crashes if the number of items in `allCases` may not be
/// known at runtime due to overriding the `allCases` property.
///
/// - Parameter index: The indexPath for which you want the `section` property to be used to retrieve the enum value from `allCases`.
/// - Returns: The enum value at the given index, or nil if `allCases` does not contain the given index.
public static func optionalForSection(at indexPath: IndexPath) -> Self? {
return optionalForIndex(indexPath.section)
}

/// Gets the enum at the index in `allCases` of the `row` of the passed-in `IndexPath`
///
/// - Parameter index: The indexPath for which you want the `section` property to be used to retrieve the enum value from `allCases`.
/// - Returns: The enum value at the given index, or nil if `allCases` does not contain the given index.
public static func optionalForRow(at indexPath: IndexPath) -> Self? {
return optionalForIndex(indexPath.row)
}

/// Gets the enum at the index in `allCases` of the `item` of the passed-in `IndexPath`
///
/// - Parameter index: The indexPath for which you want the `section` property to be used to retrieve the enum value from `allCases`.
/// - Returns: The enum value at the given index, or nil if `allCases` does not contain the given index.
public static func optionalForItem(at indexPath: IndexPath) -> Self? {
return optionalForIndex(indexPath.item)
}
}
2 changes: 1 addition & 1 deletion SweetUIKit.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "SweetUIKit"
s.summary = "Helpers and sugar for the UIKit framework."
s.version = "1.15.0"
s.version = "1.16.0"
s.homepage = "https://github.com/UseSweet/SweetUIKit"
s.license = 'MIT'
s.author = { "Use Sweet" => "[email protected]" }
Expand Down
Loading

0 comments on commit 48f8e51

Please sign in to comment.