├── .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 |
--------------------------------------------------------------------------------