├── README.md
├── 4VEngine.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── .gitignore
├── Source
├── Models
│ └── User.swift
├── BaseTypes
│ ├── Router.swift
│ └── ViewPresenter.swift
├── Stories
│ └── Users
│ │ ├── UsersList
│ │ ├── View
│ │ │ ├── UsersListTableViewCell.swift
│ │ │ ├── UsersListTableViewController.xib
│ │ │ ├── UsersListTableViewCell.xib
│ │ │ └── UsersListTableViewController.swift
│ │ ├── Interactor
│ │ │ ├── UsersParser.swift
│ │ │ └── UsersListInteractor.swift
│ │ ├── ViewFactory
│ │ │ └── UsersListViewFactory.swift
│ │ ├── ViewPresenter
│ │ │ └── UsersListViewPresenter.swift
│ │ └── ViewModel
│ │ │ └── UsersListViewModel.swift
│ │ ├── UserDetails
│ │ ├── ViewFactory
│ │ │ └── UserDetailsViewFactory.swift
│ │ ├── ViewModel
│ │ │ └── UserDetailsViewModel.swift
│ │ ├── ViewPresenter
│ │ │ └── UserDetailsViewPresenter.swift
│ │ └── View
│ │ │ ├── UserDetailsViewController.swift
│ │ │ └── UserDetailsViewController.xib
│ │ └── UsersRouter.swift
├── Extensions
│ ├── UIViewControllerExtension.swift
│ └── UIView.swift
├── AppDelegate.swift
└── Services
│ └── HTTPClient.swift
├── Supporting Files
├── Info.plist
└── Base.lproj
│ └── LaunchScreen.storyboard
└── Assets.xcassets
└── AppIcon.appiconset
└── Contents.json
/README.md:
--------------------------------------------------------------------------------
1 | # 4VEngine
2 |
3 | Sample app to explain 4V Engine - Software Architecture
4 |
--------------------------------------------------------------------------------
/4VEngine.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.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 | ## Carthage
36 | Carthage/Build
37 |
38 | ## Cocoapods
39 | Pods*
--------------------------------------------------------------------------------
/Source/Models/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | struct User {
28 | let name: String
29 | }
30 |
--------------------------------------------------------------------------------
/Source/BaseTypes/Router.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Router.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | protocol Router: class {
28 | func showInitial()
29 | func close()
30 | }
31 |
--------------------------------------------------------------------------------
/Source/BaseTypes/ViewPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewPresenter.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | protocol ViewPresenter: class {
30 | func present(in parentViewController: UIViewController)
31 | func remove()
32 | }
33 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/View/UsersListTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersListTableViewCell.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | class UsersListTableViewCell: UITableViewCell {
30 |
31 | func configure(userName: String) {
32 | textLabel?.text = userName
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/Interactor/UsersParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersParser.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | final class UsersParser {
28 |
29 | static func parse(_ jsonData: [[String: Any]]) -> [User] {
30 | return jsonData.flatMap {
31 | guard let name = $0["name"] as? String else { return nil }
32 | return User(name: name)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Source/Extensions/UIViewControllerExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewControllerExtension.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | extension UIViewController {
30 | func addFillerChildViewController(_ childController: UIViewController, toView: UIView? = nil) {
31 | addChildViewController(childController)
32 | var parentView = childController.view
33 | if let toView = toView {
34 | parentView = toView
35 | }
36 | view.addFillerSubview(parentView!)
37 | childController.didMove(toParentViewController: self)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UserDetails/ViewFactory/UserDetailsViewFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDetailsViewFactory.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | final class UserDetailsViewFactory {
30 | private var viewModel: UserDetailsViewModel
31 | private(set) var viewController: UserDetailsViewController
32 |
33 | init(user: User, navigationDelegate: UserDetailsNavigationDelegate) {
34 | viewModel = UserDetailsViewModel(user: user, navigationDelegate: navigationDelegate)
35 | viewController = UserDetailsViewController(viewModel: viewModel)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UserDetails/ViewModel/UserDetailsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDetailsViewModel.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | final class UserDetailsViewModel {
28 |
29 | var userName: String
30 |
31 | private weak var navigationDelegate: UserDetailsNavigationDelegate?
32 |
33 | init(user: User, navigationDelegate: UserDetailsNavigationDelegate) {
34 | userName = user.name
35 | self.navigationDelegate = navigationDelegate
36 | }
37 |
38 | func closeButtonDidTap() {
39 | navigationDelegate?.userDetailsCloseDidTap()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/ViewFactory/UsersListViewFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersListViewFactory.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | final class UsersListViewFactory {
30 |
31 | let viewController: UsersListTableViewController
32 | private let viewModel: UsersListViewModel
33 |
34 | init(navigationDelegate: UsersListNavigationDelegate) {
35 | let interactor = UsersListInteractor()
36 | viewModel = UsersListViewModel(usersListInteractor: interactor, navigationDelegate: navigationDelegate)
37 | viewController = UsersListTableViewController(viewModel: viewModel)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/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 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Source/Stories/Users/UserDetails/ViewPresenter/UserDetailsViewPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDetailsViewPresenter.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | final class UserDetailsViewPresenter: ViewPresenter {
30 |
31 | private let viewFactory: UserDetailsViewFactory
32 |
33 | init(user: User, navigationDelegate: UserDetailsNavigationDelegate) {
34 | viewFactory = UserDetailsViewFactory(user: user, navigationDelegate: navigationDelegate)
35 | }
36 |
37 | func present(in parentViewController: UIViewController) {
38 | parentViewController.addFillerChildViewController(viewFactory.viewController)
39 | }
40 |
41 | func remove() {
42 | viewFactory.viewController.view.removeFromSuperview()
43 | viewFactory.viewController.removeFromParentViewController()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Source/Extensions/UIView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | extension UIView {
30 | func addFillerSubview(_ subview: UIView) {
31 | subview.translatesAutoresizingMaskIntoConstraints = false
32 |
33 | addSubview(subview)
34 |
35 | let views = ["subview": subview]
36 | let verticalConstraint = NSLayoutConstraint.constraints(
37 | withVisualFormat: "V:|[subview]|",
38 | options: [],
39 | metrics: nil,
40 | views: views)
41 | let horizontalConstraint = NSLayoutConstraint.constraints(
42 | withVisualFormat: "H:|[subview]|",
43 | options: [],
44 | metrics: nil,
45 | views: views)
46 |
47 | addConstraints(verticalConstraint + horizontalConstraint)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Source/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | @UIApplicationMain
30 | class AppDelegate: UIResponder, UIApplicationDelegate {
31 |
32 | var window: UIWindow?
33 | var mainRouter: Router?
34 |
35 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
36 |
37 | let rootViewController = UIViewController()
38 |
39 | window = UIWindow()
40 | window?.rootViewController = rootViewController
41 |
42 | let usersRouter = UsersRouter(parentViewController: rootViewController)
43 | usersRouter.showInitial()
44 |
45 | window?.makeKeyAndVisible()
46 |
47 | mainRouter = usersRouter
48 |
49 | return true
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/View/UsersListTableViewController.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 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UserDetails/View/UserDetailsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDetailsViewController.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | class UserDetailsViewController: UIViewController {
30 |
31 | @IBOutlet private weak var userNameLabel: UILabel!
32 |
33 | private unowned let viewModel: UserDetailsViewModel
34 |
35 | init(viewModel: UserDetailsViewModel) {
36 | self.viewModel = viewModel
37 |
38 | super.init(nibName: nil, bundle: nil)
39 | }
40 |
41 | required init?(coder aDecoder: NSCoder) {
42 | fatalError("init(coder:) has not been implemented")
43 | }
44 |
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 |
48 | userNameLabel.text = viewModel.userName
49 | }
50 |
51 | @IBAction func onCloseButton(_ sender: Any) {
52 | viewModel.closeButtonDidTap()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Source/Services/HTTPClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPClient.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | protocol HTTPClientType: class {
30 | func get(at url: URL, completionHandler: @escaping (Data) -> Void)
31 | func cancel()
32 | }
33 |
34 | final class HTTPClient {
35 |
36 | private let session = URLSession(configuration: .default, delegate: nil, delegateQueue: nil)
37 |
38 | private var dataTask: URLSessionDataTask?
39 |
40 | func get(at url: URL, completionHandler: @escaping (Data) -> Void) {
41 |
42 | dataTask = session.dataTask(with: url) { (data, _, error) in
43 | if let error = error {
44 | print("Get error \(error)")
45 | return
46 | }
47 |
48 | guard let data = data else { return }
49 |
50 | completionHandler(data)
51 | }
52 | dataTask?.resume()
53 | }
54 |
55 | func cancel() {
56 | dataTask?.cancel()
57 | }
58 | }
59 |
60 | extension HTTPClient: HTTPClientType {}
61 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/ViewPresenter/UsersListViewPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersListViewPresenter.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | final class UsersListViewPresenter: ViewPresenter {
30 |
31 | private let viewFactory: UsersListViewFactory
32 |
33 | init(navigationDelegate: UsersListNavigationDelegate) {
34 | viewFactory = UsersListViewFactory(navigationDelegate: navigationDelegate)
35 | }
36 |
37 | // Method to add the component in a parent view controller
38 | func present(in parentViewController: UIViewController) {
39 | // Method added in the UIViewControllerExtension to add a child view controller filling the parent view with
40 | // autolayout.
41 | // Look at UIViewControllerExtension.swift for more details
42 | parentViewController.addFillerChildViewController(viewFactory.viewController)
43 | }
44 |
45 | // Method to remove the component from the device screen
46 | func remove() {
47 | viewFactory.viewController.view.removeFromSuperview()
48 | viewFactory.viewController.removeFromParentViewController()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/Interactor/UsersListInteractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersListInteractor.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | final class UsersListInteractor {
30 | private let httpClient: HTTPClientType
31 |
32 | init(httpClient: HTTPClientType = HTTPClient()) {
33 | self.httpClient = httpClient
34 | }
35 |
36 | func fetchUsers(completion: @escaping ([User]) -> Void) {
37 |
38 | guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
39 | completion([])
40 | return
41 | }
42 |
43 | let httpCompletionHandler: (Data) -> Void = { data in
44 | guard let jsonData = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [[String: Any]] else {
45 | completion([])
46 | return
47 | }
48 |
49 | let users = UsersParser.parse(jsonData)
50 | completion(users)
51 | }
52 |
53 | httpClient.get(at: url, completionHandler: httpCompletionHandler)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/View/UsersListTableViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/ViewModel/UsersListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersListViewModel.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | // Delegate used to bind the UI and the View Model
30 | protocol UsersListViewModelDelegate: class {
31 | func usersListUpdated()
32 | }
33 |
34 | final class UsersListViewModel {
35 |
36 | // Value used in View to know how many table rows to show
37 | var usersCount: Int {
38 | return users.count
39 | }
40 |
41 | private weak var delegate: UsersListViewModelDelegate?
42 | private weak var navigationDelegate: UsersListNavigationDelegate?
43 |
44 | private let usersListInteractor: UsersListInteractor
45 | private var users = [User]()
46 |
47 | init(usersListInteractor: UsersListInteractor, navigationDelegate: UsersListNavigationDelegate) {
48 | self.navigationDelegate = navigationDelegate
49 | self.usersListInteractor = usersListInteractor
50 |
51 | loadUsers()
52 | }
53 |
54 | // Asks the interactor the list of users to show in the UI
55 | private func loadUsers() {
56 | usersListInteractor.fetchUsers { [unowned self] users in
57 | self.users = users
58 |
59 | DispatchQueue.main.async {
60 | // Method used to ask the View to update the table view with the new data
61 | self.delegate?.usersListUpdated()
62 | }
63 | }
64 | }
65 |
66 | private func user(at indexPath: IndexPath) -> User {
67 | return users[indexPath.row]
68 | }
69 |
70 | // Sets the delegate to bind the UI
71 | func bind(_ delegate: UsersListViewModelDelegate) {
72 | self.delegate = delegate
73 | }
74 |
75 | // Method used in View to know which user name to show in the cell
76 | func userName(at indexPath: IndexPath) -> String {
77 | let user = self.user(at: indexPath)
78 | return user.name
79 | }
80 |
81 | // Method called in View when the user taps a cell detail button
82 | func userDetailsSelected(at indexPath: IndexPath) {
83 | let user = self.user(at: indexPath)
84 |
85 | // Method used to notify the router that a user has been selected
86 | navigationDelegate?.usersListSelected(for: user)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersRouter.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | // Delegate used to navigate from users list to user details
30 | protocol UsersListNavigationDelegate: class {
31 | func usersListSelected(for user: User)
32 | }
33 |
34 | // Delegate used to close the user details
35 | protocol UserDetailsNavigationDelegate: class {
36 | func userDetailsCloseDidTap()
37 | }
38 |
39 | final class UsersRouter {
40 |
41 | // Parent view controller to add the components
42 | fileprivate let parentViewController: UIViewController
43 |
44 | // Dictionary of presenters used
45 | fileprivate var presenters = [String: ViewPresenter]()
46 |
47 | init(parentViewController: UIViewController) {
48 | self.parentViewController = parentViewController
49 | }
50 | }
51 |
52 | extension UsersRouter: Router {
53 | // Shows first component, the users list
54 | func showInitial() {
55 | let usersListPresenter = UsersListViewPresenter(navigationDelegate: self)
56 | usersListPresenter.present(in: parentViewController)
57 |
58 | presenters["UsersList"] = usersListPresenter
59 | }
60 |
61 | // Closes the router removing all its components
62 | func close() {
63 | presenters.keys.forEach { [unowned self] in
64 | self.removePresenter(for: $0)
65 | }
66 | }
67 |
68 | fileprivate func removePresenter(for key: String) {
69 | let userDetailsPresenter = presenters[key]
70 | userDetailsPresenter?.remove()
71 |
72 | presenters[key] = nil
73 | }
74 | }
75 |
76 | extension UsersRouter: UsersListNavigationDelegate {
77 | func usersListSelected(for user: User) {
78 | let userDetailsPresenter = UserDetailsViewPresenter(user: user, navigationDelegate: self)
79 | userDetailsPresenter.present(in: parentViewController)
80 |
81 | presenters["UserDetails"] = userDetailsPresenter
82 | }
83 | }
84 |
85 | extension UsersRouter: UserDetailsNavigationDelegate {
86 | func userDetailsCloseDidTap() {
87 | // Removes user details components from the parent view controller
88 | removePresenter(for: "UserDetails")
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UsersList/View/UsersListTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersListTableViewController.swift
3 | //
4 | // 4VEngine
5 | //
6 | // Copyright (c) 2017 Marco Santarossa (https://marcosantadev.com)
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | class UsersListTableViewController: UITableViewController {
30 |
31 | // The view model used for the binding
32 | private unowned let viewModel: UsersListViewModel
33 |
34 | init(viewModel: UsersListViewModel) {
35 | self.viewModel = viewModel
36 |
37 | super.init(nibName: nil, bundle: nil)
38 | }
39 |
40 | required init?(coder aDecoder: NSCoder) {
41 | fatalError("init(coder:) has not been implemented")
42 | }
43 |
44 | override func viewDidLoad() {
45 | super.viewDidLoad()
46 |
47 | let nib = UINib.init(nibName: "UsersListTableViewCell", bundle: nil)
48 | tableView.register(nib, forCellReuseIdentifier: "Cell")
49 |
50 | // Binds View and View Model
51 | viewModel.bind(self)
52 | }
53 |
54 | // MARK: - Table view data source
55 |
56 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
57 | // Asks the View Model how many users are available
58 | return viewModel.usersCount
59 | }
60 |
61 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
62 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
63 |
64 | // If it's the custom cell, configure it
65 | if let usersListCell = cell as? UsersListTableViewCell {
66 | // Asks the View Model the user name for a specific index path
67 | let userName = viewModel.userName(at: indexPath)
68 | // Sets the user name
69 | usersListCell.configure(userName: userName)
70 | }
71 |
72 | return cell
73 | }
74 |
75 | override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
76 | // Notifies the View Model that a detail button has been tapped
77 | viewModel.userDetailsSelected(at: indexPath)
78 | }
79 | }
80 |
81 | extension UsersListTableViewController: UsersListViewModelDelegate {
82 | // Method called by View Model when the user data is changed
83 | func usersListUpdated() {
84 | tableView.reloadData()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Source/Stories/Users/UserDetails/View/UserDetailsViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
29 |
35 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Supporting Files/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 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/4VEngine.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A0854F311F51AAAA005D984C /* UsersParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0854F301F51AAAA005D984C /* UsersParser.swift */; };
11 | A0868AB61F4D6BE8002B1D24 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AB41F4D6BE8002B1D24 /* AppDelegate.swift */; };
12 | A0868AB91F4D6BF6002B1D24 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A0868AB81F4D6BF6002B1D24 /* Assets.xcassets */; };
13 | A0868ABE1F4D6C21002B1D24 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A0868ABB1F4D6C21002B1D24 /* LaunchScreen.storyboard */; };
14 | A0868AC51F4D6F92002B1D24 /* UsersRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AC41F4D6F92002B1D24 /* UsersRouter.swift */; };
15 | A0868ACC1F4D7117002B1D24 /* UsersListViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868ACB1F4D7117002B1D24 /* UsersListViewPresenter.swift */; };
16 | A0868ACE1F4D71C1002B1D24 /* UsersListViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868ACD1F4D71C1002B1D24 /* UsersListViewFactory.swift */; };
17 | A0868AD11F4D7244002B1D24 /* UsersListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868ACF1F4D7244002B1D24 /* UsersListTableViewController.swift */; };
18 | A0868AD21F4D7244002B1D24 /* UsersListTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0868AD01F4D7244002B1D24 /* UsersListTableViewController.xib */; };
19 | A0868AD51F4D7316002B1D24 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AD41F4D7316002B1D24 /* UIViewControllerExtension.swift */; };
20 | A0868AD71F4D733F002B1D24 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AD61F4D733F002B1D24 /* UIView.swift */; };
21 | A0868AD91F4D75DC002B1D24 /* UsersListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AD81F4D75DC002B1D24 /* UsersListViewModel.swift */; };
22 | A0868ADC1F4D7635002B1D24 /* ViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868ADB1F4D7635002B1D24 /* ViewPresenter.swift */; };
23 | A0868ADF1F4D7A56002B1D24 /* UsersListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868ADD1F4D7A56002B1D24 /* UsersListTableViewCell.swift */; };
24 | A0868AE01F4D7A56002B1D24 /* UsersListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0868ADE1F4D7A56002B1D24 /* UsersListTableViewCell.xib */; };
25 | A0868AE21F4D8845002B1D24 /* UsersListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AE11F4D8845002B1D24 /* UsersListInteractor.swift */; };
26 | A0868AE51F4D88F3002B1D24 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AE41F4D88F3002B1D24 /* User.swift */; };
27 | A0868AE81F4D8974002B1D24 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AE71F4D8974002B1D24 /* HTTPClient.swift */; };
28 | A0868AF11F4D9317002B1D24 /* UserDetailsViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AEC1F4D9317002B1D24 /* UserDetailsViewFactory.swift */; };
29 | A0868AF21F4D9317002B1D24 /* UserDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AEE1F4D9317002B1D24 /* UserDetailsViewModel.swift */; };
30 | A0868AF31F4D9317002B1D24 /* UserDetailsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AF01F4D9317002B1D24 /* UserDetailsViewPresenter.swift */; };
31 | A0868AF61F4D939C002B1D24 /* UserDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0868AF41F4D939C002B1D24 /* UserDetailsViewController.swift */; };
32 | A0868AF71F4D939C002B1D24 /* UserDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0868AF51F4D939C002B1D24 /* UserDetailsViewController.xib */; };
33 | A9E42D811F4E1FEB00E0DAF5 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E42D801F4E1FEB00E0DAF5 /* Router.swift */; };
34 | /* End PBXBuildFile section */
35 |
36 | /* Begin PBXFileReference section */
37 | A0854F301F51AAAA005D984C /* UsersParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersParser.swift; sourceTree = ""; };
38 | A0868A9E1F4D6B93002B1D24 /* 4VEngine.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 4VEngine.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 | A0868AB41F4D6BE8002B1D24 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
40 | A0868AB81F4D6BF6002B1D24 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
41 | A0868ABC1F4D6C21002B1D24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
42 | A0868ABD1F4D6C21002B1D24 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
43 | A0868AC41F4D6F92002B1D24 /* UsersRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersRouter.swift; sourceTree = ""; };
44 | A0868ACB1F4D7117002B1D24 /* UsersListViewPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersListViewPresenter.swift; sourceTree = ""; };
45 | A0868ACD1F4D71C1002B1D24 /* UsersListViewFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersListViewFactory.swift; sourceTree = ""; };
46 | A0868ACF1F4D7244002B1D24 /* UsersListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersListTableViewController.swift; sourceTree = ""; };
47 | A0868AD01F4D7244002B1D24 /* UsersListTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UsersListTableViewController.xib; sourceTree = ""; };
48 | A0868AD41F4D7316002B1D24 /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtension.swift; sourceTree = ""; };
49 | A0868AD61F4D733F002B1D24 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; };
50 | A0868AD81F4D75DC002B1D24 /* UsersListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersListViewModel.swift; sourceTree = ""; };
51 | A0868ADB1F4D7635002B1D24 /* ViewPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewPresenter.swift; sourceTree = ""; };
52 | A0868ADD1F4D7A56002B1D24 /* UsersListTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersListTableViewCell.swift; sourceTree = ""; };
53 | A0868ADE1F4D7A56002B1D24 /* UsersListTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UsersListTableViewCell.xib; sourceTree = ""; };
54 | A0868AE11F4D8845002B1D24 /* UsersListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersListInteractor.swift; sourceTree = ""; };
55 | A0868AE41F4D88F3002B1D24 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
56 | A0868AE71F4D8974002B1D24 /* HTTPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; };
57 | A0868AEC1F4D9317002B1D24 /* UserDetailsViewFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDetailsViewFactory.swift; sourceTree = ""; };
58 | A0868AEE1F4D9317002B1D24 /* UserDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDetailsViewModel.swift; sourceTree = ""; };
59 | A0868AF01F4D9317002B1D24 /* UserDetailsViewPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDetailsViewPresenter.swift; sourceTree = ""; };
60 | A0868AF41F4D939C002B1D24 /* UserDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDetailsViewController.swift; sourceTree = ""; };
61 | A0868AF51F4D939C002B1D24 /* UserDetailsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserDetailsViewController.xib; sourceTree = ""; };
62 | A9E42D801F4E1FEB00E0DAF5 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; };
63 | /* End PBXFileReference section */
64 |
65 | /* Begin PBXFrameworksBuildPhase section */
66 | A0868A9B1F4D6B93002B1D24 /* Frameworks */ = {
67 | isa = PBXFrameworksBuildPhase;
68 | buildActionMask = 2147483647;
69 | files = (
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | /* End PBXFrameworksBuildPhase section */
74 |
75 | /* Begin PBXGroup section */
76 | A0868A951F4D6B93002B1D24 = {
77 | isa = PBXGroup;
78 | children = (
79 | A0868AB81F4D6BF6002B1D24 /* Assets.xcassets */,
80 | A0868A9F1F4D6B93002B1D24 /* Products */,
81 | A0868AB31F4D6BE8002B1D24 /* Source */,
82 | A0868ABA1F4D6C21002B1D24 /* Supporting Files */,
83 | );
84 | sourceTree = "";
85 | };
86 | A0868A9F1F4D6B93002B1D24 /* Products */ = {
87 | isa = PBXGroup;
88 | children = (
89 | A0868A9E1F4D6B93002B1D24 /* 4VEngine.app */,
90 | );
91 | name = Products;
92 | sourceTree = "";
93 | };
94 | A0868AB31F4D6BE8002B1D24 /* Source */ = {
95 | isa = PBXGroup;
96 | children = (
97 | A0868AB41F4D6BE8002B1D24 /* AppDelegate.swift */,
98 | A0868ADA1F4D762E002B1D24 /* BaseTypes */,
99 | A0868AD31F4D72CB002B1D24 /* Extensions */,
100 | A0868AE31F4D88E1002B1D24 /* Models */,
101 | A0868AE61F4D891E002B1D24 /* Services */,
102 | A0868AC11F4D6F29002B1D24 /* Stories */,
103 | );
104 | path = Source;
105 | sourceTree = "";
106 | };
107 | A0868ABA1F4D6C21002B1D24 /* Supporting Files */ = {
108 | isa = PBXGroup;
109 | children = (
110 | A0868ABB1F4D6C21002B1D24 /* LaunchScreen.storyboard */,
111 | A0868ABD1F4D6C21002B1D24 /* Info.plist */,
112 | );
113 | path = "Supporting Files";
114 | sourceTree = "";
115 | };
116 | A0868AC11F4D6F29002B1D24 /* Stories */ = {
117 | isa = PBXGroup;
118 | children = (
119 | A0868AC21F4D6F39002B1D24 /* Users */,
120 | );
121 | path = Stories;
122 | sourceTree = "";
123 | };
124 | A0868AC21F4D6F39002B1D24 /* Users */ = {
125 | isa = PBXGroup;
126 | children = (
127 | A0868AE91F4D9317002B1D24 /* UserDetails */,
128 | A0868AC31F4D6F85002B1D24 /* UsersList */,
129 | A0868AC41F4D6F92002B1D24 /* UsersRouter.swift */,
130 | );
131 | path = Users;
132 | sourceTree = "";
133 | };
134 | A0868AC31F4D6F85002B1D24 /* UsersList */ = {
135 | isa = PBXGroup;
136 | children = (
137 | A0868AC61F4D709E002B1D24 /* Interactor */,
138 | A0868AC71F4D709E002B1D24 /* View */,
139 | A0868AC81F4D709E002B1D24 /* ViewFactory */,
140 | A0868AC91F4D709E002B1D24 /* ViewModel */,
141 | A0868ACA1F4D709E002B1D24 /* ViewPresenter */,
142 | );
143 | path = UsersList;
144 | sourceTree = "";
145 | };
146 | A0868AC61F4D709E002B1D24 /* Interactor */ = {
147 | isa = PBXGroup;
148 | children = (
149 | A0868AE11F4D8845002B1D24 /* UsersListInteractor.swift */,
150 | A0854F301F51AAAA005D984C /* UsersParser.swift */,
151 | );
152 | path = Interactor;
153 | sourceTree = "";
154 | };
155 | A0868AC71F4D709E002B1D24 /* View */ = {
156 | isa = PBXGroup;
157 | children = (
158 | A0868ADD1F4D7A56002B1D24 /* UsersListTableViewCell.swift */,
159 | A0868ADE1F4D7A56002B1D24 /* UsersListTableViewCell.xib */,
160 | A0868ACF1F4D7244002B1D24 /* UsersListTableViewController.swift */,
161 | A0868AD01F4D7244002B1D24 /* UsersListTableViewController.xib */,
162 | );
163 | path = View;
164 | sourceTree = "";
165 | };
166 | A0868AC81F4D709E002B1D24 /* ViewFactory */ = {
167 | isa = PBXGroup;
168 | children = (
169 | A0868ACD1F4D71C1002B1D24 /* UsersListViewFactory.swift */,
170 | );
171 | path = ViewFactory;
172 | sourceTree = "";
173 | };
174 | A0868AC91F4D709E002B1D24 /* ViewModel */ = {
175 | isa = PBXGroup;
176 | children = (
177 | A0868AD81F4D75DC002B1D24 /* UsersListViewModel.swift */,
178 | );
179 | path = ViewModel;
180 | sourceTree = "";
181 | };
182 | A0868ACA1F4D709E002B1D24 /* ViewPresenter */ = {
183 | isa = PBXGroup;
184 | children = (
185 | A0868ACB1F4D7117002B1D24 /* UsersListViewPresenter.swift */,
186 | );
187 | path = ViewPresenter;
188 | sourceTree = "";
189 | };
190 | A0868AD31F4D72CB002B1D24 /* Extensions */ = {
191 | isa = PBXGroup;
192 | children = (
193 | A0868AD61F4D733F002B1D24 /* UIView.swift */,
194 | A0868AD41F4D7316002B1D24 /* UIViewControllerExtension.swift */,
195 | );
196 | path = Extensions;
197 | sourceTree = "";
198 | };
199 | A0868ADA1F4D762E002B1D24 /* BaseTypes */ = {
200 | isa = PBXGroup;
201 | children = (
202 | A0868ADB1F4D7635002B1D24 /* ViewPresenter.swift */,
203 | A9E42D801F4E1FEB00E0DAF5 /* Router.swift */,
204 | );
205 | path = BaseTypes;
206 | sourceTree = "";
207 | };
208 | A0868AE31F4D88E1002B1D24 /* Models */ = {
209 | isa = PBXGroup;
210 | children = (
211 | A0868AE41F4D88F3002B1D24 /* User.swift */,
212 | );
213 | path = Models;
214 | sourceTree = "";
215 | };
216 | A0868AE61F4D891E002B1D24 /* Services */ = {
217 | isa = PBXGroup;
218 | children = (
219 | A0868AE71F4D8974002B1D24 /* HTTPClient.swift */,
220 | );
221 | path = Services;
222 | sourceTree = "";
223 | };
224 | A0868AE91F4D9317002B1D24 /* UserDetails */ = {
225 | isa = PBXGroup;
226 | children = (
227 | A0868AEA1F4D9317002B1D24 /* View */,
228 | A0868AEB1F4D9317002B1D24 /* ViewFactory */,
229 | A0868AED1F4D9317002B1D24 /* ViewModel */,
230 | A0868AEF1F4D9317002B1D24 /* ViewPresenter */,
231 | );
232 | path = UserDetails;
233 | sourceTree = "";
234 | };
235 | A0868AEA1F4D9317002B1D24 /* View */ = {
236 | isa = PBXGroup;
237 | children = (
238 | A0868AF41F4D939C002B1D24 /* UserDetailsViewController.swift */,
239 | A0868AF51F4D939C002B1D24 /* UserDetailsViewController.xib */,
240 | );
241 | path = View;
242 | sourceTree = "";
243 | };
244 | A0868AEB1F4D9317002B1D24 /* ViewFactory */ = {
245 | isa = PBXGroup;
246 | children = (
247 | A0868AEC1F4D9317002B1D24 /* UserDetailsViewFactory.swift */,
248 | );
249 | path = ViewFactory;
250 | sourceTree = "";
251 | };
252 | A0868AED1F4D9317002B1D24 /* ViewModel */ = {
253 | isa = PBXGroup;
254 | children = (
255 | A0868AEE1F4D9317002B1D24 /* UserDetailsViewModel.swift */,
256 | );
257 | path = ViewModel;
258 | sourceTree = "";
259 | };
260 | A0868AEF1F4D9317002B1D24 /* ViewPresenter */ = {
261 | isa = PBXGroup;
262 | children = (
263 | A0868AF01F4D9317002B1D24 /* UserDetailsViewPresenter.swift */,
264 | );
265 | path = ViewPresenter;
266 | sourceTree = "";
267 | };
268 | /* End PBXGroup section */
269 |
270 | /* Begin PBXNativeTarget section */
271 | A0868A9D1F4D6B93002B1D24 /* 4VEngine */ = {
272 | isa = PBXNativeTarget;
273 | buildConfigurationList = A0868AB01F4D6B93002B1D24 /* Build configuration list for PBXNativeTarget "4VEngine" */;
274 | buildPhases = (
275 | A0868A9A1F4D6B93002B1D24 /* Sources */,
276 | A0868A9B1F4D6B93002B1D24 /* Frameworks */,
277 | A0868A9C1F4D6B93002B1D24 /* Resources */,
278 | );
279 | buildRules = (
280 | );
281 | dependencies = (
282 | );
283 | name = 4VEngine;
284 | productName = 4VEngine;
285 | productReference = A0868A9E1F4D6B93002B1D24 /* 4VEngine.app */;
286 | productType = "com.apple.product-type.application";
287 | };
288 | /* End PBXNativeTarget section */
289 |
290 | /* Begin PBXProject section */
291 | A0868A961F4D6B93002B1D24 /* Project object */ = {
292 | isa = PBXProject;
293 | attributes = {
294 | LastSwiftUpdateCheck = 0830;
295 | LastUpgradeCheck = 0830;
296 | ORGANIZATIONNAME = MarcoSantaDev;
297 | TargetAttributes = {
298 | A0868A9D1F4D6B93002B1D24 = {
299 | CreatedOnToolsVersion = 8.3.2;
300 | ProvisioningStyle = Automatic;
301 | };
302 | };
303 | };
304 | buildConfigurationList = A0868A991F4D6B93002B1D24 /* Build configuration list for PBXProject "4VEngine" */;
305 | compatibilityVersion = "Xcode 3.2";
306 | developmentRegion = English;
307 | hasScannedForEncodings = 0;
308 | knownRegions = (
309 | en,
310 | Base,
311 | );
312 | mainGroup = A0868A951F4D6B93002B1D24;
313 | productRefGroup = A0868A9F1F4D6B93002B1D24 /* Products */;
314 | projectDirPath = "";
315 | projectRoot = "";
316 | targets = (
317 | A0868A9D1F4D6B93002B1D24 /* 4VEngine */,
318 | );
319 | };
320 | /* End PBXProject section */
321 |
322 | /* Begin PBXResourcesBuildPhase section */
323 | A0868A9C1F4D6B93002B1D24 /* Resources */ = {
324 | isa = PBXResourcesBuildPhase;
325 | buildActionMask = 2147483647;
326 | files = (
327 | A0868AF71F4D939C002B1D24 /* UserDetailsViewController.xib in Resources */,
328 | A0868AE01F4D7A56002B1D24 /* UsersListTableViewCell.xib in Resources */,
329 | A0868AB91F4D6BF6002B1D24 /* Assets.xcassets in Resources */,
330 | A0868AD21F4D7244002B1D24 /* UsersListTableViewController.xib in Resources */,
331 | A0868ABE1F4D6C21002B1D24 /* LaunchScreen.storyboard in Resources */,
332 | );
333 | runOnlyForDeploymentPostprocessing = 0;
334 | };
335 | /* End PBXResourcesBuildPhase section */
336 |
337 | /* Begin PBXSourcesBuildPhase section */
338 | A0868A9A1F4D6B93002B1D24 /* Sources */ = {
339 | isa = PBXSourcesBuildPhase;
340 | buildActionMask = 2147483647;
341 | files = (
342 | A0868ADF1F4D7A56002B1D24 /* UsersListTableViewCell.swift in Sources */,
343 | A0854F311F51AAAA005D984C /* UsersParser.swift in Sources */,
344 | A0868AF31F4D9317002B1D24 /* UserDetailsViewPresenter.swift in Sources */,
345 | A0868AE21F4D8845002B1D24 /* UsersListInteractor.swift in Sources */,
346 | A0868AE81F4D8974002B1D24 /* HTTPClient.swift in Sources */,
347 | A9E42D811F4E1FEB00E0DAF5 /* Router.swift in Sources */,
348 | A0868ADC1F4D7635002B1D24 /* ViewPresenter.swift in Sources */,
349 | A0868AB61F4D6BE8002B1D24 /* AppDelegate.swift in Sources */,
350 | A0868AD11F4D7244002B1D24 /* UsersListTableViewController.swift in Sources */,
351 | A0868AD51F4D7316002B1D24 /* UIViewControllerExtension.swift in Sources */,
352 | A0868AD71F4D733F002B1D24 /* UIView.swift in Sources */,
353 | A0868ACC1F4D7117002B1D24 /* UsersListViewPresenter.swift in Sources */,
354 | A0868AF61F4D939C002B1D24 /* UserDetailsViewController.swift in Sources */,
355 | A0868AE51F4D88F3002B1D24 /* User.swift in Sources */,
356 | A0868AD91F4D75DC002B1D24 /* UsersListViewModel.swift in Sources */,
357 | A0868AF21F4D9317002B1D24 /* UserDetailsViewModel.swift in Sources */,
358 | A0868AF11F4D9317002B1D24 /* UserDetailsViewFactory.swift in Sources */,
359 | A0868ACE1F4D71C1002B1D24 /* UsersListViewFactory.swift in Sources */,
360 | A0868AC51F4D6F92002B1D24 /* UsersRouter.swift in Sources */,
361 | );
362 | runOnlyForDeploymentPostprocessing = 0;
363 | };
364 | /* End PBXSourcesBuildPhase section */
365 |
366 | /* Begin PBXVariantGroup section */
367 | A0868ABB1F4D6C21002B1D24 /* LaunchScreen.storyboard */ = {
368 | isa = PBXVariantGroup;
369 | children = (
370 | A0868ABC1F4D6C21002B1D24 /* Base */,
371 | );
372 | name = LaunchScreen.storyboard;
373 | sourceTree = "";
374 | };
375 | /* End PBXVariantGroup section */
376 |
377 | /* Begin XCBuildConfiguration section */
378 | A0868AAE1F4D6B93002B1D24 /* Debug */ = {
379 | isa = XCBuildConfiguration;
380 | buildSettings = {
381 | ALWAYS_SEARCH_USER_PATHS = NO;
382 | CLANG_ANALYZER_NONNULL = YES;
383 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
384 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
385 | CLANG_CXX_LIBRARY = "libc++";
386 | CLANG_ENABLE_MODULES = YES;
387 | CLANG_ENABLE_OBJC_ARC = YES;
388 | CLANG_WARN_BOOL_CONVERSION = YES;
389 | CLANG_WARN_CONSTANT_CONVERSION = YES;
390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
391 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
392 | CLANG_WARN_EMPTY_BODY = YES;
393 | CLANG_WARN_ENUM_CONVERSION = YES;
394 | CLANG_WARN_INFINITE_RECURSION = YES;
395 | CLANG_WARN_INT_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
398 | CLANG_WARN_UNREACHABLE_CODE = YES;
399 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
400 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
401 | COPY_PHASE_STRIP = NO;
402 | DEBUG_INFORMATION_FORMAT = dwarf;
403 | ENABLE_STRICT_OBJC_MSGSEND = YES;
404 | ENABLE_TESTABILITY = YES;
405 | GCC_C_LANGUAGE_STANDARD = gnu99;
406 | GCC_DYNAMIC_NO_PIC = NO;
407 | GCC_NO_COMMON_BLOCKS = YES;
408 | GCC_OPTIMIZATION_LEVEL = 0;
409 | GCC_PREPROCESSOR_DEFINITIONS = (
410 | "DEBUG=1",
411 | "$(inherited)",
412 | );
413 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
414 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
415 | GCC_WARN_UNDECLARED_SELECTOR = YES;
416 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
417 | GCC_WARN_UNUSED_FUNCTION = YES;
418 | GCC_WARN_UNUSED_VARIABLE = YES;
419 | IPHONEOS_DEPLOYMENT_TARGET = 10.3;
420 | MTL_ENABLE_DEBUG_INFO = YES;
421 | ONLY_ACTIVE_ARCH = YES;
422 | SDKROOT = iphoneos;
423 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
424 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
425 | TARGETED_DEVICE_FAMILY = "1,2";
426 | };
427 | name = Debug;
428 | };
429 | A0868AAF1F4D6B93002B1D24 /* Release */ = {
430 | isa = XCBuildConfiguration;
431 | buildSettings = {
432 | ALWAYS_SEARCH_USER_PATHS = NO;
433 | CLANG_ANALYZER_NONNULL = YES;
434 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
436 | CLANG_CXX_LIBRARY = "libc++";
437 | CLANG_ENABLE_MODULES = YES;
438 | CLANG_ENABLE_OBJC_ARC = YES;
439 | CLANG_WARN_BOOL_CONVERSION = YES;
440 | CLANG_WARN_CONSTANT_CONVERSION = YES;
441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
442 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
443 | CLANG_WARN_EMPTY_BODY = YES;
444 | CLANG_WARN_ENUM_CONVERSION = YES;
445 | CLANG_WARN_INFINITE_RECURSION = YES;
446 | CLANG_WARN_INT_CONVERSION = YES;
447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
448 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
449 | CLANG_WARN_UNREACHABLE_CODE = YES;
450 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
451 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
452 | COPY_PHASE_STRIP = NO;
453 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
454 | ENABLE_NS_ASSERTIONS = NO;
455 | ENABLE_STRICT_OBJC_MSGSEND = YES;
456 | GCC_C_LANGUAGE_STANDARD = gnu99;
457 | GCC_NO_COMMON_BLOCKS = YES;
458 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
459 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
460 | GCC_WARN_UNDECLARED_SELECTOR = YES;
461 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
462 | GCC_WARN_UNUSED_FUNCTION = YES;
463 | GCC_WARN_UNUSED_VARIABLE = YES;
464 | IPHONEOS_DEPLOYMENT_TARGET = 10.3;
465 | MTL_ENABLE_DEBUG_INFO = NO;
466 | SDKROOT = iphoneos;
467 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
468 | TARGETED_DEVICE_FAMILY = "1,2";
469 | VALIDATE_PRODUCT = YES;
470 | };
471 | name = Release;
472 | };
473 | A0868AB11F4D6B93002B1D24 /* Debug */ = {
474 | isa = XCBuildConfiguration;
475 | buildSettings = {
476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
477 | INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
478 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
479 | PRODUCT_BUNDLE_IDENTIFIER = com.marcosantadev.4VEngine;
480 | PRODUCT_NAME = "$(TARGET_NAME)";
481 | SWIFT_VERSION = 3.0;
482 | };
483 | name = Debug;
484 | };
485 | A0868AB21F4D6B93002B1D24 /* Release */ = {
486 | isa = XCBuildConfiguration;
487 | buildSettings = {
488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
489 | INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
490 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
491 | PRODUCT_BUNDLE_IDENTIFIER = com.marcosantadev.4VEngine;
492 | PRODUCT_NAME = "$(TARGET_NAME)";
493 | SWIFT_VERSION = 3.0;
494 | };
495 | name = Release;
496 | };
497 | /* End XCBuildConfiguration section */
498 |
499 | /* Begin XCConfigurationList section */
500 | A0868A991F4D6B93002B1D24 /* Build configuration list for PBXProject "4VEngine" */ = {
501 | isa = XCConfigurationList;
502 | buildConfigurations = (
503 | A0868AAE1F4D6B93002B1D24 /* Debug */,
504 | A0868AAF1F4D6B93002B1D24 /* Release */,
505 | );
506 | defaultConfigurationIsVisible = 0;
507 | defaultConfigurationName = Release;
508 | };
509 | A0868AB01F4D6B93002B1D24 /* Build configuration list for PBXNativeTarget "4VEngine" */ = {
510 | isa = XCConfigurationList;
511 | buildConfigurations = (
512 | A0868AB11F4D6B93002B1D24 /* Debug */,
513 | A0868AB21F4D6B93002B1D24 /* Release */,
514 | );
515 | defaultConfigurationIsVisible = 0;
516 | defaultConfigurationName = Release;
517 | };
518 | /* End XCConfigurationList section */
519 | };
520 | rootObject = A0868A961F4D6B93002B1D24 /* Project object */;
521 | }
522 |
--------------------------------------------------------------------------------