├── .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 | ![CI](https://github.com/cosmicfools/ModuleServices/workflows/CI/badge.svg) 4 | [![Version](https://img.shields.io/cocoapods/v/ModuleServices.svg?style=flat)](http://cocoapods.org/pods/ModuleServices) 5 | [![License](https://img.shields.io/cocoapods/l/ModuleServices.svg?style=flat)](http://cocoapods.org/pods/ModuleServices) 6 | [![Platform](https://img.shields.io/cocoapods/p/ModuleServices.svg?style=flat)](http://cocoapods.org/pods/ModuleServices) 7 | [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/cosmicfools/moduleservices)](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 | ![Let me give it a try](https://raw.githubusercontent.com/cosmicfools/ModuleServices/master/giveATry.jpg) 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 --------------------------------------------------------------------------------