├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── Reusable
│ ├── NibLoadableView.swift
│ ├── ReusableView.swift
│ ├── UICollectionView+Reusable.swift
│ └── UITableView+Reusable.swift
└── Tests
├── LinuxMain.swift
└── ReusableTests
└── XCTestManifests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Guille Gonzalez
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 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Reusable",
7 | platforms: [
8 | .iOS(.v9),
9 | .tvOS(.v9),
10 | ],
11 | products: [
12 | .library(name: "Reusable", targets: ["Reusable"]),
13 | ],
14 | dependencies: [],
15 | targets: [
16 | .target(name: "Reusable", dependencies: []),
17 | .testTarget(name: "ReusableTests", dependencies: ["Reusable"]),
18 | ]
19 | )
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reusable
2 |
3 | 
4 | [](https://swift.org/package-manager)
5 | [](https://twitter.com/gonzalezreal)
6 |
7 | Reusable is a Swift µpackage that provides a type-safe way for cell registration and dequeuing in both table and collection views. It is based on [this post](https://gonzalezreal.github.io/2015/12/31/ios-cell-registration-reusing-with-swift-protocol-extensions-and-generics.html) I wrote a few years ago while I was exploring some of the Swift language features.
8 |
9 | ## Usage
10 |
11 | To be able to register and reuse a cell or supplementary view in a type-safe way, the view must conform to the `ReusableView` protocol:
12 |
13 | ```Swift
14 | extension PosterItemCell: ReusableView {}
15 | ```
16 |
17 | The default implementation of `ReusableView` will provide a reuse identifier based on the class name.
18 |
19 | Cells implemented with **Interface Builder** must also conform to the `NibLoadableView` protocol:
20 |
21 | ```Swift
22 | extension PosterItemCell: ReusableView, NibLoadableView {}
23 | ```
24 |
25 | Once the cell or supplementary view is ready, you can register it in a collection or table view by indicating its type:
26 |
27 | ```Swift
28 | func viewDidLoad() {
29 | super.viewDidLoad()
30 | collectionView.register(PosterItemCell.self)
31 | ...
32 | }
33 | ```
34 |
35 | Likewise, you can dequeue a cell previously registered in this way just by indicating its type:
36 |
37 | ```Swift
38 | func collectionView(_: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
39 | let cell = collectionView.dequeueReusableCell(PosterItemCell.self, for: indexPath)
40 | ...
41 | return cell
42 | }
43 | ```
44 |
45 | ## Installation
46 | **Using the Swift Package Manager**
47 |
48 | Add Reusable as a dependency to your `Package.swift` file. For more information, see the [Swift Package Manager documentation](https://github.com/apple/swift-package-manager/tree/master/Documentation).
49 |
50 | ```
51 | .package(url: "https://github.com/gonzalezreal/Reusable", from: "1.0.0")
52 | ```
53 |
54 | ## Help & Feedback
55 | - [Open an issue](https://github.com/gonzalezreal/Reusable/issues/new) if you need help, if you found a bug, or if you want to discuss a feature request.
56 | - [Open a PR](https://github.com/gonzalezreal/Reusable/pull/new/master) if you want to make some change to `Reusable`.
57 | - Contact [@gonzalezreal](https://twitter.com/gonzalezreal) on Twitter.
58 |
--------------------------------------------------------------------------------
/Sources/Reusable/NibLoadableView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public protocol NibLoadableView: AnyObject {
4 | static var nibName: String { get }
5 | static func instantiate() -> Self
6 | }
7 |
8 | public extension NibLoadableView where Self: UIView {
9 | static var nibName: String {
10 | return String(describing: self)
11 | }
12 |
13 | static func instantiate() -> Self {
14 | return UINib(nibName: nibName, bundle: Bundle(for: Self.self))
15 | .instantiate(withOwner: nil, options: nil)[0] as! Self
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Reusable/ReusableView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public protocol ReusableView: AnyObject {
4 | static var defaultReuseIdentifier: String { get }
5 | }
6 |
7 | public extension ReusableView where Self: UIView {
8 | static var defaultReuseIdentifier: String {
9 | return String(describing: self)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/Reusable/UICollectionView+Reusable.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public extension UICollectionView {
4 | func register(_: T.Type) where T: ReusableView {
5 | register(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
6 | }
7 |
8 | func register(_: T.Type) where T: ReusableView, T: NibLoadableView {
9 | let bundle = Bundle(for: T.self)
10 | let nib = UINib(nibName: T.nibName, bundle: bundle)
11 |
12 | register(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
13 | }
14 |
15 | func register(_: T.Type, forSupplementaryViewOfKind elementKind: String) where T: ReusableView {
16 | register(T.self, forSupplementaryViewOfKind: elementKind, withReuseIdentifier: T.defaultReuseIdentifier)
17 | }
18 |
19 | func register(_: T.Type, forSupplementaryViewOfKind elementKind: String) where T: ReusableView, T: NibLoadableView {
20 | let bundle = Bundle(for: T.self)
21 | let nib = UINib(nibName: T.nibName, bundle: bundle)
22 |
23 | register(nib, forSupplementaryViewOfKind: elementKind, withReuseIdentifier: T.defaultReuseIdentifier)
24 | }
25 |
26 | func dequeueReusableCell(_: T.Type, for indexPath: IndexPath) -> T where T: ReusableView {
27 | guard let cell = dequeueReusableCell(withReuseIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
28 | fatalError("Could not dequeue cell with identifier '\(T.defaultReuseIdentifier)'")
29 | }
30 |
31 | return cell
32 | }
33 |
34 | func dequeueReusableSupplementaryView(_: T.Type, ofKind elementKind: String, for indexPath: IndexPath) -> T where T: ReusableView {
35 | guard let supplementaryView = dequeueReusableSupplementaryView(ofKind: elementKind, withReuseIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
36 | fatalError("Could not dequeue supplementary view of kind '\(elementKind)' with identifier '\(T.defaultReuseIdentifier)'")
37 | }
38 |
39 | return supplementaryView
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Reusable/UITableView+Reusable.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public extension UITableView {
4 | func register(_: T.Type) where T: ReusableView {
5 | register(T.self, forCellReuseIdentifier: T.defaultReuseIdentifier)
6 | }
7 |
8 | func register(_: T.Type) where T: ReusableView, T: NibLoadableView {
9 | let bundle = Bundle(for: T.self)
10 | let nib = UINib(nibName: T.nibName, bundle: bundle)
11 |
12 | register(nib, forCellReuseIdentifier: T.defaultReuseIdentifier)
13 | }
14 |
15 | func register(_: T.Type) where T: ReusableView {
16 | register(T.self, forHeaderFooterViewReuseIdentifier: T.defaultReuseIdentifier)
17 | }
18 |
19 | func register(_: T.Type) where T: ReusableView, T: NibLoadableView {
20 | let bundle = Bundle(for: T.self)
21 | let nib = UINib(nibName: T.nibName, bundle: bundle)
22 |
23 | register(nib, forHeaderFooterViewReuseIdentifier: T.defaultReuseIdentifier)
24 | }
25 |
26 | func dequeueReusableCell(_: T.Type) -> T where T: ReusableView {
27 | guard let cell = dequeueReusableCell(withIdentifier: T.defaultReuseIdentifier) as? T else {
28 | fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
29 | }
30 |
31 | return cell
32 | }
33 |
34 | func dequeueReusableCell(_: T.Type, for indexPath: IndexPath) -> T where T: ReusableView {
35 | guard let cell = dequeueReusableCell(withIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
36 | fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
37 | }
38 |
39 | return cell
40 | }
41 |
42 | func dequeueReusableHeaderFooterView(_: T.Type) -> T where T: ReusableView {
43 | guard let view = dequeueReusableHeaderFooterView(withIdentifier: T.defaultReuseIdentifier) as? T else {
44 | fatalError("Could not dequeue reusable view with identifier: \(T.defaultReuseIdentifier)")
45 | }
46 |
47 | return view
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import ReusableTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += ReusableTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/ReusableTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return []
6 | }
7 | #endif
8 |
--------------------------------------------------------------------------------