molt 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +56 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +210 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/molt +6 -0
- data/bin/setup +7 -0
- data/bundled/models/Entity+CoreData.swift.liquid +15 -0
- data/bundled/models/Entity.swift.liquid +7 -0
- data/bundled/models/Model.swift.liquid +20 -0
- data/bundled/partials/_header.liquid +5 -0
- data/bundled/swift_helpers/ErrorTypes/APIError.swift +28 -0
- data/bundled/swift_helpers/ErrorTypes/ErrorTypes.swift +12 -0
- data/bundled/swift_helpers/ErrorTypes/PersistenceError.swift +28 -0
- data/bundled/swift_helpers/ISODateTransform.swift +26 -0
- data/bundled/swift_helpers/Identifiable.swift +29 -0
- data/bundled/swift_helpers/Loadable.swift +37 -0
- data/bundled/swift_helpers/Networking/APIRouter.swift +54 -0
- data/bundled/swift_helpers/StoryboardExtensions.swift +49 -0
- data/bundled/template_sets/viper_detail/Presenter.swift.liquid +17 -0
- data/bundled/template_sets/viper_detail/Protocols.swift.liquid +23 -0
- data/bundled/template_sets/viper_detail/View.swift.liquid +25 -0
- data/bundled/template_sets/viper_detail/WireFrame.swift.liquid +21 -0
- data/bundled/template_sets/viper_table/DataManagers/LocalDataManager.swift.liquid +45 -0
- data/bundled/template_sets/viper_table/DataManagers/RemoteDataManager.swift.liquid +26 -0
- data/bundled/template_sets/viper_table/Interactor.swift.liquid +48 -0
- data/bundled/template_sets/viper_table/Presenter.swift.liquid +49 -0
- data/bundled/template_sets/viper_table/Protocols.swift.liquid +58 -0
- data/bundled/template_sets/viper_table/View.swift.liquid +60 -0
- data/bundled/template_sets/viper_table/WireFrame.swift.liquid +43 -0
- data/lib/generamba/string-colorize.rb +31 -0
- data/lib/molt/cli/create_module.rb +76 -0
- data/lib/molt/cli/main.rb +74 -0
- data/lib/molt/cli/setup.rb +23 -0
- data/lib/molt/cli/template_sets.rb +18 -0
- data/lib/molt/configuration.rb +43 -0
- data/lib/molt/template.rb +9 -0
- data/lib/molt/version.rb +3 -0
- data/lib/molt.rb +22 -0
- data/molt.gemspec +32 -0
- data/sample_configs/global_config.yml.erb +7 -0
- metadata +218 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
//
|
2
|
+
// Identifiable.swift
|
3
|
+
// YATodo
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 2/3/18.
|
6
|
+
// Copyright © 2018 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import UIKit
|
10
|
+
import CoreData
|
11
|
+
|
12
|
+
// swiftlint:disable identifier_name
|
13
|
+
protocol Identifiable {
|
14
|
+
static var id: String { get }
|
15
|
+
}
|
16
|
+
|
17
|
+
extension Identifiable {
|
18
|
+
|
19
|
+
// return an introspective string representation of the invoking class, which is
|
20
|
+
// generally a shorter alternative to using R.swift's R.nib.viewClass.identifier
|
21
|
+
static var id: String {
|
22
|
+
return String(describing: self)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
// swiftlint:enable identifier_name
|
26
|
+
|
27
|
+
extension UIViewController: Identifiable { }
|
28
|
+
extension UIView: Identifiable { }
|
29
|
+
extension NSManagedObject: Identifiable { }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
//
|
2
|
+
// Loadable.swift
|
3
|
+
// YATodo
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 2/3/18.
|
6
|
+
// Copyright © 2018 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import UIKit
|
10
|
+
import PKHUD
|
11
|
+
|
12
|
+
protocol Loadable: class {
|
13
|
+
func show(error: Error)
|
14
|
+
func showLoadingIndicator()
|
15
|
+
func hideLoadingIndicator()
|
16
|
+
}
|
17
|
+
|
18
|
+
extension UIViewController: Loadable {
|
19
|
+
|
20
|
+
func show(error: Error) {
|
21
|
+
switch error {
|
22
|
+
case let error as DisplayableError:
|
23
|
+
HUD.flash(.labeledError(title: error.title, subtitle: error.message), delay: 5.0)
|
24
|
+
default:
|
25
|
+
// TODO: error is not displayable so log specifics of error instead
|
26
|
+
HUD.flash(.labeledError(title: "Ooops", subtitle: "Something is not cooperating"), delay: 5.0)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
func showLoadingIndicator() {
|
31
|
+
HUD.show(.progress)
|
32
|
+
}
|
33
|
+
|
34
|
+
func hideLoadingIndicator() {
|
35
|
+
HUD.hide()
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
//
|
2
|
+
// Endpoints.swift
|
3
|
+
// CapoRegime
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 18 Jan 2017.
|
6
|
+
// Copyright © 2017 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import Alamofire
|
10
|
+
|
11
|
+
struct Config {
|
12
|
+
static let API = "http://caporegime.rb/v1"
|
13
|
+
}
|
14
|
+
|
15
|
+
enum APIRouter: URLRequestConvertible {
|
16
|
+
case friends
|
17
|
+
|
18
|
+
private var path: String {
|
19
|
+
switch self {
|
20
|
+
case .friends:
|
21
|
+
return "/friends"
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
private var method: HTTPMethod {
|
26
|
+
switch self {
|
27
|
+
case .friends:
|
28
|
+
return .get
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
private var parameters: Parameters? {
|
33
|
+
return nil
|
34
|
+
}
|
35
|
+
|
36
|
+
private var headers: [String: String] {
|
37
|
+
return [ "Content-Type": "application/json" ]
|
38
|
+
}
|
39
|
+
|
40
|
+
func asURLRequest() throws -> URLRequest {
|
41
|
+
let baseURL = try Config.API.asURL()
|
42
|
+
var urlRequest = URLRequest(url: baseURL.appendingPathComponent(path))
|
43
|
+
|
44
|
+
urlRequest.httpMethod = method.rawValue
|
45
|
+
|
46
|
+
for (header, value) in headers {
|
47
|
+
urlRequest.setValue(value, forHTTPHeaderField: header)
|
48
|
+
}
|
49
|
+
|
50
|
+
urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters)
|
51
|
+
|
52
|
+
return urlRequest
|
53
|
+
}
|
54
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
//
|
2
|
+
// StoryboardExtensions.swift
|
3
|
+
// YATodo
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 18 Jan 2017.
|
6
|
+
// Copyright © 2018 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import UIKit
|
10
|
+
|
11
|
+
enum Storyboard: String {
|
12
|
+
case main = "Main"
|
13
|
+
|
14
|
+
var storyboard: UIStoryboard {
|
15
|
+
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
|
16
|
+
}
|
17
|
+
|
18
|
+
fileprivate func viewController<T: UIViewController>(as: T.Type, identifier: String,
|
19
|
+
function: String = #function, line: Int = #line, file: String = #file) -> T {
|
20
|
+
|
21
|
+
guard let scene = storyboard.instantiateViewController(withIdentifier: identifier) as? T else {
|
22
|
+
fatalError("ViewController with identifier \(identifier), not found. Reference: \(self.rawValue) Storyboard.\nFile : \(file) \nLine Number : \(line) \nFunction : \(function)")
|
23
|
+
}
|
24
|
+
|
25
|
+
return scene
|
26
|
+
}
|
27
|
+
|
28
|
+
func initialViewController() -> UIViewController? {
|
29
|
+
return storyboard.instantiateInitialViewController()
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
extension UIViewController {
|
34
|
+
|
35
|
+
static func instantiate(from storyboard: Storyboard) -> Self {
|
36
|
+
return storyboard.viewController(as: self, identifier: self.id)
|
37
|
+
}
|
38
|
+
|
39
|
+
static func instantiate(from storyboard: Storyboard, identifier: String) -> Self {
|
40
|
+
return storyboard.viewController(as: self, identifier: identifier)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
extension UITableViewCell {
|
45
|
+
|
46
|
+
static var nib: UINib {
|
47
|
+
return UINib(nibName: self.id, bundle: Bundle.main)
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Interactor.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
final class {{ module_name }}Presenter {
|
5
|
+
|
6
|
+
weak var view: {{ module_name }}ViewProtocol?
|
7
|
+
var wireFrame: {{ module_name }}WireFrameProtocol?
|
8
|
+
var dataSource: {{ model }}?
|
9
|
+
}
|
10
|
+
|
11
|
+
// MARK: CALLED BY VIEW
|
12
|
+
extension {{ module_name }}Presenter: {{ module_name }}PresenterProtocol {
|
13
|
+
|
14
|
+
func viewDidLoad() {
|
15
|
+
view?.render(content: dataSource)
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Protocols.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
// MARK: Called by VIEW -> Implemented BY PRESENTER
|
7
|
+
protocol {{ module_name }}PresenterProtocol: class {
|
8
|
+
weak var view: {{ module_name }}ViewProtocol? { get set }
|
9
|
+
weak var wireFrame: {{ module_name }}WireFrameProtocol? { get set }
|
10
|
+
var dataSource: {{ model }}? { get set }
|
11
|
+
|
12
|
+
func viewDidLoad()
|
13
|
+
}
|
14
|
+
|
15
|
+
// MARK: PRESENTER -> VIEW
|
16
|
+
protocol {{ module_name }}ViewProtocol: Loadable {
|
17
|
+
func render(content: {{ model }}?)
|
18
|
+
}
|
19
|
+
|
20
|
+
// MARK: PRESENTER -> WIREFRAME
|
21
|
+
protocol {{ module_name }}WireFrameProtocol: class {
|
22
|
+
static func prepareModule(with object: {{ model }}) -> UIViewController
|
23
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}View.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
class {{ module_name }}View: UIViewController {
|
7
|
+
|
8
|
+
var presenter: {{ module_name }}PresenterProtocol?
|
9
|
+
|
10
|
+
@IBOutlet weak var titleLabel: UILabel!
|
11
|
+
|
12
|
+
override func viewDidLoad() {
|
13
|
+
super.viewDidLoad()
|
14
|
+
presenter?.viewDidLoad()
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// MARK: CALLED BY PRESENTER
|
19
|
+
extension {{ module_name }}View: {{ module_name }}ViewProtocol {
|
20
|
+
|
21
|
+
func render(content: {{ model }}?) {
|
22
|
+
guard let content = content else { return }
|
23
|
+
titleLabel?.text = content.title
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}WireFrame.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
final class {{ module_name }}WireFrame: {{ module_name }}WireFrameProtocol {
|
7
|
+
|
8
|
+
static func prepareModule(with object: {{ model }}) -> UIViewController {
|
9
|
+
let view = {{ module_name }}View.instantiate(from: .main)
|
10
|
+
|
11
|
+
let presenter: {{ module_name }}PresenterProtocol = {{ module_name }}Presenter()
|
12
|
+
let wireFrame = {{ module_name }}WireFrame()
|
13
|
+
|
14
|
+
view.presenter = presenter
|
15
|
+
presenter.view = view
|
16
|
+
presenter.dataSource = object
|
17
|
+
presenter.wireFrame = wireFrame
|
18
|
+
|
19
|
+
return view
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}LocalDataManager.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import CoreData
|
5
|
+
import Hydra
|
6
|
+
|
7
|
+
// MARK: CALLED BY INTERACTOR
|
8
|
+
class {{ module_name }}LocalDataManager: {{ module_name }}LocalDataManagerProtocol {
|
9
|
+
|
10
|
+
func retrieveDataFromStorage() -> Promise<[{{ entity }}]> {
|
11
|
+
return Promise<[{{ entity }}]>(in: .main) { resolve, reject, _ in
|
12
|
+
guard let moc = CoreDataStore.moc else {
|
13
|
+
reject(PersistenceError.mocNotFound)
|
14
|
+
return
|
15
|
+
}
|
16
|
+
do {
|
17
|
+
let request: NSFetchRequest<{{ entity }}> = NSFetchRequest(entityName: {{ entity }}.id)
|
18
|
+
resolve(try moc.fetch(request))
|
19
|
+
} catch {
|
20
|
+
reject(PersistenceError.objectNotFound)
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
func saveEntity(id: Int, title: String) -> Promise<Void> {
|
26
|
+
return Promise<Void>(in: .main) { resolve, reject, _ in
|
27
|
+
guard let moc = CoreDataStore.moc else {
|
28
|
+
reject(PersistenceError.mocNotFound)
|
29
|
+
return
|
30
|
+
}
|
31
|
+
|
32
|
+
do {
|
33
|
+
if let entity = NSEntityDescription.entity(forEntityName: {{ entity }}.id, in: moc) {
|
34
|
+
let object = {{ entity }}(entity: entity, insertInto: moc)
|
35
|
+
object.id = Int32(id)
|
36
|
+
object.title = title
|
37
|
+
try moc.save()
|
38
|
+
resolve(())
|
39
|
+
}
|
40
|
+
} catch {
|
41
|
+
reject(PersistenceError.couldNotSaveObject)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}RemoteDataManager.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import Alamofire
|
5
|
+
import AlamofireObjectMapper
|
6
|
+
import Hydra
|
7
|
+
|
8
|
+
// MARK: CALLED BY INTERACTOR
|
9
|
+
class {{ module_name }}RemoteDataManager: {{ module_name }}RemoteDataManagerProtocol {
|
10
|
+
|
11
|
+
func retrieveDataFromAPI() -> Promise<[{{ model }}]> {
|
12
|
+
return Promise<[{{ model }}]>(in: .background) { resolve, reject, _ in
|
13
|
+
Alamofire.request(APIRouter.{{ model | downcase }})
|
14
|
+
.validate()
|
15
|
+
.responseArray(keyPath: "{{ model | downcase }}") { (response: DataResponse<[{{ model }}]>) in
|
16
|
+
|
17
|
+
switch response.result {
|
18
|
+
case .success(let data):
|
19
|
+
resolve(data)
|
20
|
+
case .failure:
|
21
|
+
reject(APIError.unknown)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Interactor.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
final class {{ module_name }}Interactor {
|
5
|
+
weak var presenter: {{ module_name }}InteractorOutputProtocol?
|
6
|
+
var localDataManager: {{ module_name }}LocalDataManagerProtocol?
|
7
|
+
var remoteDataManager: {{ module_name }}RemoteDataManagerProtocol?
|
8
|
+
}
|
9
|
+
|
10
|
+
// MARK: CALLED BY PRESENTER
|
11
|
+
extension {{ module_name }}Interactor: {{ module_name }}InteractorProtocol {
|
12
|
+
|
13
|
+
func retrieveData() {
|
14
|
+
localDataManager?.retrieveDataFromStorage().then({ [weak self] persistedData in
|
15
|
+
let data: [{{ model }}] = persistedData.map {
|
16
|
+
return {{ model }}(id: Int($0.id), title: $0.title ?? "", dueDate: $0.dueDate)
|
17
|
+
}
|
18
|
+
|
19
|
+
if !data.isEmpty {
|
20
|
+
self?.presenter?.present(data: data)
|
21
|
+
} else {
|
22
|
+
self?.retrieveDataFromAPI()
|
23
|
+
}
|
24
|
+
}).catch({ [weak self] error in
|
25
|
+
self?.presenter?.present(error: error)
|
26
|
+
})
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
extension {{ module_name }}Interactor {
|
31
|
+
|
32
|
+
fileprivate func retrieveDataFromAPI() {
|
33
|
+
remoteDataManager?.retrieveDataFromAPI().then({ [weak self] data in
|
34
|
+
self?.persist(data: data)
|
35
|
+
self?.presenter?.present(data: data)
|
36
|
+
}).catch({ [weak self] error in
|
37
|
+
self?.presenter?.present(error: error)
|
38
|
+
})
|
39
|
+
}
|
40
|
+
|
41
|
+
fileprivate func persist(data: [{{ model }}]) {
|
42
|
+
for item in data {
|
43
|
+
localDataManager?.saveEntity(id: item.id, title: item.title).catch({ [weak self] error in
|
44
|
+
self?.presenter?.present(error: error)
|
45
|
+
})
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Presenter.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
final class {{ module_name }}Presenter {
|
5
|
+
|
6
|
+
weak var view: {{ module_name }}ViewProtocol?
|
7
|
+
var wireFrame: {{ module_name }}WireFrameProtocol?
|
8
|
+
var interactor: {{ module_name }}InteractorProtocol?
|
9
|
+
var dataSource: [{{ model }}] = []
|
10
|
+
}
|
11
|
+
|
12
|
+
// MARK: CALLED BY VIEW
|
13
|
+
extension {{ module_name }}Presenter: {{ module_name }}PresenterProtocol {
|
14
|
+
|
15
|
+
func viewDidLoad() {
|
16
|
+
view?.showLoadingIndicator()
|
17
|
+
interactor?.retrieveData()
|
18
|
+
}
|
19
|
+
|
20
|
+
var numberOfSection: Int { return self.dataSource.count + 1 }
|
21
|
+
|
22
|
+
func numberOfRows(in section: Int) -> Int {
|
23
|
+
return dataSource.count
|
24
|
+
}
|
25
|
+
|
26
|
+
func content(at row: Int) -> {{ model }}? {
|
27
|
+
return dataSource[row]
|
28
|
+
}
|
29
|
+
|
30
|
+
func process(object: {{ model }}) {
|
31
|
+
guard let view = view else { return }
|
32
|
+
wireFrame?.navigate(to: object, from: view)
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
// MARK: CALLED BY INTERACTOR
|
37
|
+
extension {{ module_name }}Presenter: {{ module_name }}InteractorOutputProtocol {
|
38
|
+
|
39
|
+
func present(data: [{{ model }}]) {
|
40
|
+
self.dataSource = data
|
41
|
+
view?.hideLoadingIndicator()
|
42
|
+
view?.render()
|
43
|
+
}
|
44
|
+
|
45
|
+
func present(error: Error) {
|
46
|
+
view?.hideLoadingIndicator()
|
47
|
+
view?.show(error: error)
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Protocols.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
import Hydra
|
6
|
+
|
7
|
+
// MARK: Called by VIEW -> Implemented BY PRESENTER
|
8
|
+
protocol {{ module_name }}PresenterProtocol: class {
|
9
|
+
var view: {{ module_name }}ViewProtocol? { get set }
|
10
|
+
var wireFrame: {{ module_name }}WireFrameProtocol? { get set }
|
11
|
+
var interactor: {{ module_name }}InteractorProtocol? { get set }
|
12
|
+
|
13
|
+
func viewDidLoad()
|
14
|
+
|
15
|
+
// datasource
|
16
|
+
var numberOfSection: Int { get }
|
17
|
+
func numberOfRows(in section: Int) -> Int
|
18
|
+
func content(at row: Int) -> <{{ model }}>?
|
19
|
+
|
20
|
+
func process(object: {{ model }})
|
21
|
+
}
|
22
|
+
|
23
|
+
// MARK: PRESENTER -> INTERACTOR
|
24
|
+
protocol {{ module_name }}InteractorProtocol: class {
|
25
|
+
var presenter: {{ module_name }}InteractorOutputProtocol? { get set }
|
26
|
+
var localDataManager: {{ module_name }}LocalDataManagerProtocol? { get set }
|
27
|
+
var remoteDataManager: {{ module_name }}RemoteDataManagerProtocol? { get set }
|
28
|
+
|
29
|
+
func retrieveData()
|
30
|
+
}
|
31
|
+
|
32
|
+
// MARK: INTERACTOR -> REMOTEDATAMANAGER
|
33
|
+
protocol {{ module_name }}RemoteDataManagerProtocol: class {
|
34
|
+
func retrieveDataFromAPI() -> Promise<[{{ model }}]>
|
35
|
+
}
|
36
|
+
|
37
|
+
// MARK: INTERACTOR -> LOCALDATAMANAGER
|
38
|
+
protocol {{ module_name }}LocalDataManagerProtocol: class {
|
39
|
+
func retrieveDataFromStorage() -> Promise<[{{ entity }}]>
|
40
|
+
func saveEntity(id: Int, title: String) -> Promise<Void>
|
41
|
+
}
|
42
|
+
|
43
|
+
// MARK: INTERACTOR -> PRESENTER
|
44
|
+
protocol {{ module_name }}InteractorOutputProtocol: class {
|
45
|
+
func present(data: [{{ model }}])
|
46
|
+
func present(error: Error)
|
47
|
+
}
|
48
|
+
|
49
|
+
// MARK: PRESENTER -> VIEW
|
50
|
+
protocol {{ module_name }}ViewProtocol: Loadable {
|
51
|
+
func render()
|
52
|
+
}
|
53
|
+
|
54
|
+
// MARK: PRESENTER -> WIREFRAME
|
55
|
+
protocol {{ module_name }}WireFrameProtocol: class {
|
56
|
+
static func prepareModule() -> UIViewController
|
57
|
+
func navigate(to object: {{ model }}, from view: {{ module_name }}ViewProtocol)
|
58
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}View.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
class {{ module_name }}View: UIViewController {
|
7
|
+
|
8
|
+
var presenter: {{ module_name }}PresenterProtocol?
|
9
|
+
|
10
|
+
@IBOutlet weak var tableView: UITableView!
|
11
|
+
|
12
|
+
override func viewDidLoad() {
|
13
|
+
super.viewDidLoad()
|
14
|
+
tableView.tableFooterView = UIView()
|
15
|
+
tableView.register({{ entity}}Cell.nib, forCellReuseIdentifier: {{ entity}}Cell.id)
|
16
|
+
presenter?.viewDidLoad()
|
17
|
+
}
|
18
|
+
|
19
|
+
override func viewWillAppear(_ animated: Bool) {
|
20
|
+
super.viewWillAppear(animated)
|
21
|
+
if let selectedRow = tableView.indexPathForSelectedRow {
|
22
|
+
tableView.deselectRow(at: selectedRow, animated: true)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
// MARK: CALLED BY PRESENTER
|
28
|
+
extension {{ module_name }}View: {{ module_name }}ViewProtocol {
|
29
|
+
|
30
|
+
func render() {
|
31
|
+
tableView.reloadData()
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
// MARK: UITABLEVIEW DATASOURCE
|
36
|
+
extension {{ module_name }}View: UITableViewDataSource {
|
37
|
+
|
38
|
+
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
39
|
+
return presenter?.numberOfRows(in: section) ?? 0
|
40
|
+
}
|
41
|
+
|
42
|
+
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
43
|
+
guard let content = presenter?.content(at: indexPath.row),
|
44
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: {{ entity}}Cell.id, for: indexPath) as? {{ entity}}Cell else {
|
45
|
+
return tableView.dequeueReusableCell(withIdentifier: {{ entity}}Cell.id, for: indexPath)
|
46
|
+
}
|
47
|
+
|
48
|
+
cell.configure(with: content)
|
49
|
+
return cell
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// MARK: UITABLEVIEW DELEGATE
|
54
|
+
extension {{ module_name }}View: UITableViewDelegate {
|
55
|
+
|
56
|
+
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
57
|
+
guard let content = presenter?.content(at: indexPath.row) else { return }
|
58
|
+
presenter?.process(object: content)
|
59
|
+
}
|
60
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}WireFrame.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
final class {{ module_name }}WireFrame {
|
7
|
+
|
8
|
+
static func prepareModule() -> UIViewController {
|
9
|
+
let navController = UINavigationController.instantiate(from: .main, identifier: "mainNavigationController")
|
10
|
+
|
11
|
+
guard let view = navController.childViewControllers.first as? {{ module_name }}View else {
|
12
|
+
return UIViewController()
|
13
|
+
}
|
14
|
+
|
15
|
+
let presenter: {{ module_name }}PresenterProtocol & {{ module_name }}InteractorOutputProtocol = {{ module_name }}Presenter()
|
16
|
+
let interactor: {{ module_name }}InteractorProtocol = {{ module_name }}Interactor()
|
17
|
+
let wireFrame: {{ module_name }}WireFrameProtocol = {{ module_name }}WireFrame()
|
18
|
+
let localDataManager: {{ module_name }}LocalDataManagerProtocol = {{ module_name }}LocalDataManager()
|
19
|
+
let remoteDataManager: {{ module_name }}RemoteDataManagerProtocol = {{ module_name }}RemoteDataManager()
|
20
|
+
|
21
|
+
// INTEGRATION
|
22
|
+
view.presenter = presenter
|
23
|
+
presenter.view = view
|
24
|
+
presenter.wireFrame = wireFrame
|
25
|
+
presenter.interactor = interactor
|
26
|
+
interactor.presenter = presenter
|
27
|
+
interactor.localDataManager = localDataManager
|
28
|
+
interactor.remoteDataManager = remoteDataManager
|
29
|
+
|
30
|
+
return navController
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
// MARK: NAVIGATION
|
35
|
+
extension {{ module_name }}WireFrame: {{ module_name }}WireFrameProtocol {
|
36
|
+
|
37
|
+
func navigate(to object: {{ model }}, from view: {{ module_name }}ViewProtocol) {
|
38
|
+
guard let sourceView = view as? UIViewController else { return }
|
39
|
+
|
40
|
+
let detailVC = {{ module_name }}DetailWireFrame.prepareModule(with: object)
|
41
|
+
sourceView.navigationController?.pushViewController(detailVC, animated: true)
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Borrowed from Egor Tolstoy, Beniamin Sarkisyan, Andrey Zarembo et al (Generamba/Rambler)
|
2
|
+
class String
|
3
|
+
|
4
|
+
def colorize(color_code)
|
5
|
+
"\e[#{color_code}m#{self}\e[0m"
|
6
|
+
end
|
7
|
+
|
8
|
+
def red
|
9
|
+
colorize(31)
|
10
|
+
end
|
11
|
+
|
12
|
+
def green
|
13
|
+
colorize(32)
|
14
|
+
end
|
15
|
+
|
16
|
+
def yellow
|
17
|
+
colorize(33)
|
18
|
+
end
|
19
|
+
|
20
|
+
def blue
|
21
|
+
colorize(34)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pink
|
25
|
+
colorize(35)
|
26
|
+
end
|
27
|
+
|
28
|
+
def light_blue
|
29
|
+
colorize(36)
|
30
|
+
end
|
31
|
+
end
|