├── .github
└── workflows
│ └── compilation.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── ModuleServices.podspec
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── ModuleGenericServices
│ ├── Protocols
│ │ ├── ConfigurableCell.swift
│ │ ├── RowSelectableProtocol.swift
│ │ └── RowsDecoratorProtocol.swift
│ └── SingleCell
│ │ ├── Class
│ │ ├── ConfigurableSingleClassCellRowsModule.swift
│ │ ├── ConfigurableSingleClassRowModule.swift
│ │ ├── SelectableSingleClassRowModule.swift
│ │ ├── SingleClassCellRowsModule.swift
│ │ ├── SingleClassRowBaseModule.swift
│ │ └── SingleClassRowModule.swift
│ │ ├── Nib
│ │ ├── ConfigurableSingleNibCellRowsModule.swift
│ │ ├── ConfigurableSingleNibRowModule.swift
│ │ ├── SelectableSingleNibRowModule.swift
│ │ ├── SingleNibCellRowsModule.swift
│ │ ├── SingleNibRowBaseModule.swift
│ │ └── SingleNibRowModule.swift
│ │ └── SingleRowBaseModule.swift
├── ModuleServices
│ ├── ModulesViewAdditions.swift
│ ├── ModulesViewController.swift
│ └── TableSectionModule.swift
└── ModuleSnapshotServices
│ ├── GenericSnapshotTestSuite.swift
│ ├── ModulesHelperTestsViewController.swift
│ ├── SnapshotEngine.swift
│ ├── SnapshotGenerator.swift
│ ├── SnapshotObject.swift
│ ├── SnapshotTestDeviceInfo.swift
│ ├── SnapshotTestingCellAdditions.swift
│ └── SnapshotTestingViewAdditions.swift
├── Tests
├── ModuleGenericServicesTests
│ ├── SingleClassRowBaseModuleTests.swift
│ └── SingleClassRowModuleTests.swift
├── ModuleServicesTests
│ ├── ModulesViewControllerTest.swift
│ ├── TableSectionModuleTest.swift
│ ├── TestHelpers.swift
│ └── XCTestManifests.swift
└── ModuleSnapshotServicesTests
│ └── ModulesHelperTestsViewControllerTests.swift
└── giveATry.jpg
/.github/workflows/compilation.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 |
8 | jobs:
9 | build:
10 | runs-on: macos-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 |
15 | - name: Run tests
16 | run: |
17 | xcodebuild test -scheme ModuleServices-Package -destination 'name=iPhone 11'
18 |
19 | - name: Compile project (in Release)
20 | run: |
21 | xcodebuild build -scheme ModuleServices-Package -destination 'name=iPhone 11'
22 |
--------------------------------------------------------------------------------
/.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/
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Francisco Javier Trujillo Mata
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/ModuleServices.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint ModuleServices.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = "ModuleServices"
11 | s.version = "1.1.0"
12 | s.summary = "Reusable ViewController with TableView, splitted in Sections"
13 |
14 | s.description = "Reusable ViewController with TableView, split in Sections (called here modules) that help you to develop faster in Swift"
15 |
16 | s.homepage = "https://github.com/cosmicfools/ModuleServices"
17 | s.license = 'MIT'
18 | s.author = { "Francisco Javier Trujillo Mata" => "fjtrujy@gmail.com" }
19 | s.source = { :git => "https://github.com/cosmicfools/ModuleServices.git", :tag => s.version.to_s }
20 | s.social_media_url = 'https://twitter.com/fjtrujy'
21 |
22 | s.ios.deployment_target = '9.0'
23 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.5' }
24 | s.swift_version = '5.5'
25 | s.default_subspec = 'Core'
26 |
27 | s.subspec 'Core' do |ss|
28 | ss.source_files = 'Sources/ModuleServices/**/*'
29 | end
30 |
31 | s.subspec 'Generic' do |ss|
32 | ss.dependency 'ModuleServices/Core'
33 | ss.source_files = 'Sources/ModuleGenericServices/**/*'
34 | end
35 |
36 | s.subspec 'Snapshot' do |ss|
37 | ss.dependency 'ModuleServices/Core'
38 | ss.dependency 'CombinationGenerator', '~> 0.2.4'
39 | ss.source_files = 'Sources/ModuleSnapshotServices/**/*'
40 | ss.frameworks = 'XCTest'
41 | end
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CombinationGenerator",
6 | "repositoryURL": "https://github.com/cosmicfools/CombinationGenerator",
7 | "state": {
8 | "branch": null,
9 | "revision": "48fe12420a4765c126af9799540f474da5848f3c",
10 | "version": "0.2.3"
11 | }
12 | },
13 | {
14 | "package": "Runtime",
15 | "repositoryURL": "https://github.com/wickwirew/Runtime.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "a848b81e1a0100801f572e5273ffe4352fd54088",
19 | "version": "2.2.2"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ModuleServices",
8 | platforms: [
9 | .iOS(.v9)
10 | ],
11 | products: [
12 | .library(name: "ModuleServices", targets: ["ModuleServices"]),
13 | .library( name: "ModuleSnapshotServices", targets: ["ModuleSnapshotServices"]),
14 | .library(name: "ModuleGenericServices", targets: ["ModuleGenericServices"]),
15 | ],
16 | dependencies: [
17 | .package(name: "CombinationGenerator", url: "https://github.com/cosmicfools/CombinationGenerator", from: "0.2.3"),
18 | ],
19 | targets: [
20 | .target(
21 | name: "ModuleServices",
22 | dependencies: []),
23 | .target(
24 | name: "ModuleSnapshotServices",
25 | dependencies: ["CombinationGenerator", "ModuleServices"]),
26 | .target(
27 | name: "ModuleGenericServices",
28 | dependencies: ["ModuleServices"]),
29 | .testTarget(
30 | name: "ModuleServicesTests",
31 | dependencies: ["ModuleServices"]),
32 | .testTarget(
33 | name: "ModuleSnapshotServicesTests",
34 | dependencies: ["ModuleServices", "ModuleSnapshotServices"]),
35 | .testTarget(
36 | name: "ModuleGenericServicesTests",
37 | dependencies: ["ModuleServices", "ModuleGenericServices", "ModuleSnapshotServices"]),
38 | ]
39 | )
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ModuleServices
2 |
3 | 
4 | [](http://cocoapods.org/pods/ModuleServices)
5 | [](http://cocoapods.org/pods/ModuleServices)
6 | [](http://cocoapods.org/pods/ModuleServices)
7 | [](http://clayallsopp.github.io/readme-score?url=https://github.com/cosmicfools/moduleservices)
8 |
9 | ## Requirements
10 |
11 | Is valid for iOS 12 and higher.
12 | Requires Swift 5.5 and XCode 11.0 or higher.
13 |
14 | ## Installation
15 |
16 | ### Swift Package Manager
17 |
18 | You can install CombinationGenerator via [Swift Package Manager](https://swift.org/package-manager/) by adding the following line to your `Package.swift`:
19 |
20 | ```swift
21 | import PackageDescription
22 |
23 | let package = Package(
24 | [...]
25 | dependencies: [
26 | .Package(name: "ModuleServices", url: "https://github.com/cosmicfools/ModuleServices.git", .branch("master"))
27 | ]
28 | )
29 | ```
30 |
31 | ### Cocoapods
32 |
33 | You can install CombinationGenerator via [Cocoapods](https://cocoapods.org/) by adding the following line to your `Podfile`:
34 |
35 | ```ruby
36 | pod 'ModuleServices'
37 | ```
38 |
39 | ## How to use it
40 |
41 | ModuleServices basically is a pack of tools that is helping you to develop faster. This libabry is so useful for those UIViewControllers that are based in UITableView.
42 | The main concept in this libary is, a Module means a Section in a UITableView, so there is a subclass of UIViewController called ModulesViewController that manage all modules.
43 |
44 | A Module is a like and mini UIViewController, should be able to work it self.
45 |
46 | Basically a ModulesViewController has an array of TableSectionModules.
47 |
48 | Sometimes an example is easier to understand than 1000 words.. so, we have an additionaly repository full of examples:
49 | [Module-examples](https://github.com/fjtrujy/module-examples)
50 |
51 |
52 | 
53 |
54 | #### 1. Create a Module
55 | You need to create a subclass of `TableSectionModule`
56 |
57 | ```swift
58 | class FirstSectionModule: TableSectionModule {}
59 | ```
60 |
61 | #### 2. Override the needed methods in the Module
62 | There are a lot of methods that could be override. The most usuals are:
63 |
64 | * Registration of `UITableViewCell`/`UITableViewHeaderFooterView` with `Class`/`Nib`
65 | ```swift
66 | override func registerClassForCells() -> [AnyClass]
67 | override func registerClassForHeadersFooters() -> [AnyClass]
68 | override func registerNibsForCells() -> [AnyClass]
69 | override func registerNibsForHeadersFooters() -> [AnyClass]
70 | ```
71 |
72 | * Creation of rows, this is the like the data source of the `UITableView`
73 | ```swift
74 | override func createRows()
75 | ```
76 |
77 | * Dequeue and configure of the `UITableViewCell`
78 | ```swift
79 | override func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell
80 | ```
81 | * Rest of method that could be override
82 | You can override basically the same method that the `UITableViewDelegate` and `UITableViewDataSource` offer.
83 |
84 | * Obviously you will need to create&configure all the `UITableViewCells` that the Module would contains.
85 |
86 | ###### Example of `TableSectionModule` with methods
87 |
88 |
89 | ```swift
90 | import ModuleServices
91 |
92 | class FirstSectionModule: TableSectionModule {
93 | override func registerNibsForCells() -> [AnyClass] {
94 | return super.registerNibsForCells() + [
95 | Example1TableViewCell.classForCoder(),
96 | ]
97 | }
98 |
99 | override func registerClassForCells() -> [AnyClass] {
100 | super.registerClassForCells() + [UITableViewCell.classForCoder()]
101 | }
102 |
103 | override func createRows() {
104 | super.createRows()
105 |
106 | rows.append(String(describing: Example1TableViewCell.self))
107 | rows.append(String(describing: UITableViewCell.self))
108 | }
109 |
110 | override func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
111 | let cell = tableView.dequeueReusableCell(withIdentifier: className, for: indexPath)
112 |
113 | let className = rows[(indexPath as NSIndexPath).row] as! String
114 | //Addtional configuration for the cell
115 | switch className {
116 | case String(describing: UITableViewCell.self):
117 | cell.textLabel?.text = "A tottally native cell"
118 | break
119 | default:
120 | break
121 | }
122 |
123 | return cell
124 | }
125 |
126 | override func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
127 | return 44.0
128 | }
129 | ```
130 |
131 |
132 | #### 3. Create a ModulesViewController
133 | You need to create a subclass of `ModulesViewController`
134 | ```swift
135 | class MyViewController: ModulesViewController {}
136 | ```
137 |
138 | #### 4. Override the needed methods in the ViewController
139 | The methods to be override in the `ModulesViewController` are less than on the `Modules`. Usually is just needed to override the `createModules`
140 |
141 | ```swift
142 | override func createModules()
143 | ```
144 | This method will add all the modules that the view controller could have. Like this example:
145 |
146 | ```swift
147 | override func createModules() {
148 | super.createModules()
149 |
150 | appendModule(FirstSectionModule(tableView: tableView!))
151 | }
152 | ```
153 |
154 |
155 |
156 | ###### Example of `ModulesViewController` with methods
157 |
158 |
159 | ```swift
160 | import ModuleServices
161 |
162 | class MyViewController: ModulesViewController {
163 | override func viewDidLoad() {
164 | super.viewDidLoad()
165 | // Do any additional setup after loading the view, typically from a nib.
166 | tableView?.rowHeight = UITableViewAutomaticDimension
167 | tableView?.estimatedRowHeight = 44
168 |
169 | tableView?.tableFooterView = UIView()
170 | }
171 |
172 | override func createModules() {
173 | super.createModules()
174 |
175 | appendModule(FirstSectionModule(tableView: tableView!))
176 | }
177 |
178 | }
179 |
180 | ```
181 |
182 | As could appreciate this is a very good approach to avoid masive view controllers. The main idea is to split responsabilities.
183 |
184 | - A `ModulesViewController` will manage (add/remove) `TableSectionModule`
185 | - A `TableSectionModule` will manage the cells that the section itself will contains
186 | - A `TableSectionModule` could contain enough logic & responsability how to make it fully work that section of the `UITableView`
187 |
188 |
189 | ##### Enjoy and be module my developer! :godmode:
190 |
191 | ## Author
192 |
193 | Francisco Javier Trujillo Mata, fjtrujy@gmail.com
194 |
195 | ## License
196 |
197 | ModuleServices is available under the MIT license. See the LICENSE file for more info.
198 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/Protocols/ConfigurableCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableCell.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | public protocol ConfigurableCell: UITableViewCell {
11 | associatedtype Decorator
12 | func configure(decorator: Decorator)
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/Protocols/RowSelectableProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RowSelectableProtocol.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol RowSelectableProtocol: NSObject {
11 | var element: Any { get }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/Protocols/RowsDecoratorProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RowsDecoratorProtocol.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol RowsDecoratorProtocol {
11 | associatedtype RowElement
12 |
13 | var rows: [RowElement] { get set }
14 | init()
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Class/ConfigurableSingleClassCellRowsModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableSingleClassCellRowsModule.swift
3 | //
4 | //
5 | // Created by Francisco Navarro on 31/01/2021.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class ConfigurableSingleClassCellRowsModule: SingleClassCellRowsModule {
12 |
13 | func configure(decorator: Decorator) {
14 | rows = decorator.rows
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Class/ConfigurableSingleClassRowModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableSingleClassRowModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class ConfigurableSingleClassRowModule:
12 | SingleClassRowBaseModule {
13 |
14 | var decorator: Decorator?
15 |
16 | open override func createRows() {
17 | super.createRows()
18 | guard let decorator = decorator else { return }
19 | rows += [decorator]
20 | }
21 |
22 | open func configure(decorator: Decorator) {
23 | self.decorator = decorator
24 |
25 | createRows()
26 | }
27 |
28 | open func refreshCell() {
29 | guard let cell = tableView.cellForRow(at: IndexPath(row: .zero, section: section)) as? Cell,
30 | let decorator = decorator as? Cell.Decorator else { return }
31 | cell.configure(decorator: decorator)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Class/SelectableSingleClassRowModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectableSingleClassRowModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SelectableSingleClassRowModule:
12 | ConfigurableSingleNibRowModule {
13 | open weak var delegate: SelectableSingleClassRowModuleDelegate?
14 |
15 | open override func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
16 | tableView.deselectRow(at: indexPath, animated: true)
17 | guard let element = decorator?.element else { return }
18 | delegate?.selectableSingleClassRowModule(self, didSelectElement: element)
19 | }
20 | }
21 |
22 | // MARK: - SelectableSingleClassRowModuleDelegate
23 | public protocol SelectableSingleClassRowModuleDelegate: AnyObject {
24 | func selectableSingleClassRowModule
25 | (_ module: SelectableSingleClassRowModule, didSelectElement element: Any)
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Class/SingleClassCellRowsModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleClassCellRowsModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleClassCellRowsModule: TableSectionModule {
12 | open override func registerNibsForCells() -> [AnyClass] {
13 | super.registerNibsForCells() + [
14 | Cell.classForCoder()
15 | ]
16 | }
17 |
18 | open override func createRows() {
19 | super.createRows()
20 |
21 | rows += RowsDecorator().rows
22 | }
23 |
24 | open override func tableView(_ tableView: UITableView,
25 | cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
26 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: Cell.self), for: indexPath)
27 |
28 | guard let decorator = rows[indexPath.row] as? Cell.Decorator, let myCell = cell as? Cell else { return cell }
29 | configureDelegateCell(myCell)
30 | myCell.configure(decorator: decorator)
31 |
32 | return myCell
33 | }
34 |
35 | open override func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
36 | UITableView.automaticDimension
37 | }
38 |
39 | open func configureDelegateCell(_ cell: Cell) {}
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Class/SingleClassRowBaseModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleClassRowBaseModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleClassRowBaseModule: SingleRowBaseModule {
12 | open override func registerClassForCells() -> [AnyClass] {
13 | super.registerClassForCells() + [
14 | Cell.classForCoder()
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Class/SingleClassRowModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleClassRowModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleClassRowModule: SingleClassRowBaseModule {
12 | open override func createRows() {
13 | super.createRows()
14 |
15 | rows += [Decorator()]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Nib/ConfigurableSingleNibCellRowsModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableSingleNibCellRowsModule.swift
3 | //
4 | //
5 | // Created by Francisco Navarro on 31/01/2021.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class ConfigurableSingleNibCellRowsModule: SingleNibCellRowsModule {
12 |
13 | func configure(decorator: Decorator) {
14 | rows = decorator.rows
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Nib/ConfigurableSingleNibRowModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableSingleNibRowModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class ConfigurableSingleNibRowModule:
12 | SingleNibRowBaseModule {
13 |
14 | var decorator: Decorator?
15 |
16 | open override func createRows() {
17 | super.createRows()
18 | guard let decorator = decorator else { return }
19 | rows += [decorator]
20 | }
21 |
22 | open func configure(decorator: Decorator) {
23 | self.decorator = decorator
24 |
25 | createRows()
26 | }
27 |
28 | open func refreshCell() {
29 | guard let cell = tableView.cellForRow(at: IndexPath(row: .zero, section: section)) as? Cell,
30 | let decorator = decorator as? Cell.Decorator else { return }
31 | cell.configure(decorator: decorator)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Nib/SelectableSingleNibRowModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectableSingleNibRowModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SelectableSingleNibRowModule:
12 | ConfigurableSingleNibRowModule {
13 | open weak var delegate: SelectableSingleNibRowModuleDelegate?
14 |
15 | open override func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
16 | tableView.deselectRow(at: indexPath, animated: true)
17 | guard let element = decorator?.element else { return }
18 | delegate?.selectableSingleNibRowModule(self, didSelectElement: element)
19 | }
20 | }
21 |
22 | // MARK: - SelectableSingleNibRowModuleDelegate
23 | public protocol SelectableSingleNibRowModuleDelegate: AnyObject {
24 | func selectableSingleNibRowModule
25 | (_ module: SelectableSingleNibRowModule, didSelectElement element: Any)
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Nib/SingleNibCellRowsModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleNibCellRowsModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleNibCellRowsModule: TableSectionModule {
12 | open override func registerNibsForCells() -> [AnyClass] {
13 | super.registerNibsForCells() + [
14 | Cell.classForCoder()
15 | ]
16 | }
17 |
18 | open override func createRows() {
19 | super.createRows()
20 |
21 | rows += RowsDecorator().rows
22 | }
23 |
24 | open override func tableView(_ tableView: UITableView,
25 | cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
26 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: Cell.self), for: indexPath)
27 |
28 | guard let decorator = rows[indexPath.row] as? Cell.Decorator, let myCell = cell as? Cell else { return cell }
29 | configureDelegateCell(myCell)
30 | myCell.configure(decorator: decorator)
31 |
32 | return myCell
33 | }
34 |
35 | open override func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
36 | UITableView.automaticDimension
37 | }
38 |
39 | open func configureDelegateCell(_ cell: Cell) {}
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Nib/SingleNibRowBaseModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleNibRowBaseModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleNibRowBaseModule: SingleRowBaseModule {
12 | open override func registerNibsForCells() -> [AnyClass] {
13 | super.registerNibsForCells() + [
14 | Cell.classForCoder()
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/Nib/SingleNibRowModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleNibRowModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleNibRowModule: SingleNibRowBaseModule {
12 | open override func createRows() {
13 | super.createRows()
14 |
15 | rows += [Decorator()]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ModuleGenericServices/SingleCell/SingleRowBaseModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleRowBaseModule.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import UIKit
9 | import ModuleServices
10 |
11 | open class SingleRowBaseModule: TableSectionModule {
12 | open override func tableView(_ tableView: UITableView,
13 | cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
14 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: Cell.self), for: indexPath)
15 |
16 | guard let decorator = rows[indexPath.row] as? Cell.Decorator, let myCell = cell as? Cell else { return cell }
17 | configureDelegateCell(myCell)
18 | myCell.configure(decorator: decorator)
19 |
20 | return myCell
21 | }
22 |
23 | open override func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
24 | UITableView.automaticDimension
25 | }
26 |
27 | open func configureDelegateCell(_ cell: Cell) {}
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/ModuleServices/ModulesViewAdditions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModulesViewAdditions.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 09/05/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIView {
11 |
12 | func fitSubView(view: UIView) {
13 | fitSubView(view: view,
14 | leading: .zero,
15 | top: .zero,
16 | traling: .zero,
17 | bottom: .zero)
18 | }
19 |
20 | func fitSubView(view: UIView, leading: CGFloat, top: CGFloat, traling: CGFloat, bottom: CGFloat ) {
21 | addSubview(view)
22 | view.translatesAutoresizingMaskIntoConstraints = false
23 |
24 | let multiplier = CGFloat(truncating: NSDecimalNumber.one)
25 | addConstraint(NSLayoutConstraint(item: view,
26 | attribute: .top,
27 | relatedBy: .equal,
28 | toItem: self,
29 | attribute: .top,
30 | multiplier: multiplier,
31 | constant: top))
32 | addConstraint(NSLayoutConstraint(item: view,
33 | attribute: .bottom,
34 | relatedBy: .equal,
35 | toItem: self,
36 | attribute: .bottom,
37 | multiplier: multiplier,
38 | constant: bottom))
39 | addConstraint(NSLayoutConstraint(item: view,
40 | attribute: .leading,
41 | relatedBy: .equal,
42 | toItem: self,
43 | attribute: .leading,
44 | multiplier: multiplier,
45 | constant: leading))
46 | addConstraint(NSLayoutConstraint(item: view,
47 | attribute: .trailing,
48 | relatedBy: .equal,
49 | toItem: self,
50 | attribute: .trailing,
51 | multiplier: multiplier,
52 | constant: traling))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/ModuleServices/ModulesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModulesViewController.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 11/5/16.
6 | // Copyright © 2016 FJTRUJY. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class ModulesViewController: UIViewController {
12 | @IBOutlet weak open var tableView:UITableView?
13 | private(set) internal var modules:[TableSectionModule] = []
14 |
15 | open override func viewDidLoad() {
16 | super.viewDidLoad()
17 | setupStyle()
18 | createModules()
19 | }
20 |
21 | open override func viewWillAppear(_ animated: Bool) { modules.forEach { $0.willAppear() } }
22 | open override func viewWillDisappear(_ animated: Bool) { modules.forEach { module in module.willDissappear() } }
23 | open func setupStyle() {}
24 | open func createModules() { modules = [] }
25 | open func startFecthModules() { modules.forEach { $0.startFetch() } }
26 | open func stopFetchModules() { modules.forEach { $0.stopFetch() } }
27 | }
28 |
29 | // MARK: - TableSectionModuleSectionSource
30 | extension ModulesViewController: TableSectionModuleSectionSource {
31 | public func allModules() -> [TableSectionModule] { modules }
32 |
33 | public func appendModule(_ module: TableSectionModule) {
34 | module.sectionSource = self;
35 | modules.append(module)
36 | }
37 |
38 | public func insertModule(_ module: TableSectionModule, atIndex: Int) {
39 | module.sectionSource = self;
40 | modules.insert(module, at: atIndex)
41 | }
42 |
43 | public func removeAllModules() { modules.removeAll() }
44 | public func removeModule(_ module: TableSectionModule) { modules.removeAll { $0 == module } }
45 | public func removeModuleAtIndex(_ atIndex: Int) { modules.remove(at: atIndex) }
46 | public func removeFirstModule() { modules.removeFirst() }
47 | public func removeLastModule() { modules.removeLast() }
48 |
49 | public func replaceModuleAtSection(_ section: Int, withModule module: TableSectionModule) {
50 | module.sectionSource = self
51 | modules[section] = module
52 | }
53 |
54 | public func sectionForModule(_ module: TableSectionModule) -> Int? {
55 | modules.firstIndex(of: module)
56 | }
57 |
58 | public func firstModule() -> T? { modules.first { $0 is T } as? T }
59 |
60 | public func firstModule(where predicate: (T) -> Bool) -> T? {
61 | modules.first {
62 | guard let module = $0 as? T else { return false }
63 | return predicate(module)
64 | } as? T
65 | }
66 |
67 | public func lastModule() -> T? { modules.last { $0 is T } as? T }
68 |
69 | public func lastModule(where predicate: (T) -> Bool) -> T? {
70 | modules.last {
71 | guard let module = $0 as? T else { return false }
72 | return predicate(module)
73 | } as? T
74 | }
75 |
76 | public func filterModules() -> [T]? { modules.filter { $0 is T } as? [T] }
77 |
78 | public func filterModules(where predicate: (T) -> Bool) -> [T]? {
79 | modules.filter {
80 | guard let module = $0 as? T else { return false }
81 | return predicate(module)
82 | } as? [T]
83 | }
84 | }
85 |
86 | // MARK: - UITableViewDelegate, UITableViewDataSource
87 | extension ModulesViewController: UITableViewDelegate, UITableViewDataSource {
88 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
89 | modules[indexPath.section].tableView(tableView, willDisplayCell: cell, forRowAtIndexPath: indexPath)
90 | }
91 |
92 | public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
93 | modules[section].tableView(tableView, willDisplayHeaderView: view, forSection: section)
94 | }
95 |
96 | public func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) {
97 | modules[section].tableView(tableView, willDisplayFooterView: view, forSection: section)
98 | }
99 |
100 | public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell,
101 | forRowAt indexPath: IndexPath) {
102 | guard modules.count > indexPath.section else { return }
103 | modules[indexPath.section].tableView(tableView, didEndDisplayingCell: cell, forRowAtIndexPath: indexPath)
104 | }
105 |
106 | public func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
107 | guard modules.count > section else { return }
108 | modules[section].tableView(tableView, didEndDisplayingHeaderView: view, forSection: section)
109 | }
110 |
111 | public func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) {
112 | guard modules.count > section else { return }
113 | modules[section].tableView(tableView, didEndDisplayingFooterView: view, forSection: section)
114 | }
115 |
116 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
117 | modules[indexPath.section].tableView(tableView, heightForRowAtIndexPath: indexPath)
118 | }
119 |
120 | public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
121 | modules[section].tableView(tableView, heightForHeaderInSection: section)
122 | }
123 |
124 | public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
125 | modules[section].tableView(tableView, heightForFooterInSection: section)
126 | }
127 |
128 | public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
129 | modules[indexPath.section].tableView(tableView, estimatedHeightForRowAtIndexPath: indexPath)
130 | }
131 |
132 | public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
133 | modules[section].tableView(tableView, estimatedHeightForHeaderInSection: section)
134 | }
135 |
136 | public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
137 | modules[section].tableView(tableView, estimatedHeightForFooterInSection: section)
138 | }
139 |
140 | public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
141 | modules[section].tableView(tableView, viewForHeaderInSection: section)
142 | }
143 |
144 | public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
145 | modules[section].tableView(tableView, viewForFooterInSection: section)
146 | }
147 |
148 | public func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
149 | modules[indexPath.section].tableView(tableView, accessoryButtonTappedForRowWithIndexPath: indexPath)
150 | }
151 |
152 | public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
153 | modules[indexPath.section].tableView(tableView, shouldHighlightRowAtIndexPath: indexPath)
154 | }
155 |
156 | public func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
157 | modules[indexPath.section].tableView(tableView, didHighlightRowAtIndexPath: indexPath)
158 | }
159 |
160 | public func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
161 | modules[indexPath.section].tableView(tableView, didUnhighlightRowAtIndexPath: indexPath)
162 | }
163 |
164 | public func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
165 | modules[indexPath.section].tableView(tableView, willSelectRowAtIndexPath: indexPath)
166 | }
167 |
168 | public func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
169 | modules[indexPath.section].tableView(tableView, willDeselectRowAtIndexPath: indexPath)
170 | }
171 |
172 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
173 | modules[indexPath.section].tableView(tableView, didSelectRowAtIndexPath: indexPath)
174 | }
175 |
176 | public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
177 | modules[indexPath.section].tableView(tableView, didDeselectRowAtIndexPath: indexPath)
178 | }
179 |
180 | public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
181 | modules[indexPath.section].tableView(tableView, editingStyleForRowAtIndexPath: indexPath)
182 | }
183 |
184 | public func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
185 | modules[indexPath.section].tableView(tableView, titleForDeleteConfirmationButtonForRowAtIndexPath: indexPath)
186 | }
187 |
188 | public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
189 | modules[indexPath.section].tableView(tableView, editActionsForRowAtIndexPath: indexPath)
190 | }
191 |
192 | public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
193 | modules[indexPath.section].tableView(tableView, shouldIndentWhileEditingRowAtIndexPath: indexPath)
194 | }
195 |
196 | public func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
197 | modules[indexPath.section].tableView(tableView, willBeginEditingRowAtIndexPath: indexPath)
198 | }
199 |
200 | public func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
201 | guard let validIndexPath = indexPath, validIndexPath.section < modules.count else { return }
202 | modules[validIndexPath.section].tableView(tableView, didEndEditingRowAtIndexPath: validIndexPath)
203 | }
204 |
205 | public func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int {
206 | modules[indexPath.section].tableView(tableView, indentationLevelForRowAtIndexPath: indexPath)
207 | }
208 |
209 | public func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
210 | modules[indexPath.section].tableView(tableView, shouldShowMenuForRowAtIndexPath: indexPath)
211 | }
212 |
213 | public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath,
214 | withSender sender: Any?) -> Bool {
215 | false //FIXME: Redirect to the proper module
216 | }
217 |
218 | public func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath,
219 | withSender sender: Any?) {
220 | //FIXME: Redirect to the proper module
221 | }
222 |
223 | public func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool {
224 | modules[indexPath.section].tableView(tableView, canFocusRowAtIndexPath: indexPath)
225 | }
226 |
227 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
228 | modules[section].rows.count
229 | }
230 |
231 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
232 | modules[indexPath.section].tableView(tableView, cellForRowAtIndexPath: indexPath)
233 | }
234 |
235 | public func numberOfSections(in tableView: UITableView) -> Int { modules.count }
236 |
237 | public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
238 | modules[section].tableView(tableView, titleForHeaderInSection: section)
239 | }
240 |
241 | public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
242 | modules[section].tableView(tableView, titleForFooterInSection: section)
243 | }
244 |
245 | public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
246 | modules[indexPath.section].tableView(tableView, canEditRowAtIndexPath: indexPath)
247 | }
248 |
249 | public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
250 | modules[indexPath.section].tableView(tableView, canMoveRowAtIndexPath: indexPath)
251 | }
252 |
253 | public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
254 | modules[sourceIndexPath.section].tableView(tableView, moveRowAt: sourceIndexPath, to: destinationIndexPath)
255 | }
256 |
257 | public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
258 | modules[indexPath.section].tableView(tableView, commitEditingStyle: editingStyle, forRowAtIndexPath: indexPath)
259 | }
260 |
261 | public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath,
262 | toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
263 | modules[sourceIndexPath.section].tableView(tableView, targetIndexPathForMoveFromRowAt: sourceIndexPath,
264 | toProposedIndexPath: proposedDestinationIndexPath)
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/Sources/ModuleServices/TableSectionModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableSectionModule.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 11/5/16.
6 | // Copyright © 2016 FJTRUJY. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private enum Constants {
12 | static let separatorHeight: CGFloat = 1
13 | }
14 |
15 | open class TableSectionModule: NSObject {
16 | open var rows:[Any] = []
17 | weak internal var sectionSource: TableSectionModuleSectionSource?
18 |
19 | private(set) open var tableView: UITableView
20 | private(set) open var isPresented: Bool = false
21 | private(set) open var isFetching: Bool = false
22 | private var dynamicCells: [String: UITableViewCell] = [:]
23 |
24 | open var section: Int { (sectionSource?.sectionForModule(self) ?? NSNotFound) }
25 |
26 | public init(tableView:UITableView) {
27 | self.tableView = tableView
28 | super.init()
29 |
30 | registerViews()
31 | createRows()
32 | setupNotifications()
33 | }
34 |
35 | open func registerViews() { autoRegisterViews() }
36 | open func createRows() { rows = [] }
37 | open func startFetch() { isFetching = true }
38 | open func stopFetch() { isFetching = false }
39 | open func willAppear() { isPresented = true }
40 | open func willDissappear() { isPresented = false }
41 |
42 | // MARK: - Autoregistration of Cells, Header and Footer methods
43 | open func moduleBundle() -> Bundle? { nil }
44 | open func registerClassForCells() -> [AnyClass] { [] }
45 | open func registerClassForHeadersFooters() -> [AnyClass] { [] }
46 | open func registerNibsForCells() -> [AnyClass] { [] }
47 | open func registerNibsForHeadersFooters() -> [AnyClass] { [] }
48 |
49 |
50 | // MARK: - UITableViewDelegate
51 | open func tableView(_ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: IndexPath) {}
52 |
53 | open func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {}
54 |
55 | open func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) {}
56 |
57 | open func tableView(_ tableView: UITableView, didEndDisplayingCell cell: UITableViewCell,
58 | forRowAtIndexPath indexPath: IndexPath) {}
59 |
60 | open func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {}
61 |
62 | open func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) {}
63 |
64 | open func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat { .zero }
65 |
66 | open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
67 | guard tableView.style != .grouped else { return .leastNormalMagnitude }
68 | return .zero
69 | }
70 |
71 | open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
72 | guard tableView.style != .grouped else { return .leastNormalMagnitude }
73 | return .zero
74 | }
75 |
76 | open func tableView(_ tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
77 | .leastNormalMagnitude
78 | }
79 |
80 | open func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
81 | .leastNormalMagnitude
82 | }
83 |
84 | open func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
85 | .leastNormalMagnitude
86 | }
87 |
88 | open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { nil }
89 |
90 | open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { nil }
91 |
92 | open func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: IndexPath) {}
93 |
94 | open func tableView(_ tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: IndexPath) -> Bool { true }
95 |
96 | open func tableView(_ tableView: UITableView, didHighlightRowAtIndexPath indexPath: IndexPath) {}
97 |
98 | open func tableView(_ tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: IndexPath) {}
99 |
100 | open func tableView(_ tableView: UITableView, willSelectRowAtIndexPath indexPath: IndexPath) -> IndexPath? { indexPath }
101 |
102 | open func tableView(_ tableView: UITableView, willDeselectRowAtIndexPath indexPath: IndexPath) -> IndexPath? { indexPath }
103 |
104 | open func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {}
105 |
106 | open func tableView(_ tableView: UITableView, didDeselectRowAtIndexPath indexPath: IndexPath) {}
107 |
108 | open func tableView(_ tableView: UITableView, editingStyleForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell.EditingStyle {
109 | .none
110 | }
111 |
112 | open func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: IndexPath) -> String? { nil }
113 |
114 | open func tableView(_ tableView: UITableView, editActionsForRowAtIndexPath indexPath: IndexPath) -> [UITableViewRowAction]? { nil }
115 |
116 | open func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath: IndexPath) -> Bool { true }
117 |
118 | open func tableView(_ tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: IndexPath) {}
119 |
120 | open func tableView(_ tableView: UITableView, didEndEditingRowAtIndexPath indexPath: IndexPath) {}
121 |
122 | open func tableView(_ tableView: UITableView, indentationLevelForRowAtIndexPath indexPath: IndexPath) -> Int { .min }
123 |
124 | open func tableView(_ tableView: UITableView, shouldShowMenuForRowAtIndexPath indexPath: IndexPath) -> Bool { false }
125 |
126 | open func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: IndexPath,
127 | withSender sender: AnyObject?) -> Bool { false }
128 |
129 | open func tableView(_ tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: IndexPath,
130 | withSender sender: AnyObject?) {}
131 |
132 | open func tableView(_ tableView: UITableView, canFocusRowAtIndexPath indexPath: IndexPath) -> Bool { true }
133 |
134 | open func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell { .init() }
135 |
136 | open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { nil }
137 |
138 | open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { nil }
139 |
140 | open func tableView(_ tableView: UITableView, canEditRowAtIndexPath indexPath: IndexPath) -> Bool { false }
141 |
142 | open func tableView(_ tableView: UITableView, canMoveRowAtIndexPath indexPath: IndexPath) -> Bool { false }
143 |
144 | open func tableView(_ tableView: UITableView, commitEditingStyle editingStyle: UITableViewCell.EditingStyle,
145 | forRowAtIndexPath indexPath: IndexPath) {}
146 |
147 | open func tableView(_ tableView: UITableView, moveRowAtIndexPath sourceIndexPath: IndexPath,
148 | toIndexPath destinationIndexPath: IndexPath) {}
149 |
150 | open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath,
151 | to destinationIndexPath: IndexPath) {}
152 |
153 | open func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath,
154 | toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
155 | proposedDestinationIndexPath
156 | }
157 |
158 | // MARK: - Methods for sepatartor of the Cells
159 | open func setupSeparatorInsetForCell(_ cell : UITableViewCell, forIndexPath indexPath : IndexPath) {
160 | // Remove seperator inset
161 | cell.separatorInset = .zero
162 | cell.preservesSuperviewLayoutMargins = false
163 | cell.layoutMargins = .zero
164 | }
165 |
166 | open func removeSeparatorInsetForCell(_ cell : UITableViewCell, forIndexPath indexPath : IndexPath) {
167 | // Remove seperator inset
168 | cell.separatorInset = UIEdgeInsets.init(top: .zero, left: cell.bounds.size.width, bottom: .zero, right: .zero)
169 | cell.preservesSuperviewLayoutMargins = true
170 | cell.layoutMargins = .zero
171 | }
172 |
173 | // MARK: - Mothod for Refresh the section
174 | open func refreshSection() {
175 | createRows()
176 | tableView.reloadSections(IndexSet(integer: section), with: .automatic)
177 | }
178 | }
179 |
180 | // MARK: - Autocalculate the needed height of a cells
181 | public extension TableSectionModule {
182 | func dequeueDynamicHeightCellWithIdentifier(_ identifier: String) -> UITableViewCell? {
183 | switch dynamicCells[identifier] {
184 | case .some(let sizingCell): return sizingCell
185 | default:
186 | let sizingCell = tableView.dequeueReusableCell(withIdentifier: identifier)
187 | dynamicCells[identifier] = sizingCell
188 | return sizingCell
189 | }
190 | }
191 |
192 | func calculateHeightForSizingCell(_ sizingCell: UITableViewCell) -> CGFloat {
193 | sizingCell.bounds = CGRect(origin: .zero, size: CGSize(width: tableView.frame.width, height: sizingCell.bounds.height))
194 | sizingCell.setNeedsLayout()
195 | sizingCell.layoutIfNeeded()
196 |
197 | let size = sizingCell.contentView.systemLayoutSizeFitting(sizingCell.bounds.size, withHorizontalFittingPriority: .required,
198 | verticalFittingPriority: .fittingSizeLevel)
199 | let separator = Constants.separatorHeight / UIScreen.main.scale
200 |
201 | return size.height + separator // Add space for the cell separator height
202 | }
203 | }
204 |
205 | // MARK: - Private Methods for Autoregistration
206 | private extension TableSectionModule {
207 | //Autoregistrion - Override those methods if the ReuseIdentifier is exactly the same that the Class and the Nib file (if exits)
208 | func autoRegisterViews() {
209 | autoRegisterClassForCells()
210 | autoRegisterClassForHeadersFooters()
211 | autoRegisterNibsForCells()
212 | autoRegisterNibsForHeadersFooters()
213 | }
214 |
215 | func autoRegisterClassForCells() {
216 | registerClassForCells().forEach {
217 | tableView.register($0, forCellReuseIdentifier: String(describing: $0))
218 | }
219 | }
220 |
221 | func autoRegisterClassForHeadersFooters() {
222 | registerClassForHeadersFooters().forEach {
223 | tableView.register($0, forHeaderFooterViewReuseIdentifier: String(describing: $0))
224 | }
225 | }
226 |
227 | func autoRegisterNibsForCells() {
228 | registerNibsForCells().forEach {
229 | let identifier = String(describing: $0)
230 | let nib = UINib(nibName: identifier, bundle: moduleBundle())
231 | tableView.register(nib, forCellReuseIdentifier: identifier)
232 | }
233 | }
234 |
235 | func autoRegisterNibsForHeadersFooters() {
236 | registerNibsForHeadersFooters().forEach {
237 | let identifier = String(describing: $0)
238 | let nib = UINib(nibName: identifier, bundle: moduleBundle())
239 | tableView.register(nib, forHeaderFooterViewReuseIdentifier: identifier)
240 | }
241 | }
242 |
243 | }
244 |
245 | // MARK: - Notifications for CodeInjection
246 | private extension TableSectionModule {
247 | func setupNotifications() {
248 | let notification = Notification.Name("INJECTION_BUNDLE_NOTIFICATION")
249 | NotificationCenter.default.addObserver(self, selector: #selector(injectedCode(_:)), name: notification,
250 | object: nil)
251 | }
252 |
253 | @objc func injectedCode(_ notification: Notification) {
254 | refreshSection()
255 | }
256 | }
257 |
258 | // MARK: - Private Protocol for auto control of the section
259 | internal protocol TableSectionModuleSectionSource : NSObjectProtocol {
260 | func sectionForModule(_ module: TableSectionModule) -> Int?
261 | }
262 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/GenericSnapshotTestSuite.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GenericSnapshotTestSuite.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 08/02/2021.
6 | //
7 |
8 | import XCTest
9 |
10 | open class GenericSnapshotTestSuite: XCTestCase {
11 | let view = (Bundle.main.loadNibNamed(String(describing: View.self), owner: nil, options: nil)?.first as! View)
12 | var model: Engine.ModelGenerated?
13 | var identifier: String?
14 |
15 | required convenience public init(snashotSelector: Selector) {
16 | self.init(selector: snashotSelector)
17 | }
18 |
19 | open override class var defaultTestSuite: XCTestSuite {
20 | let combinations = Engine().combinations
21 | return combinations.reduce(into: XCTestSuite(forTestCaseClass: self.self)) {
22 | // Generate a test for our specific selector
23 | let snapshotTest = self.init(snashotSelector: #selector(verifyView))
24 |
25 | snapshotTest.model = $1
26 | snapshotTest.identifier = combinations.firstIndex(of: $1)?.description
27 |
28 | $0.addTest(snapshotTest)
29 | }
30 | }
31 |
32 | @objc func verifyView() {
33 | guard let model = model, let identifier = identifier
34 | else { return XCTFail("GenericSnapshotTestSuite The model or identifier for the snapshot were not set") }
35 |
36 | executeTestForView(view, model: model, identifier: identifier)
37 | }
38 |
39 | open func executeTestForView(_ view: View, model: Engine.ModelGenerated, identifier: String) {
40 | fatalError("executeTestForView must be implemented by subclasses, if you did DON'T call super")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/ModulesHelperTestsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModulesHelperTestsViewController.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 11/5/16.
6 | // Copyright © 2016 FJTRUJY. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import ModuleServices
11 |
12 | private struct Constants {
13 | static let estimatedRowHeight: CGFloat = 44.0
14 | }
15 |
16 | open class ModulesHelperTestsViewController: ModulesViewController {
17 | public weak var modulesDelegate: ModulesHelperTestsViewControllerDelegate?
18 |
19 | open override func setupStyle() {
20 | let tableView = UITableView(frame: CGRect.zero)
21 | tableView.delegate = self
22 | tableView.dataSource = self
23 | tableView.rowHeight = UITableView.automaticDimension
24 | tableView.estimatedRowHeight = Constants.estimatedRowHeight
25 | tableView.backgroundColor = UIColor.clear
26 |
27 | // This will remove extra separators from tableview
28 | tableView.tableFooterView = UIView(frame: CGRect.zero);
29 |
30 | view.fitSubView(view: tableView)
31 | self.tableView = tableView
32 | }
33 |
34 | open override func createModules() {
35 | super.createModules()
36 |
37 | guard let tableView = tableView,
38 | let modules = (modulesDelegate?.helperTestViewController(modulesHelperTest: self, tableView: tableView))
39 | else { return }
40 |
41 | modules.forEach { appendModule($0) }
42 | modulesDelegate?.helperTestViewControllerDidFinishToAddModules(modulesHelperTest: self, modules: modules)
43 | }
44 |
45 | open func adjustToFitScreen(orientation: UIInterfaceOrientation) {
46 | let portrait = SnapshotTestDeviceInfo().deviceSize
47 | let landscape = CGSize(width: portrait.height, height: portrait.width)
48 | var newFrame = CGRect.zero
49 |
50 | switch orientation {
51 | case .portrait, .portraitUpsideDown:
52 | newFrame.size = portrait
53 | break
54 | case .landscapeLeft, .landscapeRight:
55 | newFrame.size = landscape
56 | break
57 | default:
58 | newFrame.size = portrait
59 | break
60 | }
61 |
62 | view.frame = newFrame
63 |
64 | view.setNeedsLayout()
65 | view.layoutIfNeeded()
66 |
67 | if let tableView = tableView {
68 | newFrame.size = tableView.contentSize
69 | }
70 | view.frame = newFrame
71 | }
72 | }
73 |
74 | // MARK: - ModulesHelperTestsViewControllerDelegate
75 | public protocol ModulesHelperTestsViewControllerDelegate: NSObjectProtocol {
76 | func helperTestViewController(modulesHelperTest: ModulesHelperTestsViewController, tableView: UITableView)
77 | -> [TableSectionModule]
78 | func helperTestViewControllerDidFinishToAddModules(modulesHelperTest : ModulesHelperTestsViewController,
79 | modules: [TableSectionModule])
80 | }
81 |
82 | // MARK: - Extension ModulesHelperTestsViewControllerDelegate
83 | public extension ModulesHelperTestsViewControllerDelegate {
84 | func helperTestViewControllerDidFinishToAddModules(modulesHelperTest : ModulesHelperTestsViewController,
85 | modules: [TableSectionModule]) {}
86 | }
87 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/SnapshotEngine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotEngine.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 08/02/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol SnapshotEngine: NSObject {
11 | associatedtype ModelGenerated: SnapshotObject
12 | var combinations: [ModelGenerated] { get }
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/SnapshotGenerator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotGenerator.swift
3 | // Pods
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 20/06/2019.
6 | //
7 |
8 | import UIKit
9 | import CombinationGenerator
10 |
11 | open class SnapshotGenerator: Generator {
12 | public override init() {
13 | super.init()
14 |
15 | addCombination(propertyKey: "orientation",
16 | values: [UIInterfaceOrientation.portrait, UIInterfaceOrientation.landscapeLeft])
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/SnapshotObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotObject.swift
3 | // CombinationGenerator
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 20/06/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | open class SnapshotObject: NSObject {
11 | open var orientation: UIInterfaceOrientation?
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/SnapshotTestDeviceInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotTestDeviceInfo.swift
3 | // Pods
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 13/06/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | private struct Constants {
11 | static let deviceSize = CGSize(width: 750, height: 1334)
12 | static let scale: CGFloat = 2.0
13 | }
14 |
15 | public struct SnapshotTestDeviceInfo {
16 | public init () {} // Needed to be accesible
17 | public let deviceSize : CGSize = CGSize(width: Constants.deviceSize.width/Constants.scale,
18 | height:Constants.deviceSize.height/Constants.scale)
19 | public let scale : CGFloat = Constants.scale
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/SnapshotTestingCellAdditions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotTestingCellAdditions.swift
3 | // Pods
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 13/06/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UITableViewCell {
11 | override func adjustToFitScreen(orientation: UIInterfaceOrientation) {
12 | contentView.adjustToFitScreen(orientation: orientation)
13 |
14 | var adjustedFrame = contentView.bounds
15 |
16 | //Add separator height
17 | let scale = SnapshotTestDeviceInfo().scale
18 | adjustedFrame.size.height += CGFloat(NSDecimalNumber.one.floatValue) / scale
19 | adjustedFrame.size.height = ceil(adjustedFrame.size.height)
20 |
21 | contentView.bounds = adjustedFrame
22 | bounds = contentView.bounds
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/ModuleSnapshotServices/SnapshotTestingViewAdditions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotTestingViewAdditions.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 13/06/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIView {
11 | @objc func adjustToFitScreen(orientation: UIInterfaceOrientation) {
12 | let deviceSize = SnapshotTestDeviceInfo().deviceSize
13 | let width = orientation.isPortrait ? deviceSize.width : deviceSize.height
14 | bounds = CGRect(origin: CGPoint.zero,
15 | size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude - 1))
16 |
17 | setNeedsLayout()
18 | layoutIfNeeded()
19 |
20 | var adjustedFrame = bounds
21 | adjustedFrame.size = systemLayoutSizeFitting(adjustedFrame.size,
22 | withHorizontalFittingPriority: .required,
23 | verticalFittingPriority: .fittingSizeLevel)
24 | adjustedFrame.size.height = ceil(adjustedFrame.size.height)
25 |
26 | bounds = adjustedFrame
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tests/ModuleGenericServicesTests/SingleClassRowBaseModuleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleClassRowBaseModuleTests.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import XCTest
9 | import ModuleGenericServices
10 | import UIKit
11 |
12 | private class DummyCell: UITableViewCell, ConfigurableCell {
13 | class Decorator: NSObject {}
14 | func configure(decorator: Decorator) {}
15 | }
16 |
17 | class SingleClassRowBaseModuleTests: XCTestCase {
18 |
19 | func testCreateModule() {
20 | let tableView = UITableView(frame: .zero)
21 | let module = SingleClassRowBaseModule(tableView: tableView)
22 |
23 | XCTAssertNotNil(module, "Generic module has not been properly created")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/ModuleGenericServicesTests/SingleClassRowModuleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleClassRowModuleTests.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 15/10/2020.
6 | //
7 |
8 | import XCTest
9 | import ModuleServices
10 | import ModuleGenericServices
11 | import ModuleSnapshotServices
12 | import UIKit
13 |
14 | private class DummyCell: UITableViewCell, ConfigurableCell {
15 | var configured: Bool = false
16 | class Decorator: NSObject {}
17 | func configure(decorator: Decorator) { configured = true }
18 | }
19 |
20 | class SingleClassRowModuleTests: XCTestCase {
21 | let modulesHelperVC = ModulesHelperTestsViewController()
22 |
23 | func testRowCreated() {
24 | let tableView = UITableView(frame: .zero)
25 | let module = SingleClassRowModule(tableView: tableView)
26 |
27 | XCTAssert(module.rows.count == 1, "Generic module has not created properly the row")
28 | }
29 |
30 | func testConfigureCalled() {
31 | modulesHelperVC.modulesDelegate = self
32 | modulesHelperVC.adjustToFitScreen(orientation: .portrait)
33 |
34 | guard let cell = modulesHelperVC.tableView?.cellForRow(at: IndexPath(row: .zero, section: .zero)) as? DummyCell
35 | else { return XCTFail("The cell has not been initilizated") }
36 | XCTAssert(cell.configured, "The cell has not been configured")
37 | }
38 | }
39 |
40 | extension SingleClassRowModuleTests: ModulesHelperTestsViewControllerDelegate {
41 | func helperTestViewController(modulesHelperTest: ModulesHelperTestsViewController,
42 | tableView: UITableView) -> [TableSectionModule] {
43 | [SingleClassRowModule(tableView: tableView)]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Tests/ModuleServicesTests/ModulesViewControllerTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModulesViewControllerTest.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 16/5/16.
6 | // Copyright © 2016 FJTRUJY. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import ModuleServices
11 |
12 | class ModulesViewControllerTest: XCTestCase {
13 | let modulesVC : ModulesViewController = ModulesViewController()
14 | let newTableView : UITableView = UITableView()
15 |
16 | func testModulesDataSourceEmpty() {
17 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 0, "The number of section shoulb be 0")
18 | }
19 |
20 | func testAppendModule() {
21 | modulesVC.appendModule(TestModule1(tableView: newTableView))
22 |
23 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 1, "The number of section shoulb be 1")
24 | }
25 |
26 | func testInsertModuleAtFirstIndex() {
27 | modulesVC.insertModule(TestModule1(tableView: newTableView), atIndex: 0)
28 |
29 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 1, "The number of section shoulb be 0")
30 | }
31 |
32 | func testInsertModuleAtIndexWithMoreElements() {
33 | let firstModule = TestModule1(tableView: newTableView)
34 | let lastModule = TestModule1(tableView: newTableView)
35 | modulesVC.appendModule(lastModule)
36 | modulesVC.insertModule(firstModule, atIndex: 0)
37 |
38 | XCTAssert(modulesVC.sectionForModule(firstModule) == 0, "The firstModule should be the first in the array")
39 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 2, "The number of section shoulb be 0")
40 | }
41 |
42 |
43 | func testRemoveAllModules() {
44 | modulesVC.appendModule(TestModule1(tableView: newTableView))
45 | modulesVC.appendModule(TestModule1(tableView: newTableView))
46 |
47 | modulesVC.removeAllModules()
48 |
49 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 0, "The number of section shoulb be 0")
50 | }
51 |
52 | func testRemoveModuleAtIndex() {
53 | let firstModule = TestModule1(tableView: newTableView)
54 | let lastModule = TestModule1(tableView: newTableView)
55 | modulesVC.appendModule(firstModule)
56 | modulesVC.appendModule(lastModule)
57 |
58 | modulesVC.removeModuleAtIndex(0)
59 |
60 | XCTAssertNil(modulesVC.sectionForModule(firstModule), "The firstModule shouldbn't in the modules array")
61 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 1, "The number of section shoulb be 1")
62 | }
63 |
64 | func testRemoveFirstModule() {
65 | let firstModule = TestModule1(tableView: newTableView)
66 | let lastModule = TestModule1(tableView: newTableView)
67 | modulesVC.appendModule(firstModule)
68 | modulesVC.appendModule(lastModule)
69 |
70 | modulesVC.removeFirstModule()
71 |
72 | XCTAssertNil(modulesVC.sectionForModule(firstModule), "The firstModule shouldbn't in the modules array")
73 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 1, "The number of section shoulb be 1")
74 | }
75 |
76 | func testRemoveLastModule() {
77 | let firstModule = TestModule1(tableView: newTableView)
78 | let lastModule = TestModule1(tableView: newTableView)
79 | modulesVC.appendModule(firstModule)
80 | modulesVC.appendModule(lastModule)
81 |
82 | modulesVC.removeLastModule()
83 |
84 | XCTAssertNil(modulesVC.sectionForModule(lastModule), "The firstModule shouldbn't in the modules array")
85 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 1, "The number of section shoulb be 1")
86 | }
87 |
88 | func testRemoveModule() {
89 | let module = TestModule1(tableView: newTableView)
90 | modulesVC.appendModule(module)
91 | modulesVC.removeModule(module)
92 |
93 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 0, "The number of section shoulb be 0")
94 | }
95 |
96 | func testReplaceModule() {
97 | let firstModule = TestModule1(tableView: newTableView)
98 | let secondModule = TestModule1(tableView: newTableView)
99 | let newSecondModule = TestModule1(tableView: newTableView)
100 | let lastModule = TestModule1(tableView: newTableView)
101 |
102 | modulesVC.appendModule(firstModule)
103 | modulesVC.appendModule(secondModule)
104 | modulesVC.appendModule(lastModule)
105 |
106 | guard let index = modulesVC.sectionForModule(secondModule) else { return XCTFail("There should be module") }
107 |
108 | modulesVC.replaceModuleAtSection(index, withModule: newSecondModule)
109 | XCTAssertNil(modulesVC.sectionForModule(secondModule), "The second shouldbn't in the modules array")
110 | XCTAssert(modulesVC.sectionForModule(newSecondModule) == index, "The newSecondModule should be in the second position")
111 | XCTAssert(modulesVC.numberOfSections(in: newTableView) == 3, "The number of section shoulb be 3")
112 | }
113 |
114 | func testFirstModule() {
115 | let module0 = TestModule2(tableView: newTableView)
116 | let module1 = TestModule1(tableView: newTableView)
117 | let module2 = TestModule2(tableView: newTableView)
118 | let module3 = TestModule1(tableView: newTableView)
119 |
120 | modulesVC.appendModule(module0)
121 | modulesVC.appendModule(module1)
122 | modulesVC.appendModule(module2)
123 | modulesVC.appendModule(module3)
124 |
125 | let firstModule1: TestModule1? = modulesVC.firstModule()
126 | let firstModule2: TestModule2? = modulesVC.firstModule()
127 | let firstModule3: TestModule3? = modulesVC.firstModule()
128 |
129 | XCTAssert(module1 == firstModule1, "The first module filter is not working properly")
130 | XCTAssert(module0 == firstModule2, "The first module filter is not working properly")
131 | XCTAssertNil(firstModule3, "The first module filter is not working properly, it doesn't contains TestModule3")
132 | }
133 |
134 | func testFirstModuleCondition() {
135 | let module0 = TestModule1(tableView: newTableView)
136 | let module1 = TestBooleanModule(tableView: newTableView)
137 | let module2 = TestModule1(tableView: newTableView)
138 | let module3 = TestBooleanModule(tableView: newTableView)
139 |
140 | module3.fold = true
141 |
142 | modulesVC.appendModule(module0)
143 | modulesVC.appendModule(module1)
144 | modulesVC.appendModule(module2)
145 | modulesVC.appendModule(module3)
146 |
147 |
148 | let foldModule: TestBooleanModule? = modulesVC.firstModule { $0.fold }
149 | XCTAssert(module3 == foldModule, "The first where filter is not working properly")
150 | }
151 |
152 | func testLastModule() {
153 | let module0 = TestModule2(tableView: newTableView)
154 | let module1 = TestModule1(tableView: newTableView)
155 | let module2 = TestModule2(tableView: newTableView)
156 | let module3 = TestModule1(tableView: newTableView)
157 |
158 | modulesVC.appendModule(module0)
159 | modulesVC.appendModule(module1)
160 | modulesVC.appendModule(module2)
161 | modulesVC.appendModule(module3)
162 |
163 | let lastModule1: TestModule1? = modulesVC.lastModule()
164 | let lastModule2: TestModule2? = modulesVC.lastModule()
165 | let lastModule3: TestModule3? = modulesVC.firstModule()
166 |
167 | XCTAssert(module3 == lastModule1, "The last module filter is not working properly")
168 | XCTAssert(module2 == lastModule2, "The last module filter is not working properly")
169 | XCTAssertNil(lastModule3, "The last module filter is not working properly, it doesn't contains TestModule3")
170 | }
171 |
172 | func testLastModuleCondition() {
173 | let module0 = TestModule1(tableView: newTableView)
174 | let module1 = TestBooleanModule(tableView: newTableView)
175 | let module2 = TestModule1(tableView: newTableView)
176 | let module3 = TestBooleanModule(tableView: newTableView)
177 |
178 | module1.fold = true
179 |
180 | modulesVC.appendModule(module0)
181 | modulesVC.appendModule(module1)
182 | modulesVC.appendModule(module2)
183 | modulesVC.appendModule(module3)
184 |
185 |
186 | let foldModule: TestBooleanModule? = modulesVC.lastModule { $0.fold }
187 | XCTAssert(module1 == foldModule, "The last where filter is not working properly")
188 | }
189 |
190 | func testFilterModule() {
191 | let module0 = TestModule2(tableView: newTableView)
192 | let module1 = TestModule1(tableView: newTableView)
193 | let module2 = TestModule2(tableView: newTableView)
194 | let module3 = TestModule1(tableView: newTableView)
195 |
196 | modulesVC.appendModule(module0)
197 | modulesVC.appendModule(module1)
198 | modulesVC.appendModule(module2)
199 | modulesVC.appendModule(module3)
200 |
201 | let filterModules1: [TestModule1]? = modulesVC.filterModules()
202 | let filterModules3: [TestModule3]? = modulesVC.filterModules()
203 |
204 | XCTAssert(filterModules1?.count == 2, "The filter modules function is not working properly")
205 | XCTAssert(filterModules1?.first == module1, "The filter modules function is not working properly")
206 | XCTAssert(filterModules1?.last == module3, "The filter modules function is not working properly")
207 | XCTAssert(filterModules3?.count == 0, "The filter modules is not working properly, it doesn't contains TestModule3")
208 | }
209 |
210 | func testFilterModuleCondition() {
211 | let module0 = TestModule1(tableView: newTableView)
212 | let module1 = TestBooleanModule(tableView: newTableView)
213 | let module2 = TestModule1(tableView: newTableView)
214 | let module3 = TestBooleanModule(tableView: newTableView)
215 |
216 | module1.fold = true
217 |
218 | modulesVC.appendModule(module0)
219 | modulesVC.appendModule(module1)
220 | modulesVC.appendModule(module2)
221 | modulesVC.appendModule(module3)
222 |
223 | let filterModules1: [TestModule1]? = modulesVC.filterModules()
224 | let filterBooleanModules: [TestBooleanModule]? = modulesVC.filterModules { $0.fold }
225 |
226 | XCTAssert(filterModules1?.count == 2, "The filter modules function is not working properly")
227 | XCTAssert(filterModules1?.first == module0, "The filter modules function is not working properly")
228 | XCTAssert(filterModules1?.last == module2, "The filter modules function is not working properly")
229 | XCTAssert(filterBooleanModules?.count == 1, "The filter modules is not working properly, it doesn't contains TestModule3")
230 | XCTAssert(filterBooleanModules?.last == module1, "The filter modules function is not working properly")
231 | }
232 |
233 | }
234 |
--------------------------------------------------------------------------------
/Tests/ModuleServicesTests/TableSectionModuleTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableSectionModuleTest.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Trujillo on 16/05/2016.
6 | // Copyright © 2016 FJTRUJY. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 | import ModuleServices
12 |
13 | class TableSectionModuleTest: XCTestCase {
14 | }
15 |
16 | // MARK: - Fetching functions
17 | extension TableSectionModuleTest {
18 | func testStartFetchDefault() {
19 | let tableView : UITableView = UITableView()
20 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
21 |
22 | XCTAssert(!module.isFetching, "The module shouldnt be fecthing by default")
23 | }
24 |
25 | func testStartFetchTrue() {
26 | let tableView : UITableView = UITableView()
27 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
28 |
29 | module.startFetch()
30 |
31 | XCTAssert(module.isFetching, "The module should be fecthing")
32 | }
33 |
34 | func testStartFetch() {
35 | let tableView : UITableView = UITableView()
36 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
37 |
38 | XCTAssert(!module.isFetching, "The module shouldn't be fecthing")
39 |
40 | module.startFetch()
41 |
42 | XCTAssert(module.isFetching, "The module should be fecthing")
43 | }
44 |
45 | func testStopFetchDefault() {
46 | let tableView : UITableView = UITableView()
47 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
48 |
49 | XCTAssert(!module.isFetching, "The module shouldnt be fecthing by default")
50 | }
51 |
52 | func testStopFetchTrue() {
53 | let tableView : UITableView = UITableView()
54 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
55 |
56 | module.startFetch()
57 | module.stopFetch()
58 |
59 | XCTAssert(!module.isFetching, "The module shouldnt be fecthing")
60 | }
61 |
62 | func testStopFetchFalse() {
63 | let tableView : UITableView = UITableView()
64 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
65 |
66 | module.startFetch()
67 |
68 | XCTAssert(module.isFetching, "The module should be fecthing")
69 | }
70 |
71 | func testStopFetch() {
72 | let tableView : UITableView = UITableView()
73 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
74 |
75 | module.startFetch()
76 |
77 | XCTAssert(module.isFetching, "The module should be fecthing")
78 |
79 | module.stopFetch()
80 |
81 | XCTAssert(!module.isFetching, "The module should be fecthing")
82 | }
83 |
84 | func testFetchCicle() {
85 | let tableView : UITableView = UITableView()
86 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
87 |
88 | XCTAssert(!module.isFetching, "The module shouldnt be fecthing by default")
89 |
90 | module.startFetch()
91 |
92 | XCTAssert(module.isFetching, "The module should be fecthing")
93 |
94 | module.stopFetch()
95 |
96 | XCTAssert(!module.isFetching, "The module should be fecthing")
97 | }
98 | }
99 |
100 | //MARK: - isPresented Functions
101 | extension TableSectionModuleTest {
102 | func testDefaultPresentedValue() {
103 | let tableView : UITableView = UITableView()
104 | let module : TableSectionModule = TableSectionModule(tableView: tableView)
105 |
106 | XCTAssert(!module.isPresented, "The module shouldnt be presented by default")
107 | }
108 | }
109 |
110 | //MARK: - Registers Functions
111 | extension TableSectionModuleTest {
112 | func testRegisterNotNilHeader() {
113 | let tableView : UITableView = UITableView()
114 | let module : TestModule1 = TestModule1(tableView: tableView)
115 |
116 | let header : UITableViewHeaderFooterView = module.tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: TestExample1HeaderFooterView.self))!
117 |
118 | XCTAssertNotNil(header, "The TestExample1HeaderFooterView should be dequeued")
119 | }
120 |
121 | func testRegisterNotNilCells() {
122 | let tableView : UITableView = UITableView()
123 | let module : TestModule1 = TestModule1(tableView: tableView)
124 |
125 | let cell : UITableViewCell = module.tableView.dequeueReusableCell(withIdentifier: String(describing: TestExample1TableViewCell.self))!
126 |
127 | XCTAssertNotNil(cell, "The TestExample1TableViewCell should be dequeued")
128 | }
129 |
130 | func testRegisterFullModule() {
131 | let tableView : UITableView = UITableView()
132 | let module : TestModule3 = TestModule3(tableView: tableView)
133 | var header : UITableViewHeaderFooterView
134 | var cell : UITableViewCell
135 |
136 | header = module.tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: TestExample1HeaderFooterView.self))!
137 | XCTAssertNotNil(header, "The TestExample1HeaderFooterView should be dequeued")
138 |
139 | header = module.tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: TestExample2HeaderFooterView.self))!
140 | XCTAssertNotNil(header, "The TestExample2HeaderFooterView should be dequeued")
141 |
142 | cell = module.tableView.dequeueReusableCell(withIdentifier: String(describing: TestExample1TableViewCell.self))!
143 | XCTAssertNotNil(cell, "The TestExample1TableViewCell should be dequeued")
144 |
145 | cell = module.tableView.dequeueReusableCell(withIdentifier: String(describing: TestExample2TableViewCell.self))!
146 | XCTAssertNotNil(cell, "The TestExample2TableViewCell should be dequeued")
147 |
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/Tests/ModuleServicesTests/TestHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestHelpers.swift
3 | // ModuleServices
4 | //
5 | // Created by Francisco Trujillo on 16/05/2016.
6 | // Copyright © 2016 FJTRUJY. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 | import ModuleServices
12 |
13 | // MARK: - UITableViewCell subclass
14 | class TestExample1TableViewCell:UITableViewCell {}
15 |
16 | // MARK: - UITableViewCell subclass
17 | class TestExample2TableViewCell:UITableViewCell {}
18 |
19 | // MARK: - UITableViewHeaderFooterView subclass
20 | class TestExample1HeaderFooterView : UITableViewHeaderFooterView {}
21 |
22 | // MARK: - UITableViewHeaderFooterView subclass
23 | class TestExample2HeaderFooterView : UITableViewHeaderFooterView {}
24 |
25 | // MARK: - TableSectionModule subclass
26 | class TestModule1 : TableSectionModule {
27 | override func registerClassForCells() -> [AnyClass] { [TestExample1TableViewCell.classForCoder()] }
28 | override func registerClassForHeadersFooters() -> [AnyClass] { [TestExample1HeaderFooterView.classForCoder()] }
29 | }
30 |
31 | // MARK: - TableSectionModule subclass
32 | class TestModule2 : TableSectionModule {
33 | override func registerClassForCells() -> [AnyClass] { [TestExample2TableViewCell.classForCoder()] }
34 | override func registerClassForHeadersFooters() -> [AnyClass] { [TestExample2HeaderFooterView.classForCoder()] }
35 | }
36 |
37 | // MARK: - TableSectionModule subclass
38 | class TestModule3 : TableSectionModule {
39 | override func registerClassForCells() -> [AnyClass] {
40 | [TestExample1TableViewCell.classForCoder(), TestExample2TableViewCell.classForCoder()]
41 | }
42 |
43 | override func registerClassForHeadersFooters() -> [AnyClass] {
44 | [TestExample1HeaderFooterView.classForCoder(), TestExample2HeaderFooterView.classForCoder()]
45 | }
46 | }
47 |
48 | // MARK: - TableSectionModule subclass
49 | class TestBooleanModule : TableSectionModule {
50 | var fold: Bool = false
51 | override func registerClassForCells() -> [AnyClass] {
52 | [TestExample1TableViewCell.classForCoder(), TestExample2TableViewCell.classForCoder()]
53 | }
54 |
55 | override func registerClassForHeadersFooters() -> [AnyClass] {
56 | [TestExample1HeaderFooterView.classForCoder(), TestExample2HeaderFooterView.classForCoder()]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/ModuleServicesTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(ModuleServicesTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Tests/ModuleSnapshotServicesTests/ModulesHelperTestsViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModulesHelperTestsViewControllerTests.swift
3 | //
4 | //
5 | // Created by Francisco Javier Trujillo Mata on 07/10/2020.
6 | //
7 |
8 | import XCTest
9 | import UIKit
10 | import ModuleServices
11 | import ModuleSnapshotServices
12 |
13 | private enum Constants {
14 | static let expectationDuration: TimeInterval = 0.1
15 | }
16 |
17 | class ModulesHelperTestsViewControllerTests: XCTestCase {
18 | let modulesHelperVC = ModulesHelperTestsViewController()
19 | var expect: XCTestExpectation?
20 |
21 | func testDelegateCalled() {
22 | let expect = expectation(description: "Delegate create moduled didn't called")
23 | modulesHelperVC.modulesDelegate = self
24 |
25 | self.expect = expect
26 | modulesHelperVC.adjustToFitScreen(orientation: .portrait)
27 | wait(for: [expect], timeout: Constants.expectationDuration)
28 | }
29 |
30 | func testFrameAfterAdjust() {
31 | modulesHelperVC.adjustToFitScreen(orientation: .portrait)
32 | XCTAssert(modulesHelperVC.view.frame != .zero, "The size of the HelperVC didn't change")
33 | }
34 | }
35 |
36 | extension ModulesHelperTestsViewControllerTests: ModulesHelperTestsViewControllerDelegate {
37 | func helperTestViewController(modulesHelperTest: ModulesHelperTestsViewController,
38 | tableView: UITableView) -> [TableSectionModule] {
39 | expect?.fulfill()
40 | return []
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/giveATry.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicfools/ModuleServices/39f73fcfa657226740398b09957f701eecf2735b/giveATry.jpg
--------------------------------------------------------------------------------
| | | | | | | | | | | |