├── .gitignore ├── Example ├── nemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── nemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── ad1.imageset │ │ ├── Contents.json │ │ └── ad1.png │ ├── adventuretime.imageset │ │ ├── Contents.json │ │ └── adventuretime.png │ ├── avatar.imageset │ │ ├── Contents.json │ │ └── andyy_avatar.png │ ├── bmo.imageset │ │ ├── Contents.json │ │ └── bmo.png │ ├── city.imageset │ │ ├── Contents.json │ │ └── city.jpg │ ├── dune.imageset │ │ ├── Contents.json │ │ └── dune.jpg │ ├── finn.imageset │ │ ├── Contents.json │ │ └── finn.png │ ├── icon1.imageset │ │ ├── Contents.json │ │ └── icon.png │ ├── jake.imageset │ │ ├── Contents.json │ │ └── jake.png │ ├── lsp.imageset │ │ ├── Contents.json │ │ └── lsp.png │ ├── marceline.imageset │ │ ├── Contents.json │ │ └── marceline.png │ ├── mountain.imageset │ │ ├── Contents.json │ │ └── mountain.jpg │ └── pb.imageset │ │ ├── Contents.json │ │ └── pb.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CellController │ ├── CarouselCellController │ │ ├── AdCarouselCellController │ │ │ ├── AdCarouselCellController.swift │ │ │ ├── AdCarouselCellEntity.swift │ │ │ ├── AdCarouselCellModel.swift │ │ │ └── Cell │ │ │ │ ├── AdCarouselCell.swift │ │ │ │ └── AdCarouselCell.xib │ │ ├── CarouselCellController.swift │ │ ├── CarouselCellControllerType.swift │ │ ├── CarouselCellDataSource.swift │ │ ├── CarouselCellEntity.swift │ │ ├── CarouselCellModel.swift │ │ ├── CarouselCellType.swift │ │ ├── Cell │ │ │ ├── CarouselCell.swift │ │ │ └── CarouselCell.xib │ │ ├── IconCarouselCellController │ │ │ ├── Cell │ │ │ │ ├── IconCarouselCell.swift │ │ │ │ └── IconCarouselCell.xib │ │ │ ├── IconCarouselCellController.swift │ │ │ ├── IconCarouselCellEntity.swift │ │ │ └── IconCarouselCellModel.swift │ │ └── ImageCarouselCellController │ │ │ ├── Cell │ │ │ ├── ImageCarouselCell.swift │ │ │ └── ImageCarouselCell.xib │ │ │ ├── ImageCarouselCellController.swift │ │ │ ├── ImageCarouselCellEntity.swift │ │ │ └── ImageCarouselCellModel.swift │ ├── CellControllerDisplayable.swift │ └── ContentCellController │ │ ├── ButtonFieldCellController │ │ ├── ButtonFieldCellController.swift │ │ ├── ButtonFieldCellDataSource.swift │ │ ├── ButtonFieldCellEntity.swift │ │ ├── ButtonFieldCellModel.swift │ │ └── Cell │ │ │ ├── ButtonFieldCell.swift │ │ │ └── ButtonFieldCell.xib │ │ ├── ContentCellControllerType.swift │ │ ├── ContentCellEntity.swift │ │ ├── DetailCellController │ │ ├── Cell │ │ │ ├── DetailCell.swift │ │ │ └── DetailCell.xib │ │ ├── DetailCellController.swift │ │ ├── DetailCellEntity.swift │ │ └── DetailCellModel.swift │ │ ├── ImageCellController │ │ ├── Cell │ │ │ ├── ImageCell.swift │ │ │ └── ImageCell.xib │ │ ├── ImageCellController.swift │ │ ├── ImageCellEntity.swift │ │ └── ImageCellModel.swift │ │ ├── SwitchFieldCellController │ │ ├── Cell │ │ │ ├── SwitchFieldCell.swift │ │ │ └── SwitchFieldCell.xib │ │ ├── SwitchFieldCellController.swift │ │ ├── SwitchFieldCellDataSource.swift │ │ ├── SwitchFieldCellEntity.swift │ │ └── SwitchFieldCellModel.swift │ │ ├── TextCellController │ │ ├── Cell │ │ │ ├── TextCell.swift │ │ │ └── TextCell.xib │ │ ├── TextCellController.swift │ │ ├── TextCellEntity.swift │ │ └── TextCellModel.swift │ │ └── TextFieldCellController │ │ ├── Cell │ │ ├── TextFieldCell.swift │ │ └── TextFieldCell.xib │ │ ├── TextFieldCellController.swift │ │ ├── TextFieldCellDataSource.swift │ │ ├── TextFieldCellEntity.swift │ │ └── TextFieldCellModel.swift │ ├── Cells │ └── Carousel │ │ ├── AdCarouselCell.swift │ │ └── AdCarouselCell.xib │ ├── Extension │ ├── UICollectionView+RegisterReuse.swift │ ├── UICollectionViewCell+Reusable.swift │ ├── UIColor+ColorFactory.swift │ ├── UITableView+RegisterReuse.swift │ ├── UITableViewCell+Reusable.swift │ ├── UITableViewHeaderFooterView+Reusable.swift │ ├── UIView+CornerRadius.swift │ └── UIViewController+AlertPresenting.swift │ ├── Info.plist │ ├── Protocol │ ├── Form │ │ ├── FormFieldContaining.swift │ │ ├── FormFieldHandling.swift │ │ └── FormFieldValueStoring.swift │ ├── HeightDefaultable.swift │ ├── NibLoadable.swift │ ├── Requesting.swift │ ├── Reusable.swift │ ├── SizeDefaultable.swift │ └── ViewStateManageable.swift │ ├── SectionController │ ├── ContentSectionController │ │ ├── ContentSectionController.swift │ │ ├── ContentSectionEntity.swift │ │ └── ContentSectionModel.swift │ ├── FormSectionController │ │ ├── Entity │ │ │ └── FormSectionEntity.swift │ │ ├── FormSectionController.swift │ │ ├── FormSectionDataSource.swift │ │ └── FormSectionModel.swift │ ├── SectionControllerDisplayable.swift │ ├── SectionControllerType.swift │ ├── SectionEntity.swift │ ├── SegmentSectionController │ │ ├── Entity │ │ │ ├── SegmentSectionEntity.swift │ │ │ └── SegmentSectionOptionEntity.swift │ │ ├── SegmentSectionController.swift │ │ ├── SegmentSectionDataSource.swift │ │ ├── SegmentSectionModel.swift │ │ ├── SegmentSectionOption.swift │ │ └── View │ │ │ ├── SegmentSectionHeaderView.swift │ │ │ └── SegmentSectionHeaderView.xib │ └── View │ │ ├── SectionTitleHeaderView.swift │ │ └── SectionTitleHeaderView.xib │ ├── Utilities │ ├── ColorFactory.swift │ ├── Navigation │ │ ├── NavigationDelegate.swift │ │ ├── NavigationDestinationType.swift │ │ └── ViewControllerFactory.swift │ ├── NavigationFactory.swift │ ├── Requester │ │ ├── Requester.swift │ │ └── Result.swift │ ├── State.swift │ └── Typealiases.swift │ ├── ViewController │ └── Main │ │ ├── MainEntity.swift │ │ ├── MainViewController.swift │ │ ├── MainViewDataSource.swift │ │ └── MainViewModel.swift │ ├── content │ └── main.json │ └── notes.txt ├── LICENSE ├── README.md └── Templates ├── Cell - Co, DaSo, Mo.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___Cell.swift ├── ___FILEBASENAME___Cell.xib ├── ___FILEBASENAME___CellController.swift ├── ___FILEBASENAME___CellDataSource.swift └── ___FILEBASENAME___CellModel.swift ├── Cell - Co, Mo.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___Cell.swift ├── ___FILEBASENAME___Cell.xib ├── ___FILEBASENAME___CellController.swift └── ___FILEBASENAME___CellModel.swift ├── Entity.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist └── ___FILEBASENAME___.swift ├── README.md ├── Section - Co, DaSo, Mo.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___Section.swift ├── ___FILEBASENAME___Section.xib ├── ___FILEBASENAME___SectionController.swift ├── ___FILEBASENAME___SectionDataSource.swift └── ___FILEBASENAME___SectionModel.swift ├── Section - Co, Mo.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___Section.swift ├── ___FILEBASENAME___Section.xib ├── ___FILEBASENAME___SectionController.swift └── ___FILEBASENAME___SectionModel.swift └── ViewController - DaSo, Mo.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___ViewController.swift ├── ___FILEBASENAME___ViewDataSource.swift └── ___FILEBASENAME___ViewModel.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /Example/nemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/nemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/nemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 1/4/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/ad1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "ad1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/ad1.imageset/ad1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/ad1.imageset/ad1.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/adventuretime.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "adventuretime.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/adventuretime.imageset/adventuretime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/adventuretime.imageset/adventuretime.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/avatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "andyy_avatar.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/avatar.imageset/andyy_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/avatar.imageset/andyy_avatar.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/bmo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "bmo.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/bmo.imageset/bmo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/bmo.imageset/bmo.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/city.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "city.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/city.imageset/city.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/city.imageset/city.jpg -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/dune.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "dune.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/dune.imageset/dune.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/dune.imageset/dune.jpg -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/finn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "finn.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/finn.imageset/finn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/finn.imageset/finn.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/icon1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/icon1.imageset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/icon1.imageset/icon.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/jake.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "jake.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/jake.imageset/jake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/jake.imageset/jake.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/lsp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lsp.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/lsp.imageset/lsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/lsp.imageset/lsp.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/marceline.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "marceline.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/marceline.imageset/marceline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/marceline.imageset/marceline.png -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/mountain.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "mountain.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/mountain.imageset/mountain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/mountain.imageset/mountain.jpg -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/pb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "pb.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/nemo/Assets.xcassets/pb.imageset/pb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Example/nemo/Assets.xcassets/pb.imageset/pb.png -------------------------------------------------------------------------------- /Example/nemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example/nemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/AdCarouselCellController/AdCarouselCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdCarouselCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class AdCarouselCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let model: AdCarouselCellModel 16 | let entity: AdCarouselCellEntity 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: AdCarouselCellEntity) { 21 | self.entity = entity 22 | self.model = AdCarouselCellModel(entity: entity) 23 | } 24 | 25 | // MARK: - Preparation 26 | 27 | func prepare(_ cell: AdCarouselCell) { 28 | prepareBindings(for: cell) 29 | cell.backgroundColor = model.backgroundColor 30 | cell.imageView.image = model.image 31 | cell.captionLabel.text = model.captionLabelText 32 | } 33 | 34 | private func prepareBindings(for cell: AdCarouselCell) { 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/AdCarouselCellController/AdCarouselCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdCarouselCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct AdCarouselCellEntity { 12 | 13 | let company: String 14 | let description: String 15 | let imageName: String 16 | let url: URL 17 | let backgroundColor: String? 18 | 19 | init?(json: JSON) { 20 | guard 21 | let company = json["company"] as? String, 22 | let description = json["description"] as? String, 23 | let urlString = json["url"] as? String, 24 | let url = URL(string: urlString), 25 | let imageName = json["image"] as? String 26 | else { return nil } 27 | 28 | self.company = company 29 | self.description = description 30 | self.url = url 31 | self.imageName = imageName 32 | self.backgroundColor = json["backgroundColor"] as? String 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/AdCarouselCellController/AdCarouselCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdCarouselCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class AdCarouselCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | private let entity: AdCarouselCellEntity 16 | 17 | // MARK: - Initializer 18 | 19 | init(entity: AdCarouselCellEntity) { 20 | self.entity = entity 21 | } 22 | 23 | // MARK: - Computed Properties 24 | 25 | var captionLabelText: String { 26 | return entity.company 27 | } 28 | 29 | var backgroundColor: UIColor { 30 | if let color = entity.backgroundColor{ 31 | return .color(forColor: color) 32 | } 33 | else { 34 | return .defaultBackgroundColor 35 | } 36 | } 37 | 38 | var image: UIImage? { 39 | return UIImage(named: entity.imageName) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/AdCarouselCellController/Cell/AdCarouselCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdCarouselCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 1/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AdCarouselCell: UICollectionViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var captionLabel: UILabel! { 14 | didSet { 15 | captionLabel.textColor = .white 16 | captionLabel.font = .boldSystemFont(ofSize: 16) 17 | } 18 | } 19 | 20 | @IBOutlet weak var imageView: UIImageView! { didSet { 21 | imageView.contentMode = .scaleAspectFill 22 | } 23 | } 24 | 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | updateCornerRadii() 28 | } 29 | } 30 | 31 | extension AdCarouselCell: SizeDefaultable { 32 | 33 | static var defaultSize: CGSize { 34 | return .init(width: 200, height: CarouselCell.defaultHeight) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/AdCarouselCellController/Cell/AdCarouselCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/CarouselCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarouselCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CarouselCellController: NSObject { 12 | 13 | // MARK: - Properties 14 | 15 | let dataSource: CarouselCellDataSource 16 | 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: CarouselCellEntity) { 21 | self.dataSource = CarouselCellDataSource(entity: entity) 22 | } 23 | 24 | 25 | // MARK: - Computed Properties 26 | 27 | var model: CarouselCellModel { 28 | return dataSource.model 29 | } 30 | 31 | var entity: CarouselCellEntity { 32 | return dataSource.entity 33 | } 34 | 35 | 36 | // MARK: - Preparation 37 | 38 | func prepare(_ cell: CarouselCell) { 39 | prepareBindings(for: cell) 40 | 41 | var collectionView: UICollectionView { return cell.collectionView } 42 | collectionView.dataSource = self 43 | collectionView.delegate = self 44 | collectionView.backgroundColor = model.backgroundColor 45 | collectionView.collectionViewLayout = model.layout 46 | collectionView.showsHorizontalScrollIndicator = false 47 | collectionView.register(AdCarouselCell.self) 48 | collectionView.register(IconCarouselCell.self) 49 | collectionView.register(ImageCarouselCell.self) 50 | } 51 | 52 | private func prepareBindings(for cell: CarouselCell) { 53 | 54 | } 55 | } 56 | 57 | extension CarouselCellController: UICollectionViewDataSource { 58 | 59 | func numberOfSections(in collectionView: UICollectionView) -> Int { 60 | return dataSource.numberOfSections 61 | } 62 | 63 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 64 | return dataSource.numberOfCells(inSection: section) 65 | } 66 | 67 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 68 | switch dataSource.cellController(for: indexPath) { 69 | case .ad(let cellController): 70 | let cell: AdCarouselCell = collectionView.dequeueReusableCell(for: indexPath) 71 | cellController.prepare(cell) 72 | return cell 73 | 74 | case .icon(let cellController): 75 | let cell: IconCarouselCell = collectionView.dequeueReusableCell(for: indexPath) 76 | cellController.prepare(cell) 77 | return cell 78 | 79 | case .image(let cellController): 80 | let cell: ImageCarouselCell = collectionView.dequeueReusableCell(for: indexPath) 81 | cellController.prepare(cell) 82 | return cell 83 | } 84 | } 85 | } 86 | 87 | extension CarouselCellController: UICollectionViewDelegate { 88 | 89 | } 90 | 91 | extension CarouselCellController: UICollectionViewDelegateFlowLayout { 92 | 93 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 94 | 95 | let size: CGSize = { 96 | switch dataSource.cellController(for: indexPath) { 97 | case .ad: 98 | return AdCarouselCell.defaultSize 99 | case .icon: 100 | return IconCarouselCell.defaultSize 101 | case .image: 102 | return ImageCarouselCell.defaultSize 103 | } 104 | }() 105 | 106 | return .init( 107 | width: size.width, 108 | height: size.height - (CarouselCell.inset * 2)) 109 | } 110 | 111 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 112 | 113 | return CarouselCell.edgeInsets 114 | } 115 | 116 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 117 | 118 | return CarouselCell.inset 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/CarouselCellControllerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarouselCellControllerType.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 1/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum CarouselCellControllerType { 12 | 13 | case ad(AdCarouselCellController) 14 | case icon(IconCarouselCellController) 15 | case image(ImageCarouselCellController) 16 | 17 | 18 | init(entity: CarouselCellType) { 19 | switch entity { 20 | case .ad(let entity): 21 | self = .ad(.init(entity: entity)) 22 | case .icon(let entity): 23 | self = .icon(.init(entity: entity)) 24 | case .image(let entity): 25 | self = .image(.init(entity: entity)) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/CarouselCellDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarouselCellDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CarouselCellDataSource { //: StateManageable { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: CarouselCellEntity 16 | var model: CarouselCellModel 17 | var cellControllers: [CarouselCellControllerType] 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: CarouselCellEntity) { 22 | self.entity = entity 23 | self.model = CarouselCellModel(entity: entity) 24 | self.cellControllers = entity.entities 25 | .map { CarouselCellControllerType(entity: $0) } 26 | } 27 | // MARK: - Computered Properties 28 | 29 | var numberOfSections: Int { 30 | return 1 31 | } 32 | 33 | func numberOfCells(inSection section: Int) -> Int { 34 | return cellControllers.count 35 | } 36 | 37 | func cellController(for indexPath: IndexPath) -> CarouselCellControllerType { 38 | return cellControllers[indexPath.row] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/CarouselCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarouselCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CarouselCellEntity { 12 | 13 | let backgroundColor: String? 14 | let entities: [CarouselCellType] 15 | 16 | init?(json: JSON) { 17 | guard 18 | let entities = json["cells"] as? [JSON] 19 | else { return nil } 20 | 21 | self.backgroundColor = json["backgroundColor"] as? String 22 | self.entities = entities.compactMap { CarouselCellType(json: $0) } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/CarouselCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarouselCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CarouselCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: CarouselCellEntity 16 | let layout: UICollectionViewLayout 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: CarouselCellEntity) { 22 | self.entity = entity 23 | self.layout = UICollectionViewFlowLayout() 24 | (self.layout as? UICollectionViewFlowLayout)?.scrollDirection = .horizontal 25 | } 26 | 27 | // MARK: - Layout 28 | 29 | func minimumInteritemSpacing(forSection section: Int) -> CGFloat { 30 | return 8 31 | } 32 | 33 | // MARK: - Computed Properties 34 | 35 | var backgroundColor: UIColor { 36 | if let color = entity.backgroundColor{ 37 | return .color(forColor: color) 38 | } 39 | else { 40 | return .defaultBackgroundColor 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/CarouselCellType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarouselCellType.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 1/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum CarouselCellType { 12 | 13 | case ad(AdCarouselCellEntity) 14 | case icon(IconCarouselCellEntity) 15 | case image(ImageCarouselCellEntity) 16 | 17 | private enum Types: String { 18 | case ad, icon, image 19 | } 20 | 21 | init?(json: JSON) { 22 | guard 23 | let typeString = json["type"] as? String, 24 | let type = Types(rawValue: typeString) 25 | else { assertionFailure("Invalid CellController 'type' key passed"); return nil } 26 | 27 | switch type { 28 | case .ad: 29 | guard let entity = AdCarouselCellEntity(json: json) else { fallthrough } 30 | self = .ad(entity) 31 | case .icon: 32 | guard let entity = IconCarouselCellEntity(json: json) else { fallthrough } 33 | self = .icon(entity) 34 | case .image: 35 | guard let entity = ImageCarouselCellEntity(json: json) else { fallthrough } 36 | self = .image(entity) 37 | default: 38 | assertionFailure("Invalid CellController identifier passed: \(type)") 39 | return nil 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/Cell/CarouselCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BetaCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CarouselCell: UITableViewCell, NibLoadable { 12 | 13 | static var inset: CGFloat { 14 | return 8 15 | } 16 | 17 | static var edgeInsets: UIEdgeInsets { 18 | return .init(top: inset, left: inset * 2, bottom: inset, right: inset * 2) 19 | } 20 | 21 | @IBOutlet weak var collectionView: UICollectionView! 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | } 26 | } 27 | 28 | extension CarouselCell: HeightDefaultable { 29 | static var defaultHeight: CGFloat { 30 | return 200 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/Cell/CarouselCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/IconCarouselCellController/Cell/IconCarouselCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconCarouselCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class IconCarouselCell: UICollectionViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var label: UILabel! { 14 | didSet { 15 | label.font = .boldSystemFont(ofSize: 16) 16 | } 17 | } 18 | 19 | @IBOutlet weak var iconImageView: UIImageView! { 20 | didSet { 21 | iconImageView.contentMode = .scaleAspectFit 22 | } 23 | } 24 | 25 | 26 | override func awakeFromNib() { 27 | super.awakeFromNib() 28 | } 29 | } 30 | 31 | extension IconCarouselCell: SizeDefaultable { 32 | 33 | static var defaultSize: CGSize { 34 | return .init(width: 100, height: CarouselCell.defaultHeight) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/IconCarouselCellController/Cell/IconCarouselCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/IconCarouselCellController/IconCarouselCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconCarouselCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class IconCarouselCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let model: IconCarouselCellModel 16 | let entity: IconCarouselCellEntity 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: IconCarouselCellEntity) { 22 | self.entity = entity 23 | self.model = IconCarouselCellModel(entity: entity) 24 | } 25 | 26 | 27 | // MARK: - Computed Properties 28 | 29 | 30 | 31 | // MARK: - Preparation 32 | 33 | func prepare(_ cell: IconCarouselCell) { 34 | prepareBindings(for: cell) 35 | cell.backgroundColor = model.backgroundColor 36 | cell.iconImageView.image = model.iconImage 37 | cell.label.text = model.textLabelText 38 | } 39 | 40 | private func prepareBindings(for cell: IconCarouselCell) { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/IconCarouselCellController/IconCarouselCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconCarouselCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct IconCarouselCellEntity { 12 | 13 | let text: String 14 | let iconName: String 15 | let backgroundColor: String? 16 | 17 | init?(json: JSON) { 18 | guard 19 | let text = json["text"] as? String, 20 | let iconName = json["icon"] as? String 21 | else { return nil } 22 | 23 | self.text = text 24 | self.iconName = iconName 25 | self.backgroundColor = json["backgroundColor"] as? String 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/IconCarouselCellController/IconCarouselCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconCarouselCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class IconCarouselCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | private let entity: IconCarouselCellEntity 16 | 17 | // MARK: - Initializer 18 | 19 | init(entity: IconCarouselCellEntity) { 20 | self.entity = entity 21 | } 22 | 23 | // MARK: - Computed Properties 24 | 25 | var backgroundColor: UIColor { 26 | if let color = entity.backgroundColor{ 27 | return .color(forColor: color) 28 | } 29 | else { 30 | return .defaultBackgroundColor 31 | } 32 | } 33 | 34 | var iconImage: UIImage? { 35 | return UIImage(named: entity.iconName) 36 | } 37 | 38 | var textLabelText: String { 39 | return entity.text 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/ImageCarouselCellController/Cell/ImageCarouselCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCarouselCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageCarouselCell: UICollectionViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var imageView: UIImageView! { didSet { 14 | imageView.contentMode = .scaleAspectFill 15 | } 16 | } 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | updateCornerRadii() 21 | } 22 | } 23 | 24 | extension ImageCarouselCell: SizeDefaultable { 25 | 26 | static var defaultSize: CGSize { 27 | return .init(width: 200, height: CarouselCell.defaultHeight) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/ImageCarouselCellController/Cell/ImageCarouselCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/ImageCarouselCellController/ImageCarouselCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCarouselCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ImageCarouselCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let model: ImageCarouselCellModel 16 | let entity: ImageCarouselCellEntity 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: ImageCarouselCellEntity) { 21 | self.entity = entity 22 | self.model = ImageCarouselCellModel(entity: entity) 23 | } 24 | 25 | // MARK: - Preparation 26 | 27 | func prepare(_ cell: ImageCarouselCell) { 28 | cell.backgroundColor = model.backgroundColor 29 | cell.imageView.image = model.image 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/ImageCarouselCellController/ImageCarouselCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ImageCarouselCellEntity { 12 | 13 | let caption: String 14 | let imageName: String 15 | let backgroundColor: String? 16 | 17 | init?(json: JSON) { 18 | guard 19 | let caption = json["caption"] as? String, 20 | let imageName = json["image"] as? String 21 | else { return nil } 22 | 23 | self.caption = caption 24 | self.imageName = imageName 25 | self.backgroundColor = json["backgroundColor"] as? String 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CarouselCellController/ImageCarouselCellController/ImageCarouselCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCarouselCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ImageCarouselCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | private let entity: ImageCarouselCellEntity 16 | 17 | // MARK: - Initializer 18 | 19 | init(entity: ImageCarouselCellEntity) { 20 | self.entity = entity 21 | } 22 | 23 | // MARK: - Computed Properties 24 | 25 | var backgroundColor: UIColor { 26 | if let color = entity.backgroundColor{ 27 | return .color(forColor: color) 28 | } 29 | else { 30 | return .defaultBackgroundColor 31 | } 32 | } 33 | 34 | var image: UIImage? { 35 | return UIImage(named: entity.imageName) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example/nemo/CellController/CellControllerDisplayable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ControllerDisplayable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol CellControllerDisplayable { 13 | 14 | func registerCells(for tableView: UITableView) 15 | func tableView(_ tableView: UITableView, estimatedCellHeightFor cellController: ContentCellControllerType) -> CGFloat 16 | func tableView(_ tableView: UITableView, cellHeightFor cellController: ContentCellControllerType) -> CGFloat 17 | func tableView(_ tableView: UITableView, cellFor cellController: ContentCellControllerType, at indexPath: IndexPath) -> UITableViewCell 18 | } 19 | 20 | extension CellControllerDisplayable where Self: UIViewController { 21 | 22 | func registerCells(for tableView: UITableView) { 23 | tableView.register(TextCell.self) 24 | tableView.register(DetailCell.self) 25 | tableView.register(ImageCell.self) 26 | tableView.register(CarouselCell.self) 27 | tableView.register(TextFieldCell.self) 28 | tableView.register(SwitchFieldCell.self) 29 | tableView.register(ButtonFieldCell.self) 30 | } 31 | 32 | func tableView(_ tableView: UITableView, cellFor cellController: ContentCellControllerType, at indexPath: IndexPath) -> UITableViewCell { 33 | 34 | switch cellController { 35 | case .text(let cellController): 36 | let cell: TextCell = tableView.dequeueReusableCell(for: indexPath) 37 | cellController.prepare(cell) 38 | return cell 39 | 40 | case .detail(let cellController): 41 | let cell: DetailCell = tableView.dequeueReusableCell(for: indexPath) 42 | cellController.prepare(cell) 43 | return cell 44 | 45 | case .image(let cellController): 46 | let cell: ImageCell = tableView.dequeueReusableCell(for: indexPath) 47 | cellController.prepare(cell) 48 | return cell 49 | 50 | case .carousel(let cellController): 51 | let cell: CarouselCell = tableView.dequeueReusableCell(for: indexPath) 52 | cellController.prepare(cell) 53 | return cell 54 | 55 | case .textField(let cellController): 56 | let cell: TextFieldCell = tableView.dequeueReusableCell(for: indexPath) 57 | cellController.prepare(cell) 58 | return cell 59 | 60 | case .switchField(let cellController): 61 | let cell: SwitchFieldCell = tableView.dequeueReusableCell(for: indexPath) 62 | cellController.prepare(cell) 63 | return cell 64 | 65 | case .buttonField(let cellController): 66 | let cell: ButtonFieldCell = tableView.dequeueReusableCell(for: indexPath) 67 | cellController.prepare(cell) 68 | return cell 69 | } 70 | } 71 | 72 | func tableView(_ tableView: UITableView, estimatedCellHeightFor cellController: ContentCellControllerType) -> CGFloat { 73 | switch cellController { 74 | case .detail: 75 | return DetailCell.defaultHeight 76 | default: 77 | return self.tableView(tableView, cellHeightFor: cellController) 78 | } 79 | } 80 | 81 | func tableView(_ tableView: UITableView, cellHeightFor cellController: ContentCellControllerType) -> CGFloat { 82 | switch cellController { 83 | case .text: 84 | return TextCell.defaultHeight 85 | case .detail: 86 | return tableView.estimatedRowHeight 87 | case .image: 88 | return ImageCell.defaultHeight 89 | case .carousel: 90 | return CarouselCell.defaultHeight 91 | case .textField: 92 | return TextFieldCell.defaultHeight 93 | case .switchField: 94 | return SwitchFieldCell.defaultHeight 95 | case .buttonField: 96 | return ButtonFieldCell.defaultHeight 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ButtonFieldCellController/ButtonFieldCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonFieldCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ButtonFieldCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let dataSource: ButtonFieldCellDataSource 16 | weak var delegate: FormCellControllerDelegate? 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: ButtonFieldCellEntity) { 22 | self.dataSource = ButtonFieldCellDataSource(entity: entity) 23 | } 24 | 25 | 26 | // MARK: - Computed Properties 27 | 28 | var model: ButtonFieldCellModel { 29 | return dataSource.model 30 | } 31 | 32 | var entity: ButtonFieldCellEntity { 33 | return dataSource.entity 34 | } 35 | 36 | 37 | // MARK: - Preparation 38 | 39 | func prepare(_ cell: ButtonFieldCell) { 40 | cell.button.setTitle(model.buttonTitleText, for: .normal) 41 | cell.button.backgroundColor = model.buttonColor 42 | cell.button.addTarget(self, action: #selector(formElementDidUpdate(sender:)), for: .touchUpInside) 43 | cell.button.tintColor = model.buttonLabelColor 44 | cell.activityIndicatorView.color = model.buttonLabelColor 45 | cell.button.isEnabled = model.isEnabled 46 | model.isActivityIndicatorAnimating 47 | ? cell.activityIndicatorView.startAnimating() 48 | : cell.activityIndicatorView.stopAnimating() 49 | } 50 | } 51 | 52 | extension ButtonFieldCellController: FormFieldHandling { 53 | @objc func formElementDidUpdate(sender: Any) { 54 | let type: FormFieldType = entity.property == .submit 55 | ? .submit 56 | : .cancel 57 | 58 | delegate?.formDidUpdate(for: type) 59 | } 60 | 61 | func clearFormField() { return } 62 | 63 | func setInteractionEnabled(_ isInteractionEnabled: Bool) { 64 | model.state = isInteractionEnabled ? .enabled : .disabled 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ButtonFieldCellController/ButtonFieldCellDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonFieldCellDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ButtonFieldCellDataSource { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: ButtonFieldCellEntity 16 | var model: ButtonFieldCellModel 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: ButtonFieldCellEntity) { 21 | self.entity = entity 22 | self.model = ButtonFieldCellModel(entity: entity) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ButtonFieldCellController/ButtonFieldCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonFieldCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ButtonFieldCellEntity { 12 | 13 | enum ButtonType: String { 14 | case submit, cancel 15 | } 16 | 17 | let label: String 18 | let property: ButtonType 19 | let backgroundColor: String? 20 | let buttonColor: String? 21 | let buttonLabelColor: String? 22 | 23 | init?(json: JSON) { 24 | guard 25 | let propertyString = json["property"] as? String, 26 | let property = ButtonType(rawValue: propertyString) 27 | else { return nil } 28 | 29 | self.property = property 30 | self.buttonColor = json["buttonColor"] as? String 31 | self.buttonLabelColor = json["buttonLabelColor"] as? String 32 | self.label = (json["label"] as? String) 33 | ?? property.rawValue.capitalized 34 | self.backgroundColor = json["backgroundColor"] as? String 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ButtonFieldCellController/ButtonFieldCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonFieldCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ButtonFieldCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | let entity: ButtonFieldCellEntity 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: ButtonFieldCellEntity) { 22 | self.entity = entity 23 | self.state = .enabled 24 | } 25 | 26 | // MARK: - Computed Properties 27 | 28 | var buttonTitleText: String { 29 | return isEnabled ? entity.label : "Please wait..." 30 | } 31 | 32 | var backgroundColor: UIColor { 33 | return ColorFactory(string: entity.backgroundColor ?? "").color 34 | } 35 | 36 | var buttonColor: UIColor { 37 | return ColorFactory(string: entity.buttonColor ?? "").color 38 | } 39 | 40 | var buttonLabelColor: UIColor { 41 | return ColorFactory(string: entity.buttonLabelColor ?? "").color 42 | } 43 | 44 | var isEnabled: Bool { 45 | return state == .enabled 46 | } 47 | 48 | var isActivityIndicatorAnimating: Bool { 49 | return !isEnabled 50 | } 51 | } 52 | 53 | extension ButtonFieldCellModel: ViewStateManageable { 54 | enum ViewState { 55 | case enabled, disabled 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ButtonFieldCellController/Cell/ButtonFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonFieldCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ButtonFieldCell: UITableViewCell, NibLoadable { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | // MARK: - IBOutlets 17 | 18 | @IBOutlet weak var button: UIButton! { 19 | didSet { 20 | button.titleLabel?.font = .boldSystemFont(ofSize: 16) 21 | button.updateCornerRadii() 22 | } 23 | } 24 | 25 | @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! { 26 | didSet { 27 | activityIndicatorView.hidesWhenStopped = true 28 | } 29 | } 30 | 31 | // MARK: - View Lifecycle 32 | 33 | override func awakeFromNib() { 34 | super.awakeFromNib() 35 | } 36 | 37 | 38 | // MARK: - Reuse 39 | 40 | override func prepareForReuse() { 41 | super.prepareForReuse() 42 | } 43 | } 44 | 45 | extension ButtonFieldCell: HeightDefaultable { 46 | static var defaultHeight: CGFloat { 47 | return 60 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ButtonFieldCellController/Cell/ButtonFieldCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ContentCellControllerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 29/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ContentCellControllerType { 12 | 13 | // Content 14 | case text(TextCellController) 15 | case detail(DetailCellController) 16 | case image(ImageCellController) 17 | case carousel(CarouselCellController) 18 | 19 | // Form 20 | case textField(TextFieldCellController) 21 | case switchField(SwitchFieldCellController) 22 | case buttonField(ButtonFieldCellController) 23 | 24 | init(cellEntity: ContentCellEntity) { 25 | switch cellEntity { 26 | case .text(let entity): 27 | self = .text(.init(entity: entity)) 28 | case .detail(let entity): 29 | self = .detail(.init(entity: entity)) 30 | case .image(let entity): 31 | self = .image(.init(entity: entity)) 32 | case .carousel(let entity): 33 | self = .carousel(.init(entity: entity)) 34 | case .textField(let entity): 35 | self = .textField(.init(entity: entity)) 36 | case .switchField(let entity): 37 | self = .switchField(.init(entity: entity)) 38 | case .buttonField(let entity): 39 | self = .buttonField(.init(entity: entity)) 40 | } 41 | } 42 | 43 | func setNavigationDelegate(_ navigationDelegate: NavigationDelegate) { 44 | switch self { 45 | case .text(let cellController): 46 | cellController.navigationDelegate = navigationDelegate 47 | case .image(let cellController): 48 | cellController.navigationDelegate = navigationDelegate 49 | default: 50 | return 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ContentCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 29/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ContentCellEntity { 12 | 13 | // Content 14 | case text(TextCellEntity) 15 | case detail(DetailCellEntity) 16 | case image(ImageCellEntity) 17 | case carousel(CarouselCellEntity) 18 | 19 | // Form 20 | case textField(TextFieldCellEntity) 21 | case switchField(SwitchFieldCellEntity) 22 | case buttonField(ButtonFieldCellEntity) 23 | 24 | private enum Types: String { 25 | case text, detail, image, carousel, textField, switchField, buttonField 26 | } 27 | 28 | init?(json: JSON) { 29 | guard 30 | let typeString = json["type"] as? String, 31 | let type = Types(rawValue: typeString) 32 | else { assertionFailure("Invalid CellController 'type' key passed"); return nil } 33 | 34 | switch type { 35 | case .text: 36 | guard let entity = TextCellEntity(json: json) else { fallthrough } 37 | self = .text(entity) 38 | case .detail: 39 | guard let entity = DetailCellEntity(json: json) else { fallthrough } 40 | self = .detail(entity) 41 | case .image: 42 | guard let entity = ImageCellEntity(json: json) else { fallthrough } 43 | self = .image(entity) 44 | case .carousel: 45 | guard let entity = CarouselCellEntity(json: json) else { fallthrough } 46 | self = .carousel(entity) 47 | case .textField: 48 | guard let entity = TextFieldCellEntity(json: json) else { fallthrough } 49 | self = .textField(entity) 50 | case .switchField: 51 | guard let entity = SwitchFieldCellEntity(json: json) else { fallthrough } 52 | self = .switchField(entity) 53 | case .buttonField: 54 | guard let entity = ButtonFieldCellEntity(json: json) else { fallthrough } 55 | self = .buttonField(entity) 56 | default: 57 | assertionFailure("Invalid CellController identifier passed: \(type)") 58 | return nil 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/DetailCellController/Cell/DetailCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GammaCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailCell: UITableViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var headingLabel: UILabel! { 14 | didSet { 15 | headingLabel.numberOfLines = 0 16 | } 17 | } 18 | @IBOutlet weak var detailLabel: UILabel! { 19 | didSet { 20 | detailLabel.numberOfLines = 0 21 | } 22 | } 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | } 27 | } 28 | 29 | extension DetailCell: HeightDefaultable { 30 | static var defaultHeight: CGFloat { 31 | return 100 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/DetailCellController/Cell/DetailCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/DetailCellController/DetailCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class DetailCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let model: DetailCellModel 16 | let entity: DetailCellEntity 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: DetailCellEntity) { 22 | self.entity = entity 23 | self.model = DetailCellModel(entity: entity) 24 | } 25 | 26 | 27 | // MARK: - Preparation 28 | 29 | func prepare(_ cell: DetailCell) { 30 | prepareBindings(for: cell) 31 | cell.headingLabel.text = model.headingLabelText 32 | cell.headingLabel.font = model.headingLabelFont 33 | cell.detailLabel.text = model.detailLabelText 34 | cell.backgroundColor = model.backgroundColor 35 | } 36 | 37 | private func prepareBindings(for cell: DetailCell) { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/DetailCellController/DetailCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DetailCellEntity { 12 | let detail: String 13 | let heading: String 14 | let backgroundColor: String? 15 | 16 | init?(json: JSON) { 17 | guard 18 | let heading = json["heading-text"] as? String, 19 | let detail = json["detail-text"] as? String 20 | else { return nil } 21 | 22 | self.heading = heading 23 | self.detail = detail 24 | self.backgroundColor = json["backgroundColor"] as? String 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/DetailCellController/DetailCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class DetailCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | private let entity: DetailCellEntity 16 | 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: DetailCellEntity) { 21 | self.entity = entity 22 | } 23 | 24 | 25 | // MARK: - Computed Properties 26 | 27 | var headingLabelText: String { 28 | return entity.heading 29 | } 30 | 31 | var headingLabelFont: UIFont { 32 | return .boldSystemFont(ofSize: 22) 33 | } 34 | 35 | var detailLabelText: String { 36 | return entity.detail 37 | } 38 | 39 | var backgroundColor: UIColor { 40 | if let color = entity.backgroundColor{ 41 | return .color(forColor: color) 42 | } 43 | else { 44 | return .defaultBackgroundColor 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ImageCellController/Cell/ImageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeltaCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageCell: UITableViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var coverImageView: UIImageView! { didSet { 14 | coverImageView.contentMode = .scaleAspectFill 15 | coverImageView.clipsToBounds = true 16 | } 17 | } 18 | 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | } 22 | } 23 | 24 | extension ImageCell: HeightDefaultable { 25 | static var defaultHeight: CGFloat { 26 | return 100 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ImageCellController/Cell/ImageCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ImageCellController/ImageCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ImageCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let model: ImageCellModel 16 | let entity: ImageCellEntity 17 | weak var navigationDelegate: NavigationDelegate? 18 | let tapGestureRecognizer: UITapGestureRecognizer 19 | 20 | // MARK: - Initializer 21 | 22 | init(entity: ImageCellEntity) { 23 | self.entity = entity 24 | self.model = ImageCellModel(entity: entity) 25 | self.tapGestureRecognizer = UITapGestureRecognizer() 26 | } 27 | 28 | 29 | // MARK: - Preparation 30 | 31 | func prepare(_ cell: ImageCell) { 32 | cell.coverImageView.image = model.image 33 | cell.backgroundColor = model.backgroundColor 34 | addGestureRecognizer(to: cell.coverImageView) 35 | } 36 | 37 | private func addGestureRecognizer(to view: UIView) { 38 | guard view.gestureRecognizers == nil else { return } 39 | tapGestureRecognizer.addTarget(self, 40 | action: #selector(navigationWillBegin(sender:))) 41 | view.addGestureRecognizer(tapGestureRecognizer) 42 | view.isUserInteractionEnabled = true 43 | } 44 | } 45 | 46 | extension ImageCellController: NavigationRequesting { 47 | 48 | @objc func navigationWillBegin(sender: Any) { 49 | navigationDelegate?.navigate(to: .gallery(galleryID: 123, title: "Ramen")) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ImageCellController/ImageCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ImageCellEntity { 12 | 13 | let caption: String 14 | let imageName: String 15 | let backgroundColor: String? 16 | 17 | init?(json: JSON) { 18 | guard 19 | let caption = json["caption"] as? String, 20 | let imageName = json["image"] as? String 21 | else { return nil } 22 | 23 | self.caption = caption 24 | self.imageName = imageName 25 | self.backgroundColor = json["backgroundColor"] as? String 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/ImageCellController/ImageCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ImageCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | private let entity: ImageCellEntity 16 | 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: ImageCellEntity) { 21 | self.entity = entity 22 | } 23 | 24 | 25 | // MARK: - Computed Properties 26 | 27 | var captianLabelText: String { 28 | return entity.caption 29 | } 30 | 31 | var backgroundColor: UIColor { 32 | if let color = entity.backgroundColor{ 33 | return .color(forColor: color) 34 | } 35 | else { 36 | return .defaultBackgroundColor 37 | } 38 | } 39 | 40 | var image: UIImage? { 41 | return UIImage(named: entity.imageName) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/SwitchFieldCellController/Cell/SwitchFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SwitchFieldCell: UITableViewCell, NibLoadable { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | // MARK: - IBOutlets 17 | 18 | @IBOutlet weak var label: UILabel! 19 | @IBOutlet weak var `switch`: UISwitch! 20 | 21 | 22 | // MARK: - View Lifecycle 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | } 27 | 28 | 29 | // MARK: - Reuse 30 | 31 | override func prepareForReuse() { 32 | super.prepareForReuse() 33 | } 34 | } 35 | 36 | extension SwitchFieldCell: HeightDefaultable { 37 | static var defaultHeight: CGFloat { 38 | return 44 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/SwitchFieldCellController/Cell/SwitchFieldCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/SwitchFieldCellController/SwitchFieldCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SwitchFieldCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: SwitchFieldCellEntity 16 | let dataSource: SwitchFieldCellDataSource 17 | weak var delegate: FormCellControllerDelegate? 18 | 19 | 20 | // MARK: - Initializer 21 | 22 | init(entity: SwitchFieldCellEntity) { 23 | self.dataSource = SwitchFieldCellDataSource(entity: entity) 24 | self.entity = entity 25 | } 26 | 27 | 28 | // MARK: - Computed Properties 29 | 30 | var model: SwitchFieldCellModel { 31 | return dataSource.model 32 | } 33 | 34 | 35 | // MARK: - Preparation 36 | 37 | func prepare(_ cell: SwitchFieldCell) { 38 | cell.label.text = model.labelText 39 | cell.switch.isOn = model.isOn 40 | cell.switch.addTarget(self, action: #selector(formElementDidUpdate(sender:)), for: .valueChanged) 41 | cell.switch.isEnabled = model.isEnabled 42 | 43 | delegate?.formDidUpdate( 44 | for: .field(property: dataSource.property, value: model.isOn)) 45 | } 46 | } 47 | 48 | extension SwitchFieldCellController: FormFieldHandling { 49 | 50 | @objc func formElementDidUpdate(sender: Any) { 51 | guard let sender = sender as? UISwitch else { return } 52 | 53 | dataSource.setFormValue(sender.isOn) 54 | delegate?.formDidUpdate( 55 | for: .field(property: dataSource.property, value: sender.isOn)) 56 | } 57 | 58 | func clearFormField() { 59 | dataSource.setFormValue(nil) 60 | } 61 | 62 | func setInteractionEnabled(_ isInteractionEnabled: Bool) { 63 | model.state = isInteractionEnabled ? .enabled : .disabled 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/SwitchFieldCellController/SwitchFieldCellDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCellDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SwitchFieldCellDataSource { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: SwitchFieldCellEntity 16 | var model: SwitchFieldCellModel 17 | 18 | internal (set) var formValue: Any? 19 | 20 | // MARK: - Initializer 21 | 22 | init(entity: SwitchFieldCellEntity) { 23 | self.entity = entity 24 | self.model = SwitchFieldCellModel(entity: entity) 25 | self.formValue = entity.defaultValue 26 | } 27 | 28 | 29 | // MARK: - Computered Properties 30 | 31 | var property: String { 32 | return entity.property 33 | } 34 | } 35 | 36 | extension SwitchFieldCellDataSource: FormFieldValueStoring { 37 | 38 | func setFormValue(_ value: Any?) { 39 | formValue = value 40 | model.value = formValue as? Bool 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/SwitchFieldCellController/SwitchFieldCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SwitchFieldCellEntity { 12 | 13 | let property: String 14 | let label: String 15 | let defaultValue: Bool 16 | 17 | init?(json: JSON) { 18 | guard 19 | let label = json["label"] as? String, 20 | let property = json["property"] as? String 21 | else { return nil } 22 | 23 | self.label = label 24 | self.property = property 25 | self.defaultValue = json["default-value"] as? Bool ?? false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/SwitchFieldCellController/SwitchFieldCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SwitchFieldCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: SwitchFieldCellEntity 16 | var value: Bool? 17 | var state: ViewState 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: SwitchFieldCellEntity) { 22 | self.entity = entity 23 | self.state = .enabled 24 | } 25 | 26 | // MARK: - Computed Properties 27 | 28 | var labelText: String { 29 | return entity.label 30 | } 31 | 32 | var isOn: Bool { 33 | return value ?? entity.defaultValue 34 | } 35 | 36 | var isEnabled: Bool { 37 | return state == .enabled 38 | } 39 | } 40 | 41 | extension SwitchFieldCellModel: ViewStateManageable { 42 | enum ViewState { 43 | case enabled, disabled 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextCellController/Cell/TextCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphaCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TextCell: UITableViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var button: UIButton! 14 | @IBOutlet weak var headingLabel: UILabel! { 15 | didSet { 16 | headingLabel.numberOfLines = 0 17 | headingLabel.text = nil 18 | } 19 | } 20 | @IBOutlet weak var contentImageView: UIImageView! { 21 | didSet { 22 | contentImageView.backgroundColor = .lightGray 23 | contentImageView.updateCornerRadii(defaultCornerRadius/2) 24 | } 25 | } 26 | 27 | override func awakeFromNib() { 28 | super.awakeFromNib() 29 | } 30 | } 31 | 32 | extension TextCell: HeightDefaultable { 33 | static var defaultHeight: CGFloat { 34 | return 80 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextCellController/TextCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TextCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let model: TextCellModel 16 | let entity: TextCellEntity 17 | weak var navigationDelegate: NavigationDelegate? 18 | 19 | 20 | // MARK: - Initializer 21 | 22 | init(entity: TextCellEntity) { 23 | self.entity = entity 24 | self.model = TextCellModel(entity: entity) 25 | } 26 | 27 | 28 | // MARK: - Preparation 29 | 30 | func prepare(_ cell: TextCell) { 31 | cell.headingLabel.attributedText = model.headingLabelAttributedText 32 | cell.contentImageView.image = model.image 33 | cell.backgroundColor = model.backgroundColor 34 | cell.button.addTarget(self, 35 | action: #selector(navigationWillBegin(sender:)), 36 | for: .touchUpInside) 37 | } 38 | } 39 | 40 | extension TextCellController: NavigationRequesting { 41 | 42 | @objc func navigationWillBegin(sender: Any) { 43 | navigationDelegate?.navigate(to: .article(articleID: 123, title: "Boo")) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextCellController/TextCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct TextCellEntity { 12 | 13 | let text: String 14 | let subtext: String? 15 | let backgroundColor: String? 16 | let image: String? 17 | 18 | init?(json: JSON) { 19 | guard 20 | let text = json["text"] as? String 21 | else { return nil } 22 | 23 | self.text = text 24 | self.subtext = json["subtext"] as? String 25 | self.backgroundColor = json["backgroundColor"] as? String 26 | self.image = json["image"] as? String 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextCellController/TextCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TextCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | private let entity: TextCellEntity 16 | 17 | 18 | // MARK: - Initializer 19 | 20 | init(entity: TextCellEntity) { 21 | self.entity = entity 22 | } 23 | 24 | 25 | // MARK: - Computed Properties 26 | 27 | var image: UIImage? { 28 | return UIImage(named: entity.image ?? "") 29 | } 30 | 31 | var headingLabelAttributedText: NSAttributedString { 32 | let mutableString = NSMutableAttributedString(attributedString: headingAttributedString) 33 | mutableString.append(subheadingAttributedString) 34 | 35 | return mutableString 36 | } 37 | 38 | private var headingAttributedString: NSAttributedString { 39 | return NSAttributedString( 40 | string: entity.text, 41 | attributes: [.font: UIFont.boldSystemFont(ofSize: 16)]) 42 | } 43 | 44 | private var subheadingAttributedString: NSAttributedString { 45 | return NSAttributedString( 46 | string: entity.subtext != nil ? "\n\(entity.subtext!)" : "", 47 | attributes: [ 48 | .font: UIFont.systemFont(ofSize: 14), 49 | .foregroundColor: UIColor.lightGray]) 50 | } 51 | 52 | var backgroundColor: UIColor { 53 | if let color = entity.backgroundColor{ 54 | return .color(forColor: color) 55 | } 56 | else { 57 | return .defaultBackgroundColor 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextFieldCellController/Cell/TextFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TextFieldCell: UITableViewCell, NibLoadable { 12 | 13 | // MARK: - IBOutlets 14 | 15 | @IBOutlet weak var label: UILabel! 16 | @IBOutlet weak var textField: UITextField! 17 | 18 | 19 | // MARK: - View Lifecycle 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | } 24 | 25 | 26 | // MARK: - Reuse 27 | 28 | override func prepareForReuse() { 29 | super.prepareForReuse() 30 | } 31 | } 32 | 33 | extension TextFieldCell: HeightDefaultable { 34 | 35 | static var defaultHeight: CGFloat { 36 | return 100 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextFieldCellController/Cell/TextFieldCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextFieldCellController/TextFieldCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCellController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TextFieldCellController { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: TextFieldCellEntity 16 | let dataSource: TextFieldCellDataSource 17 | weak var delegate: FormCellControllerDelegate? 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: TextFieldCellEntity) { 22 | self.entity = entity 23 | self.dataSource = TextFieldCellDataSource(entity: entity) 24 | self.delegate = nil 25 | } 26 | 27 | 28 | // MARK: - Computed Properties 29 | 30 | var model: TextFieldCellModel { 31 | return dataSource.model 32 | } 33 | 34 | 35 | // MARK: - Preparation 36 | 37 | func prepare(_ cell: TextFieldCell) { 38 | cell.label.text = model.labelText 39 | cell.textField.text = model.text 40 | cell.textField.placeholder = model.placeholderText 41 | cell.textField.isSecureTextEntry = model.isSecureTextEntry 42 | cell.textField.addTarget(self, action: #selector(formElementDidUpdate(sender:)), for: .editingChanged) 43 | cell.textField.isEnabled = model.isEnabled 44 | } 45 | } 46 | 47 | extension TextFieldCellController: FormFieldHandling { 48 | 49 | @objc func formElementDidUpdate(sender: Any) { 50 | guard 51 | let sender = sender as? UITextField, 52 | let text = sender.text 53 | else { return } 54 | 55 | dataSource.setFormValue(text) 56 | delegate?.formDidUpdate( 57 | for: .field(property: dataSource.property, value: text)) 58 | } 59 | 60 | func clearFormField() { 61 | dataSource.setFormValue(nil) 62 | } 63 | 64 | func setInteractionEnabled(_ isInteractionEnabled: Bool) { 65 | model.state = isInteractionEnabled ? .enabled : .disabled 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextFieldCellController/TextFieldCellDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCellDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TextFieldCellDataSource: FormFieldValueStoring { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: TextFieldCellEntity 16 | var model: TextFieldCellModel 17 | 18 | internal (set) var formValue: Any? 19 | 20 | // MARK: - Initializer 21 | 22 | init(entity: TextFieldCellEntity) { 23 | self.entity = entity 24 | self.model = TextFieldCellModel(entity: entity) 25 | self.formValue = nil 26 | } 27 | 28 | var property: String { 29 | return entity.property 30 | } 31 | 32 | func setFormValue(_ value: Any?) { 33 | model.text = value as? String 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextFieldCellController/TextFieldCellEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCellEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct TextFieldCellEntity { 12 | 13 | let property: String 14 | let label: String 15 | let placeholder: String? 16 | let isSecureTextEntry: Bool 17 | 18 | init?(json: JSON) { 19 | guard 20 | let label = json["label"] as? String, 21 | let property = json["property"] as? String 22 | else { return nil } 23 | 24 | self.label = label 25 | self.property = property 26 | self.placeholder = json["placeholder"] as? String 27 | self.isSecureTextEntry = json["is-password"] as? Bool ?? false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/nemo/CellController/ContentCellController/TextFieldCellController/TextFieldCellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCellModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TextFieldCellModel { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | let entity: TextFieldCellEntity 17 | var text: String? 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: TextFieldCellEntity) { 22 | self.entity = entity 23 | self.state = .enabled 24 | } 25 | 26 | // MARK: - Computed Properties 27 | 28 | var labelText: String { 29 | return entity.label 30 | } 31 | 32 | var placeholderText: String? { 33 | return entity.placeholder 34 | } 35 | 36 | var isSecureTextEntry: Bool { 37 | return entity.isSecureTextEntry 38 | } 39 | 40 | var isEnabled: Bool { 41 | return state == .enabled 42 | } 43 | } 44 | 45 | extension TextFieldCellModel: ViewStateManageable { 46 | 47 | enum ViewState { 48 | case enabled, disabled 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Example/nemo/Cells/Carousel/AdCarouselCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdCarouselCell.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 18/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AdCarouselCell: UICollectionViewCell, NibLoadable { 12 | 13 | @IBOutlet weak var adImageView: UIImageView! 14 | @IBOutlet weak var label: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | } 22 | 23 | extension AdCarouselCell: HeightDefaultable { 24 | static var defaultHeight: CGFloat { 25 | return 80 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/nemo/Cells/Carousel/AdCarouselCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UICollectionView+RegisterReuse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+RegisterReuse.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | extension UICollectionView { 14 | 15 | // MARK: - Register 16 | 17 | func register(_: T.Type) where T: NibLoadable { 18 | register(T.nib, forCellWithReuseIdentifier: T.reuseIdentifier) 19 | } 20 | 21 | func register(_: T.Type) { 22 | register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier) 23 | } 24 | 25 | func register(elementKind: String, _: T.Type) where T: NibLoadable { 26 | register(T.nib, forSupplementaryViewOfKind: elementKind, withReuseIdentifier: T.reuseIdentifier) 27 | } 28 | 29 | func register(elementKind: String, _: T.Type) { 30 | register(T.self, forSupplementaryViewOfKind: elementKind, withReuseIdentifier: T.reuseIdentifier) 31 | } 32 | 33 | // MARK: - Dequeuing 34 | 35 | func dequeueReusableCell(for indexPath: IndexPath) -> T { 36 | if let cell = dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T { 37 | return cell 38 | } 39 | else { 40 | fatalError("The dequeueReusableCell \(String(describing: T.self)) couldn't be loaded.") 41 | } 42 | } 43 | 44 | func dequeueReusableView(elementKind: String, for indexPath: IndexPath) -> T { 45 | if let view = dequeueReusableSupplementaryView(ofKind: elementKind, withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T { 46 | return view 47 | } 48 | else { 49 | fatalError("The dequeueReusableSupplementaryView \(String(describing: T.self)) couldn't be loaded.") 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UICollectionViewCell+Reusable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewCell+Reusable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UICollectionViewCell: Reusable { } 13 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UIColor+ColorFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+ColorFactory.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 29/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIColor { 13 | static func color(forColor string: String) -> UIColor { 14 | return ColorFactory(string: string).color 15 | } 16 | 17 | static var defaultBackgroundColor: UIColor { 18 | return .white 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UITableView+RegisterReuse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCellLoadable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UITableView { 13 | 14 | // MARK: - Register Views 15 | 16 | func register(_: T.Type) where T: NibLoadable { 17 | register(T.nib, forCellReuseIdentifier: T.reuseIdentifier) 18 | } 19 | 20 | func register(_: T.Type){ 21 | register(T.self, forCellReuseIdentifier: T.reuseIdentifier) 22 | } 23 | 24 | func register(_: T.Type) where T: NibLoadable { 25 | register(T.nib, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier) 26 | } 27 | 28 | func register(_: T.Type) { 29 | register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier) 30 | } 31 | 32 | // MARK: - Dequeuing 33 | 34 | func dequeueReusableCell(for indexPath: IndexPath) -> T { 35 | if let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T { 36 | return cell 37 | } 38 | else { 39 | fatalError("The dequeueReusableCell \(String(describing: T.self)) couldn't be loaded.") 40 | } 41 | } 42 | 43 | func dequeueReusableView() -> T { 44 | if let view = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T { 45 | return view 46 | } 47 | else { 48 | fatalError("The dequeueReusableView \(String(describing: T.self)) couldn't be loaded.") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UITableViewCell+Reusable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewCell+Reusable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UITableViewCell: Reusable { } 13 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UITableViewHeaderFooterView+Reusable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewHeaderFooterView+Reusable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UITableViewHeaderFooterView: Reusable { } 13 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UIView+CornerRadius.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+CornerRadius.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIView { 13 | static var defaultCornerRadius: CGFloat { 14 | return 16 15 | } 16 | 17 | var defaultCornerRadius: CGFloat { 18 | return UIView.defaultCornerRadius 19 | } 20 | 21 | func updateCornerRadii(_ radii: CGFloat? = nil) { 22 | layer.cornerRadius = radii ?? defaultCornerRadius 23 | clipsToBounds = true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/nemo/Extension/UIViewController+AlertPresenting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorAlerting.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 29/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIViewController { 13 | func presentError(withMessage message: String) { 14 | presentAlert(withTitle: "Error", message: message) 15 | } 16 | 17 | func presentSuccess(withMessage message: String) { 18 | presentAlert(withTitle: "Success", message: message) 19 | } 20 | 21 | func presentAlert(withTitle title: String, message: String, duration: TimeInterval = 2) { 22 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 23 | self.present(alertController, animated: true, completion: nil) 24 | 25 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) { 26 | alertController.dismiss(animated: true, completion: nil) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/nemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/Form/FormFieldContaining.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormFieldContaining.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 19/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol FormFieldContaining { 12 | 13 | var dataSource: FormFieldValueStoring { get set } 14 | } 15 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/Form/FormFieldHandling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormFieldHandling.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol FormFieldHandling { 12 | 13 | var delegate: FormCellControllerDelegate? { get set } 14 | 15 | func formElementDidUpdate(sender: Any) 16 | func clearFormField() 17 | func setInteractionEnabled(_ isInteractionEnabled: Bool) 18 | } 19 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/Form/FormFieldValueStoring.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormFieldValueStoring.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 19/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol FormFieldValueStoring { 12 | 13 | var formValue: Any? { get } 14 | 15 | func setFormValue(_ value: Any?) 16 | } 17 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/HeightDefaultable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeightDefaultable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol HeightDefaultable { 13 | 14 | static var defaultHeight: CGFloat { get } 15 | } 16 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/NibLoadable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NibLoadable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol NibLoadable: class { 13 | 14 | static var nib: UINib { get } 15 | } 16 | 17 | extension NibLoadable { 18 | 19 | static var nib: UINib { 20 | return UINib(nibName: String(describing: self), bundle: Bundle(for: self)) 21 | } 22 | } 23 | 24 | extension NibLoadable where Self: UIView { 25 | 26 | static func loadNib() -> Self { 27 | if let view = nib.instantiate(withOwner: nil, options: nil).first as? Self { 28 | return view 29 | } 30 | else { 31 | fatalError("The nib \(nib) expected its root view to be of type \(String(describing: self))") 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/Requesting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Requesting.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Requesting { 12 | 13 | associatedtype Request 14 | } 15 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/Reusable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reusable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol Reusable: class { 13 | 14 | static var reuseIdentifier: String { get } 15 | } 16 | 17 | extension Reusable { 18 | 19 | static var reuseIdentifier: String { 20 | return String(describing: self) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/SizeDefaultable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SizeDefaultable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 1/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol SizeDefaultable { 13 | 14 | static var defaultSize: CGSize { get } 15 | } 16 | -------------------------------------------------------------------------------- /Example/nemo/Protocol/ViewStateManageable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewStateManageable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ViewStateManageable { 12 | 13 | associatedtype ViewState 14 | 15 | var state: ViewState { get set } 16 | } 17 | 18 | enum ViewState { 19 | case loading, completed, failed 20 | } 21 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/ContentSectionController/ContentSectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentSectionControllerType.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 4/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ContentSectionController { 12 | 13 | let entity: ContentSectionEntity 14 | let cellControllers: [ContentCellControllerType] 15 | let model: ContentSectionModel 16 | 17 | init(entity: ContentSectionEntity) { 18 | self.entity = entity 19 | self.cellControllers = self.entity.cellEntities 20 | .compactMap { ContentCellControllerType(cellEntity: $0) } 21 | self.model = ContentSectionModel(entity: entity) 22 | } 23 | 24 | func prepare(_ view: SectionTitleHeaderView) { 25 | view.titleLabel.text = model.titleLabelText 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/ContentSectionController/ContentSectionEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentSectionEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 4/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ContentSectionEntity { 12 | 13 | let cellEntities: [ContentCellEntity] 14 | let title: String? 15 | 16 | init?(json: JSON) { 17 | guard let entities = json["cells"] as? [JSON] else { return nil } 18 | 19 | self.cellEntities = entities 20 | .compactMap{ ContentCellEntity(json: $0) } 21 | self.title = json["title"] as? String 22 | 23 | if self.cellEntities.count == 0 { 24 | return nil 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/ContentSectionController/ContentSectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentSectionModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 12/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class ContentSectionModel { 12 | 13 | let entity: ContentSectionEntity 14 | 15 | init(entity: ContentSectionEntity) { 16 | self.entity = entity 17 | } 18 | 19 | var titleLabelText: String? { 20 | return entity.title 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/FormSectionController/Entity/FormSectionEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormSectionEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FormSectionEntity { 12 | 13 | let cellEntities: [ContentCellEntity] 14 | let title: String? 15 | 16 | init?(json: JSON) { 17 | guard let entities = json["cells"] as? [JSON] else { return nil } 18 | 19 | self.cellEntities = entities 20 | .compactMap{ ContentCellEntity(json: $0) } 21 | self.title = json["title"] as? String 22 | if self.cellEntities.count == 0 { 23 | return nil 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/FormSectionController/FormSectionDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormSectionDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class FormSectionDataSource { 12 | 13 | // MARK: - Properties 14 | 15 | let entity: FormSectionEntity 16 | var model: FormSectionModel 17 | var state: State 18 | private var postJSON: JSON 19 | 20 | // MARK: - Initializer 21 | 22 | init(entity: FormSectionEntity) { 23 | self.entity = entity 24 | self.state = .loading 25 | self.model = FormSectionModel(entity: entity) 26 | self.postJSON = [:] 27 | } 28 | 29 | // MARK: - POST request 30 | 31 | func setPost(property: String, value: Any) { 32 | postJSON[property] = value 33 | } 34 | } 35 | 36 | extension FormSectionDataSource: Requesting { 37 | 38 | enum Request { 39 | case submit 40 | } 41 | 42 | func request(_ request: Request, completion: @escaping ResultClosure) { 43 | state = .loading 44 | 45 | switch request { 46 | case .submit: 47 | requestSubmit(completion) 48 | } 49 | } 50 | 51 | private func requestSubmit(_ completion: @escaping ResultClosure) { 52 | Requester.submit(.submitForm(postJSON)) { 53 | completion($0) 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/FormSectionController/FormSectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormSectionModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class FormSectionModel { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | private let entity: FormSectionEntity 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: FormSectionEntity) { 22 | self.entity = entity 23 | self.state = .unfilled 24 | } 25 | 26 | // MARK: - Computed Properties 27 | 28 | var titleLabelText: String? { 29 | return entity.title 30 | } 31 | } 32 | 33 | extension FormSectionModel: ViewStateManageable { 34 | 35 | enum ViewState { 36 | case unfilled, sent 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SectionControllerDisplayable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionControllerDisplayable.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | private let defaultFooterHeight: CGFloat = 16 13 | 14 | protocol SectionControllerDisplayable { 15 | 16 | func registerViews(for tableView: UITableView) 17 | 18 | func tableView(_ tableView: UITableView, sectionHeaderHeightFor sectionController: SectionControllerType) -> CGFloat 19 | 20 | func tableView(_ tableView: UITableView, sectionFooterHeightFor sectionController: SectionControllerType) -> CGFloat 21 | 22 | func tableView(_ tableView: UITableView, headerViewFor sectionController: SectionControllerType, atIndex index: Int) -> UIView? 23 | 24 | func tableView(_ tableView: UITableView, footerViewFor sectionController: SectionControllerType, atIndex index: Int) -> UIView? 25 | } 26 | 27 | extension SectionControllerDisplayable where Self: UIViewController { 28 | 29 | func registerViews(for tableView: UITableView) { 30 | tableView.register(SegmentSectionHeaderView.self) 31 | tableView.register(SectionTitleHeaderView.self) 32 | } 33 | 34 | func tableView(_ tableView: UITableView, sectionHeaderHeightFor sectionController: SectionControllerType) -> CGFloat { 35 | switch sectionController { 36 | case .content(let sectionController): 37 | return sectionController.model.titleLabelText != nil 38 | ? SectionTitleHeaderView.defaultHeight 39 | : .leastNormalMagnitude 40 | 41 | case .segment: 42 | return SegmentSectionHeaderView.defaultHeight 43 | 44 | case .form(let sectionController): 45 | return sectionController.model.titleLabelText != nil 46 | ? SectionTitleHeaderView.defaultHeight 47 | : .leastNormalMagnitude 48 | } 49 | } 50 | 51 | func tableView(_ tableView: UITableView, sectionFooterHeightFor sectionController: SectionControllerType) -> CGFloat { 52 | return defaultFooterHeight 53 | } 54 | 55 | 56 | func tableView(_ tableView: UITableView, headerViewFor sectionController: SectionControllerType, atIndex index: Int) -> UIView? { 57 | switch sectionController { 58 | case .content(let sectionController): 59 | guard sectionController.model.titleLabelText != nil else { return nil } 60 | let view: SectionTitleHeaderView = tableView.dequeueReusableView() 61 | sectionController.prepare(view) 62 | return view 63 | 64 | case .segment(let sectionController): 65 | let view: SegmentSectionHeaderView = tableView.dequeueReusableView() 66 | sectionController.prepare(view) 67 | return view 68 | 69 | case .form(let sectionController): 70 | guard sectionController.model.titleLabelText != nil else { return nil } 71 | let view: SectionTitleHeaderView = tableView.dequeueReusableView() 72 | sectionController.prepare(view) 73 | return view 74 | } 75 | } 76 | 77 | func tableView(_ tableView: UITableView, footerViewFor sectionController: SectionControllerType, atIndex index: Int) -> UIView? { 78 | return nil 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SectionControllerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionController.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 29/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SectionControllerType { 12 | 13 | case content(ContentSectionController) 14 | case segment(SegmentSectionController) 15 | case form(FormSectionController) 16 | 17 | init(sectionEntity: SectionEntity) { 18 | switch sectionEntity { 19 | case .content(let entity): 20 | self = .content(.init(entity: entity)) 21 | case .segment(let entity): 22 | self = .segment(.init(entity: entity)) 23 | case .form(let entity): 24 | self = .form(.init(entity: entity)) 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SectionEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionType.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 22/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SectionEntity { 12 | 13 | private enum Types: String { 14 | case content, carousel, segment, form 15 | } 16 | 17 | case content(ContentSectionEntity) 18 | case segment(SegmentSectionEntity) 19 | case form(FormSectionEntity) 20 | 21 | init?(json: JSON) { 22 | guard 23 | let typeString = json["type"] as? String, 24 | let type = Types(rawValue: typeString) 25 | else { assertionFailure("Invalid SectionEntity 'type' key passed"); return nil } 26 | 27 | switch type { 28 | case .content: 29 | guard let entity = ContentSectionEntity(json: json) else { fallthrough } 30 | self = .content(entity) 31 | case .segment: 32 | guard let entity = SegmentSectionEntity(json: json) else { fallthrough } 33 | self = .segment(entity) 34 | case .form: 35 | guard let entity = FormSectionEntity(json: json) else { fallthrough } 36 | self = .form(entity) 37 | default: 38 | assertionFailure("Invalid SectionEntity identifier passed: \(type)") 39 | return nil 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/Entity/SegmentSectionEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SegmentSectionEntity { 12 | 13 | let optionEntities: [SegmentSectionOptionEntity] 14 | 15 | init?(json: JSON) { 16 | guard let entities = json["segments"] as? [JSON] else { return nil } 17 | 18 | self.optionEntities = entities 19 | .compactMap{ SegmentSectionOptionEntity(json: $0) } 20 | 21 | if self.optionEntities.count == 0 { 22 | return nil 23 | } 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/Entity/SegmentSectionOptionEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionOptionEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SegmentSectionOptionEntity { 12 | 13 | let title: String 14 | let cellEntities: [ContentCellEntity] 15 | 16 | init?(json: JSON) { 17 | guard 18 | let title = json["title"] as? String, 19 | let entities = json["cells"] as? [JSON] 20 | else { return nil } 21 | 22 | self.title = title 23 | self.cellEntities = entities 24 | .compactMap{ ContentCellEntity(json: $0) } 25 | 26 | if self.cellEntities.count == 0 { 27 | return nil 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/SegmentSectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionControllerType.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol SegmentSectionControllerDelegate: class { 13 | 14 | func segmentSectionControllerDidUpdate(_ sectionController: SegmentSectionController) 15 | } 16 | 17 | final class SegmentSectionController { 18 | 19 | let entity: SegmentSectionEntity 20 | let dataSource: SegmentSectionDataSource 21 | weak var delegate: SegmentSectionControllerDelegate? 22 | 23 | var model: SegmentSectionModel { 24 | return dataSource.model 25 | } 26 | 27 | var options: [SegmentSectionOption] { 28 | return dataSource.options 29 | } 30 | 31 | var selectedIndexCellControllers: [ContentCellControllerType] { 32 | return dataSource.selectedIndexCellControllers 33 | } 34 | 35 | var allCellControllers: [ContentCellControllerType] { 36 | return dataSource.allCellControllers 37 | } 38 | 39 | init(entity: SegmentSectionEntity) { 40 | self.entity = entity 41 | self.dataSource = SegmentSectionDataSource(entity: self.entity) 42 | } 43 | 44 | func prepare(_ view: SegmentSectionHeaderView) { 45 | model.titles.enumerated() 46 | .forEach { view.segmentedControl.setTitle($1, forSegmentAt: $0) } 47 | 48 | view.segmentedControl.addTarget( 49 | self, 50 | action: #selector(segmentedControlValueChanged(sender:)), 51 | for: .valueChanged) 52 | } 53 | 54 | @objc func segmentedControlValueChanged(sender: UISegmentedControl) { 55 | dataSource.updateSelectedIndex(sender.selectedSegmentIndex) 56 | delegate?.segmentSectionControllerDidUpdate(self) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/SegmentSectionDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class SegmentSectionDataSource { 13 | 14 | let entity: SegmentSectionEntity 15 | let model: SegmentSectionModel 16 | let options: [SegmentSectionOption] 17 | 18 | private(set) var selectedIndex: Int = 0 19 | 20 | init(entity: SegmentSectionEntity) { 21 | self.entity = entity 22 | self.options = self.entity.optionEntities 23 | .compactMap { SegmentSectionOption(entity: $0) } 24 | self.model = SegmentSectionModel( 25 | entity: self.entity, 26 | titles: self.options.map { $0.entity.title }) 27 | } 28 | 29 | var numberOfValues: Int { 30 | return options.count 31 | } 32 | 33 | var selectedIndexCellControllers: [ContentCellControllerType] { 34 | return options[selectedIndex].cellControllers 35 | } 36 | 37 | var allCellControllers: [ContentCellControllerType] { 38 | return options.flatMap { $0.cellControllers } 39 | } 40 | 41 | func updateSelectedIndex(_ index: Int) { 42 | self.selectedIndex = index 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/SegmentSectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SegmentSectionModel { 12 | 13 | let entity: SegmentSectionEntity 14 | let titles: [String] 15 | 16 | init(entity: SegmentSectionEntity, titles: [String]) { 17 | self.entity = entity 18 | self.titles = titles 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/SegmentSectionOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionOption.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | struct SegmentSectionOption { 13 | 14 | let entity: SegmentSectionOptionEntity 15 | let title: String 16 | let cellControllers: [ContentCellControllerType] 17 | 18 | init(entity: SegmentSectionOptionEntity) { 19 | self.entity = entity 20 | self.title = entity.title 21 | self.cellControllers = self.entity.cellEntities 22 | .compactMap { ContentCellControllerType(cellEntity: $0) } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/View/SegmentSectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionHeaderView.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SegmentSectionHeaderView: UITableViewHeaderFooterView, NibLoadable { 12 | 13 | @IBOutlet weak var segmentedControl: UISegmentedControl! 14 | 15 | required init?(coder aDecoder: NSCoder) { 16 | super.init(coder: aDecoder) 17 | } 18 | } 19 | 20 | extension SegmentSectionHeaderView: HeightDefaultable { 21 | static var defaultHeight: CGFloat { 22 | return 60 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/SegmentSectionController/View/SegmentSectionHeaderView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/View/SectionTitleHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentSectionHeaderView.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 5/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SectionTitleHeaderView: UITableViewHeaderFooterView, NibLoadable { 12 | 13 | @IBOutlet weak var titleLabel: UILabel! { 14 | didSet { 15 | titleLabel.text = nil 16 | titleLabel.font = .systemFont(ofSize: 32, weight: .heavy) 17 | } 18 | } 19 | 20 | required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | } 23 | } 24 | 25 | extension SectionTitleHeaderView: HeightDefaultable { 26 | 27 | static var defaultHeight: CGFloat { 28 | return 60 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/nemo/SectionController/View/SectionTitleHeaderView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/ColorFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorFactory.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | enum ColorFactory: String { 13 | 14 | case red 15 | case blue 16 | case green 17 | case black 18 | case white 19 | case clear 20 | 21 | init(string: String) { 22 | self = ColorFactory(rawValue: string) ?? .clear 23 | } 24 | 25 | var color: UIColor { 26 | switch self { 27 | case .red: 28 | return .red 29 | case .blue: 30 | return .blue 31 | case .green: 32 | return .green 33 | case .black: 34 | return .black 35 | case .white: 36 | return .white 37 | case .clear: 38 | return .clear 39 | } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/Navigation/NavigationDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentCellControllerNavigationHandling.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 13/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol NavigationRequesting: class { 12 | 13 | var navigationDelegate: NavigationDelegate? { get set } 14 | 15 | func navigationWillBegin(sender: Any) 16 | } 17 | 18 | protocol NavigationDelegate: class { 19 | 20 | func navigate(to navigation: NavigationDestinationType) 21 | } 22 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/Navigation/NavigationDestinationType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationType.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 13/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum NavigationDestinationType { 12 | 13 | case article(articleID: Int, title: String) 14 | case gallery(galleryID: Int, title: String) 15 | } 16 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/Navigation/ViewControllerFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerFactory.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 13/8/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ViewControllerFactory { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/NavigationFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationFactory.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Navigation: String { 12 | 13 | case main 14 | case detail 15 | case article 16 | case gallery 17 | case player 18 | } 19 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/Requester/Requester.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Requester.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private let delay: UInt32 = 1 12 | private let type: String = "json" 13 | 14 | enum Submit { 15 | case submitForm(JSON) 16 | } 17 | 18 | enum Request: String { 19 | case main 20 | 21 | var resource: String { 22 | return rawValue 23 | } 24 | } 25 | 26 | struct Requester { 27 | 28 | static func request(_ request: Request) -> Result { 29 | guard 30 | let path = Bundle.main.path(forResource: request.resource, ofType: type) 31 | else { return .failure("resource file doesn't exist within bundle") } 32 | do { 33 | let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) 34 | let serialized = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) 35 | if let json = serialized as? JSON { 36 | sleep(delay) 37 | return .success(json) 38 | } 39 | else { 40 | return .failure("Couldn't parse json file") 41 | } 42 | } 43 | catch { 44 | return .failure("Couldn't parse json file:\n\(error.localizedDescription)") 45 | } 46 | } 47 | 48 | static func submit(_ submit: Submit, completion: @escaping ResultClosure) { 49 | switch submit { 50 | case .submitForm(let json): 51 | print("Submitting:\n\(json)") 52 | } 53 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 54 | completion(.success("")) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/Requester/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 29/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Result { 12 | 13 | case success(T) 14 | case failure(String) 15 | } 16 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // State.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 3/4/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum State { 12 | 13 | case loading 14 | case completed 15 | case failed(Failure) 16 | } 17 | 18 | extension State { 19 | 20 | enum Failure { 21 | case unknown(String) 22 | case generic 23 | case json 24 | case network 25 | 26 | var description: String { 27 | switch self { 28 | case .unknown(let error): 29 | return error 30 | case .generic: 31 | return "Something went wrong" 32 | case .json: 33 | return "The json couldn't be parsed" 34 | case .network: 35 | return "Network connection timed out" 36 | } 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Example/nemo/Utilities/Typealiases.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Typealiases.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 3/4/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias CompletionClosure = (State) -> Void 12 | typealias ResultClosure = (Result) -> Void 13 | typealias JSON = [String: Any] 14 | -------------------------------------------------------------------------------- /Example/nemo/ViewController/Main/MainEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainEntity.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MainEntity { 12 | 13 | let sections: [SectionEntity] 14 | 15 | init?(json: JSON) { 16 | guard let sections = json["sections"] as? [JSON] else { return nil } 17 | 18 | self.sections = sections 19 | .compactMap{ SectionEntity(json: $0) } 20 | 21 | if self.sections.count == 0 { 22 | return nil 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/nemo/ViewController/Main/MainViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewDataSource.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class MainViewDataSource { 12 | 13 | // MARK: - Properties 14 | 15 | var entity: MainEntity? 16 | var state: State 17 | var model: MainViewModel 18 | private var sectionControllers: [SectionControllerType] 19 | 20 | // MARK: - Initializer 21 | 22 | init() { 23 | self.state = .loading 24 | self.model = MainViewModel(entity: nil) 25 | self.entity = nil 26 | self.sectionControllers = [] 27 | } 28 | 29 | 30 | // MARK: - Data 31 | 32 | var numberOfSections: Int { 33 | return sectionControllers.count 34 | } 35 | 36 | func numberOfRows(inSection section: Int) -> Int { 37 | switch sectionControllers[section] { 38 | case .content(let sectionController): 39 | return sectionController.cellControllers.count 40 | case .segment(let sectionController): 41 | return sectionController.dataSource.selectedIndexCellControllers.count 42 | case .form(let sectionController): 43 | return sectionController.cellControllers.count 44 | } 45 | } 46 | 47 | func sectionController(forIndex index: Int) -> SectionControllerType { 48 | return sectionControllers[index] 49 | } 50 | 51 | func cellControllers(forIndex index: Int) -> [ContentCellControllerType] { 52 | switch sectionController(forIndex: index) { 53 | case .content(let sectionController): 54 | return sectionController.cellControllers 55 | case .form(let sectionController): 56 | return sectionController.cellControllers 57 | case .segment(let sectionController): 58 | return sectionController.selectedIndexCellControllers 59 | } 60 | } 61 | 62 | func cellController(for indexPath: IndexPath) -> ContentCellControllerType { 63 | return cellControllers(forIndex: indexPath.section)[indexPath.row] 64 | } 65 | 66 | private func updateEntity(_ entity: MainEntity) { 67 | self.entity = entity 68 | self.sectionControllers = entity.sections 69 | .compactMap { SectionControllerType(sectionEntity: $0) } 70 | } 71 | 72 | var cellControllers: [ContentCellControllerType] { 73 | let cellControllers: [[ContentCellControllerType]] = sectionControllers 74 | .map { 75 | switch $0 { 76 | case .content(let sectionController): 77 | return sectionController.cellControllers 78 | case .form(let sectionController): 79 | return sectionController.cellControllers 80 | case .segment(let sectionController): 81 | return sectionController.allCellControllers 82 | } 83 | } 84 | return cellControllers.flatMap { $0 } 85 | } 86 | } 87 | 88 | // MARK: - Requesting 89 | 90 | extension MainViewDataSource: Requesting { 91 | 92 | enum Request { 93 | case initial 94 | } 95 | 96 | // MARK: Request 97 | 98 | func request(_ request: Request, completion: @escaping CompletionClosure) { 99 | state = .loading 100 | 101 | switch request { 102 | case .initial: 103 | requestInitial(completion) 104 | } 105 | } 106 | 107 | private func requestInitial(_ completion: @escaping CompletionClosure) { 108 | switch Requester.request(.main) { 109 | case .success(let json): 110 | if let entity = MainEntity(json: json) { 111 | updateEntity(entity) 112 | state = .completed 113 | } 114 | else { 115 | state = .failed(.generic) 116 | } 117 | case .failure(let error): 118 | state = .failed(.unknown(error)) 119 | } 120 | completion(state) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Example/nemo/ViewController/Main/MainViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewModel.swift 3 | // nemo 4 | // 5 | // Created by Andyy Hope on 21/7/18. 6 | // Copyright © 2018 Andyy Hope. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class MainViewModel { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | var entity: MainEntity? 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(entity: MainEntity?) { 22 | self.entity = entity 23 | self.state = .loading 24 | } 25 | 26 | // MARK: - Computed Properties 27 | 28 | var title: String { 29 | return "NEMO" 30 | } 31 | 32 | var backgroundColor: UIColor { 33 | return .white 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Example/nemo/content/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "main", 3 | "sections": [ 4 | { 5 | "type": "content", 6 | "title": "Related Content", 7 | "cells": [ 8 | { 9 | "type": "image", 10 | "image": "adventuretime", 11 | "caption": "This is a photo", 12 | "navigation": { 13 | "destination": "orange" 14 | }, 15 | }, 16 | { 17 | "type": "detail", 18 | "heading-text": "Breaking News", 19 | "detail-text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 20 | "navigation": { 21 | "destination": "carrot" 22 | }, 23 | }, 24 | { 25 | "type": "text", 26 | "text": "Lumpy Space Princess", 27 | "subtext": "500 followers", 28 | "image": "lsp", 29 | "navigation": { 30 | "destination": "apple" 31 | }, 32 | }, 33 | { 34 | "type": "carousel", 35 | "cells": [ 36 | { 37 | "type": "ad", 38 | "description": "Space rides", 39 | "company": "Join NASA Today!", 40 | "url": "nasaa.org", 41 | "image": "ad1", 42 | }, 43 | { 44 | "type": "image", 45 | "image": "dune", 46 | "caption": "banjo", 47 | }, 48 | { 49 | "type": "icon", 50 | "icon": "icon1", 51 | "text": "Gaming", 52 | }, 53 | ] 54 | }, 55 | ] 56 | }, 57 | { 58 | "type": "segment", 59 | "segments": [ 60 | { 61 | "title": "Featured", 62 | "cells": [ 63 | { 64 | "type": "image", 65 | "image": "mountain", 66 | "caption": "This is a photo", 67 | }, 68 | { 69 | "type": "detail", 70 | "heading-text": "Breaking News", 71 | "detail-text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 72 | }, 73 | { 74 | "type": "text", 75 | "text": "Andyy Hope", 76 | "subtext": "3k followers", 77 | "image": "avatar" 78 | }, 79 | ] 80 | }, 81 | { 82 | "title": "More", 83 | "cells": [ 84 | { 85 | "type": "text", 86 | "text": "Finn the Human", 87 | "subtext": "1m followers", 88 | "image": "finn" 89 | }, 90 | { 91 | "type": "text", 92 | "text": "Jake the Dog", 93 | "subtext": "500k followers", 94 | "image": "jake" 95 | }, 96 | { 97 | "type": "text", 98 | "text": "Princess Bubblegum", 99 | "subtext": "30k followers", 100 | "image": "pb" 101 | }, 102 | { 103 | "type": "text", 104 | "text": "Lumpy Space Princess", 105 | "subtext": "500 followers", 106 | "image": "lsp" 107 | }, 108 | ] 109 | } 110 | ], 111 | }, 112 | { 113 | "type": "form", 114 | "title": "Subscribe today", 115 | "cells": [ 116 | { 117 | "type": "textField", 118 | "label": "Email address", 119 | "placeholder": "Lorem ipsum", 120 | "property": "email", 121 | }, 122 | { 123 | "type": "textField", 124 | "label": "Password", 125 | "placeholder": "", 126 | "property": "password", 127 | "is-password": true 128 | }, 129 | { 130 | "type": "switchField", 131 | "property": "email-subscription", 132 | "label": "Subscribe to newsletter", 133 | "default-value": true, 134 | }, 135 | { 136 | "type": "buttonField", 137 | "property": "submit", 138 | "label": "Let's do it!", 139 | "buttonColor": "red", 140 | "buttonLabelColor": "white", 141 | }, 142 | ] 143 | }, 144 | 145 | ] 146 | } 147 | -------------------------------------------------------------------------------- /Example/nemo/notes.txt: -------------------------------------------------------------------------------- 1 | - Add completion closures to requesters 2 | - add code gen for protocols 3 | - Add initial `state` for VC Model codegen 4 | - Comment out ViewController codegen optional functions 5 | - Added initial `cellControllers` `sectionControllers` to VC Model codegen 6 | 7 | - Cell- Co-Mo 8 | - self.state not initialised 9 | - DaSo `model` not init'd 10 | 11 | - Cell - DaSoMo 12 | - Remove entity from initializer/remove entity from computed property. Needs to match with other DaSoMo 13 | - Remove "CellCell" from Class nib 14 | 15 | 16 | VC - DaSo 17 | - Add: 18 | - var numberOfSections: Int 19 | - func numberOfRows(inSection section: Int) -> Int -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nemo 2 | An architecture for developing iOS apps which require the presentation of interchangeable lists of data 3 | -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Cell - Co, DaSo, Mo.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Cell - Co, DaSo, Mo.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/___FILEBASENAME___Cell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___: UITableViewCell { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | // MARK: - IBOutlets 17 | 18 | @IBOutlet weak var headingLabel: UILabel! 19 | @IBOutlet weak var subheadingLabel: UILabel! 20 | 21 | 22 | // MARK: - View Lifecycle 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | } 27 | 28 | 29 | // MARK: - Reuse 30 | 31 | override func prepareForReuse() { 32 | super.prepareForReuse() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/___FILEBASENAME___Cell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/___FILEBASENAME___CellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 16 | let dataSource: ___VARIABLE_cellName:identifier___CellDataSource 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 22 | self.dataSource = ___VARIABLE_cellName:identifier___CellDataSource(___VARIABLE_entityName:identifier___: ___VARIABLE_entityName:identifier___) 23 | } 24 | 25 | 26 | // MARK: - Computed Properties 27 | 28 | var model: ___VARIABLE_cellName:identifier___CellModel { 29 | return dataSource.model 30 | } 31 | 32 | var ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ { 33 | return dataSource.___VARIABLE_entityName:identifier___ 34 | } 35 | 36 | 37 | // MARK: - Preparation 38 | 39 | func prepare(_ cell: ___VARIABLE_cellName:identifier___Cell) { 40 | cell.headingLabel.text = model.headingLabelText 41 | 42 | prepareBindings(for: cell) 43 | } 44 | 45 | private func prepareBindings(for cell: ___VARIABLE_cellName:identifier___Cell) { 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/___FILEBASENAME___CellDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___: StateManageable { 12 | 13 | // MARK: - Properties 14 | 15 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 16 | var model: ___VARIABLE_cellName:identifier___CellModel 17 | var state: State 18 | 19 | 20 | // MARK: - Initializer 21 | 22 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 23 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 24 | } 25 | 26 | 27 | // MARK: - Computered Properties 28 | 29 | 30 | } 31 | 32 | extension ___FILEBASENAMEASIDENTIFIER___: Requesting { 33 | 34 | enum Request { 35 | case <#request#> 36 | } 37 | 38 | func request(_ request: Request) { 39 | state = .loading 40 | 41 | switch request { 42 | case .<#request#>: 43 | request<#Request#>() 44 | } 45 | } 46 | 47 | private func request<#Request#>() { 48 | 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /Templates/Cell - Co, DaSo, Mo.xctemplate/___FILEBASENAME___CellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 22 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 23 | } 24 | 25 | // MARK: - Computed Properties 26 | 27 | var headingLabelText: String { 28 | return ___VARIABLE_entityName:identifier___.<#name#> 29 | } 30 | } 31 | 32 | extension ___FILEBASENAMEASIDENTIFIER___: ViewStateManageable { 33 | enum ViewState { 34 | case <#state 1#>, <#state 2#> 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Templates/Cell - Co, Mo.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Cell - Co, Mo.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/Cell - Co, Mo.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Cell - Co, Mo.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/Cell - Co, Mo.xctemplate/___FILEBASENAME___Cell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___: UITableViewCell { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | // MARK: - IBOutlets 17 | 18 | @IBOutlet weak var headingLabel: UILabel! 19 | @IBOutlet weak var subheadingLabel: UILabel! 20 | 21 | 22 | // MARK: - View Lifecycle 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | } 27 | 28 | 29 | // MARK: - Reuse 30 | 31 | override func prepareForReuse() { 32 | super.prepareForReuse() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Templates/Cell - Co, Mo.xctemplate/___FILEBASENAME___Cell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Templates/Cell - Co, Mo.xctemplate/___FILEBASENAME___CellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | let model: ___VARIABLE_cellName:identifier___CellModel 16 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 22 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 23 | self.model = ___VARIABLE_cellName:identifier___CellModel(___VARIABLE_entityName:identifier___: ___VARIABLE_entityName:identifier___) 24 | } 25 | 26 | 27 | // MARK: - Computed Properties 28 | 29 | 30 | 31 | // MARK: - Preparation 32 | 33 | func prepare(_ cell: ___VARIABLE_cellName:identifier___Cell) { 34 | cell.headingLabel.text = model.headingLabelText 35 | 36 | prepareBindings(for: cell) 37 | } 38 | 39 | private func prepareBindings(for cell: ___VARIABLE_cellName:identifier___Cell) { 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Templates/Cell - Co, Mo.xctemplate/___FILEBASENAME___CellModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | private let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 22 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 23 | } 24 | 25 | 26 | // MARK: - Computed Properties 27 | 28 | var headingLabelText: String { 29 | return ___VARIABLE_entityName:identifier___.<#name#> 30 | } 31 | } 32 | 33 | extension ___FILEBASENAMEASIDENTIFIER___: ViewStateManageable { 34 | enum ViewState { 35 | case <#state 1#>, <#state 2#> 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Templates/Entity.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Entity.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/Entity.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Entity.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/Entity.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | This will generate a View Controller, Data Source and Model 9 | Summary 10 | This will generate a View Controller, Data Source and Model 11 | SortOrder 12 | 30 13 | DefaultCompletionName 14 | File 15 | MainTemplateFile 16 | ___FILEBASENAME___.swift 17 | Platforms 18 | 19 | com.apple.platform.iphoneos 20 | 21 | Options 22 | 23 | 24 | Description 25 | The name of the entity 26 | Identifier 27 | entityName 28 | Name 29 | Entity Name: 30 | NotPersisted 31 | 32 | Required 33 | 34 | Type 35 | text 36 | 37 | 38 | Default 39 | ___VARIABLE_entityName:identifier___ 40 | Identifier 41 | productName 42 | Type 43 | static 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Templates/Entity.xctemplate/___FILEBASENAME___.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | struct ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | 17 | // MARK: - Initializer 18 | 19 | init?(json: JSON) { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Templates/README.md: -------------------------------------------------------------------------------- 1 | # Template installation 2 | 3 | In terminal: 4 | 5 | ``` 6 | $ mkdir -p ~/Library/Developer/Xcode/Templates/Nemo 7 | 8 | $ cp -R ~/Library/Developer/Xcode/Templates/Nemo/ 9 | ``` -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Section - Co, DaSo, Mo.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Section - Co, DaSo, Mo.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/___FILEBASENAME___Section.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___: UITableViewHeaderFooterView { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | // MARK: - IBOutlets 17 | 18 | @IBOutlet weak var headingLabel: UILabel! 19 | @IBOutlet weak var subheadingLabel: UILabel! 20 | 21 | 22 | // MARK: - View Lifecycle 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | } 27 | 28 | 29 | // MARK: - Reuse 30 | 31 | override func prepareForReuse() { 32 | super.prepareForReuse() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/___FILEBASENAME___Section.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/___FILEBASENAME___SectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | let model: ___VARIABLE_sectionName:identifier___SectionModel 16 | let controllers: [<#CellController#>] 17 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 18 | 19 | 20 | // MARK: - Initializer 21 | 22 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 23 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 24 | self.model = ___VARIABLE_sectionName:identifier___SectionModel(___VARIABLE_entityName:identifier___: ___VARIABLE_entityName:identifier___) 25 | self.controllers = ___VARIABLE_entityName:identifier___.<#items#>.map { <#CellController#>(<#entity#>: $0) } 26 | } 27 | 28 | 29 | // MARK: - Computed Properties 30 | 31 | 32 | 33 | 34 | // MARK: - Preparation 35 | 36 | func prepare(_ section: ___VARIABLE_sectionName:identifier___Section) { 37 | prepareBindings(for: section) 38 | } 39 | 40 | private func prepareBindings(for section: ___VARIABLE_sectionName:identifier___Section) { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/___FILEBASENAME___SectionDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___: StateManageable { 12 | 13 | // MARK: - Properties 14 | 15 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 16 | var model: ___VARIABLE_sectionName:identifier___CellModel 17 | var state: State 18 | 19 | 20 | // MARK: - Initializer 21 | 22 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 23 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 24 | } 25 | 26 | 27 | // MARK: - Computered Properties 28 | 29 | 30 | } 31 | 32 | extension ___FILEBASENAMEASIDENTIFIER___: Requesting { 33 | 34 | enum Request { 35 | case <#request#> 36 | } 37 | 38 | func request(_ request: Request) { 39 | state = .loading 40 | 41 | switch request { 42 | case .<#request#>: 43 | request<#Request#>() 44 | } 45 | } 46 | 47 | private func request<#Request#>() { 48 | 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /Templates/Section - Co, DaSo, Mo.xctemplate/___FILEBASENAME___SectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | private let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 22 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 23 | } 24 | 25 | // MARK: - Computed Properties 26 | 27 | var headingLabelText: String { 28 | return ___VARIABLE_entityName:identifier___.<#name#> 29 | } 30 | } 31 | 32 | extension ___FILEBASENAMEASIDENTIFIER___: ViewStateManageable { 33 | enum ViewState { 34 | case <#state 1#>, <#state 2#> 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Templates/Section - Co, Mo.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Section - Co, Mo.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/Section - Co, Mo.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/Section - Co, Mo.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/Section - Co, Mo.xctemplate/___FILEBASENAME___Section.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___: UITableViewHeaderFooterView { 12 | 13 | // MARK: - Properties 14 | 15 | 16 | // MARK: - IBOutlets 17 | 18 | @IBOutlet weak var headingLabel: UILabel! 19 | @IBOutlet weak var subheadingLabel: UILabel! 20 | 21 | 22 | // MARK: - View Lifecycle 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | } 27 | 28 | 29 | // MARK: - Reuse 30 | 31 | override func prepareForReuse() { 32 | super.prepareForReuse() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Templates/Section - Co, Mo.xctemplate/___FILEBASENAME___Section.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Templates/Section - Co, Mo.xctemplate/___FILEBASENAME___SectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | let model: ___VARIABLE_sectionName:identifier___SectionModel 16 | let controllers: [<#CellController#>] 17 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 18 | 19 | 20 | // MARK: - Initializer 21 | 22 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 23 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 24 | self.model = ___VARIABLE_sectionName:identifier___SectionModel(___VARIABLE_entityName:identifier___: ___VARIABLE_entityName:identifier___) 25 | self.controllers = ___VARIABLE_entityName:identifier___.<#items#>.map { <#CellController#>(<#entity#>: $0) } 26 | } 27 | 28 | 29 | // MARK: - Computed Properties 30 | 31 | 32 | 33 | 34 | // MARK: - Preparation 35 | 36 | func prepare(_ section: ___VARIABLE_sectionName:identifier___Section) { 37 | prepareBindings(for: section) 38 | } 39 | 40 | private func prepareBindings(for section: ___VARIABLE_sectionName:identifier___Section) { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Templates/Section - Co, Mo.xctemplate/___FILEBASENAME___SectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | private let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___ 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___) { 22 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 23 | } 24 | 25 | // MARK: - Computed Properties 26 | 27 | var headingLabelText: String { 28 | return ___VARIABLE_entityName:identifier___.<#name#> 29 | } 30 | } 31 | 32 | extension ___FILEBASENAMEASIDENTIFIER___: ViewStateManageable { 33 | enum ViewState { 34 | case <#state 1#>, <#state 2#> 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Templates/ViewController - DaSo, Mo.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/ViewController - DaSo, Mo.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/ViewController - DaSo, Mo.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyyhope/nemo/f66232afb945b652ba5260876e4b6119a51d9720/Templates/ViewController - DaSo, Mo.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/ViewController - DaSo, Mo.xctemplate/___FILEBASENAME___ViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension ___FILEBASENAMEASIDENTIFIER___ { 12 | typealias CellController = <#CellController#> 13 | typealias SectionController = <#SectionController#> 14 | 15 | enum Section: Int { 16 | case <#section#> 17 | } 18 | } 19 | 20 | final class ___FILEBASENAMEASIDENTIFIER___ { 21 | 22 | // MARK: - Properties 23 | 24 | let ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___? 25 | var state: State 26 | var model: ___VARIABLE_sceneName:identifier___ViewModel 27 | var cellControllers: [CellController] 28 | var sectionControllers: [SectionController] 29 | 30 | // MARK: - Initializer 31 | 32 | init() { 33 | self.state = .loading 34 | self.model = ___VARIABLE_sceneName:identifier___ViewModel(___VARIABLE_entityName:identifier___: nil) 35 | self.___VARIABLE_entityName:identifier___ = nil 36 | } 37 | 38 | 39 | // MARK: - Data 40 | 41 | func section(forIndex index: Int) -> Section { 42 | if let section = Section(rawValue: index) { 43 | return section 44 | } 45 | else { 46 | fatalError("Invalid index passed in. Could not return `Section`") 47 | } 48 | } 49 | 50 | func sectionController(for indexPath: IndexPath) -> SectionController { 51 | return sectionControllers[indexPath.section] 52 | } 53 | 54 | func cellController(for indexPath: IndexPath) -> CellController { 55 | return sectionController(for: indexPath).cellControllers[indexPath.row] 56 | } 57 | } 58 | 59 | 60 | // MARK: - Requesting 61 | 62 | extension ___FILEBASENAMEASIDENTIFIER___: Requesting { 63 | 64 | enum Request { 65 | case <#request#> 66 | } 67 | 68 | // MARK: Request 69 | 70 | func request(_ request: Request) { 71 | state = .loading 72 | 73 | switch request { 74 | case .<#request#>: 75 | request<#Request#>() 76 | } 77 | 78 | } 79 | 80 | private func request<#Request#>() { 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Templates/ViewController - DaSo, Mo.xctemplate/___FILEBASENAME___ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | final class ___FILEBASENAMEASIDENTIFIER___ { 12 | 13 | // MARK: - Properties 14 | 15 | var state: ViewState 16 | var ___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___? 17 | 18 | 19 | // MARK: - Initializer 20 | 21 | init(___VARIABLE_entityName:identifier___: ___VARIABLE_entityType:identifier___?) { 22 | self.___VARIABLE_entityName:identifier___ = ___VARIABLE_entityName:identifier___ 23 | } 24 | } 25 | 26 | extension ___FILEBASENAMEASIDENTIFIER___: ViewStateManageable { 27 | enum ViewState { 28 | case <#state 1#>, <#state 2#> 29 | } 30 | } 31 | --------------------------------------------------------------------------------