├── .gitignore
├── LICENSE
├── README.md
├── mvc
├── MVC.playground
│ ├── Contents.swift
│ ├── contents.xcplayground
│ └── playground.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── README.md
├── mvp
├── MVP.playground
│ ├── Contents.swift
│ ├── contents.xcplayground
│ ├── playground.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── timeline.xctimeline
└── README.md
├── mvvm
├── MVVM.playground
│ ├── Contents.swift
│ ├── contents.xcplayground
│ ├── playground.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── timeline.xctimeline
└── README.md
└── viper
├── README.md
└── VIPER.playground
├── Contents.swift
├── contents.xcplayground
├── playground.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── timeline.xctimeline
/.gitignore:
--------------------------------------------------------------------------------
1 | xcuserdata/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Wasin Thonkaew
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ios-design-patterns
2 |
3 | Demo projects (in Playground) compare design patterns in iOS included MVC, MVP, MVVM, and VIPER.
4 |
5 | # Credits
6 |
7 | * [iOS Architecture Patterns by Bohdan Orlov](https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52#.dq4fwa189)
8 | I take the chance to follow along with article, learn along in the process and mainly base on his code then I add UI part (in pure code) + modify code slightly + make it runnable on Xcode 8.2 (updated to support Xcode 9.3). Thus Playground projects are ready to test right away. Again big shout out to author.
9 |
10 | # Changelog
11 |
12 | * All playground projects are updated to be runnable on Xcode 9.3 Playground.
13 |
14 | # LICENSE
15 |
16 | This project is licensed under MIT.
17 | See [LICENSE](https://github.com/haxpor/ios-design-patterns/blob/master/LICENSE)
18 |
--------------------------------------------------------------------------------
/mvc/MVC.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * MVC iOS Design Pattern
3 | *
4 | */
5 |
6 | import UIKit
7 | import PlaygroundSupport
8 |
9 | struct Person { // Model
10 | let firstName: String
11 | let lastName: String
12 | }
13 |
14 | class GreetingViewController : UIViewController { // View + Controller
15 | var person: Person!
16 | var showGreetingButton: UIButton!
17 | var greetingLabel: UILabel!
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
23 |
24 | self.setupUIElements()
25 | self.layout()
26 | }
27 |
28 | func setupUIElements() {
29 | self.title = "Test"
30 |
31 | self._setupButton()
32 | self._setupLabel()
33 | }
34 |
35 | private func _setupButton() {
36 | self.showGreetingButton = UIButton()
37 | self.showGreetingButton.setTitle("Click me", for: .normal)
38 | self.showGreetingButton.setTitle("You badass", for: .highlighted)
39 | self.showGreetingButton.setTitleColor(UIColor.white, for: .normal)
40 | self.showGreetingButton.setTitleColor(UIColor.red, for: .highlighted)
41 | self.showGreetingButton.translatesAutoresizingMaskIntoConstraints = false
42 | self.showGreetingButton.addTarget(self, action: #selector(didTapButton(sender:)), for: .touchUpInside)
43 | self.view.addSubview(self.showGreetingButton)
44 | }
45 |
46 | private func _setupLabel() {
47 | self.greetingLabel = UILabel()
48 | self.greetingLabel.textColor = UIColor.white
49 | self.greetingLabel.textAlignment = .center
50 | self.greetingLabel.translatesAutoresizingMaskIntoConstraints = false
51 | self.view.addSubview(self.greetingLabel)
52 | }
53 |
54 | func layout() {
55 | self._layoutButton()
56 | self._layoutLabel()
57 |
58 | self.view.layoutIfNeeded()
59 | }
60 |
61 | private func _layoutButton() {
62 | // layout button at the center of the screen
63 | let cs1 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
64 | let cs2 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.0, constant: 1.0)
65 |
66 | self.view.addConstraints([cs1, cs2])
67 | }
68 |
69 | private func _layoutLabel() {
70 | // layout label at the center, bottom of the screen
71 | let cs1 = NSLayoutConstraint(item: self.greetingLabel, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
72 | let cs2 = NSLayoutConstraint(item: self.greetingLabel, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: -10)
73 | let cs3 = NSLayoutConstraint(item: self.greetingLabel, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 0.70, constant: 0)
74 |
75 | self.view.addConstraints([cs1, cs2, cs3])
76 | }
77 |
78 | @objc func didTapButton(sender: UIButton) {
79 | self.greetingLabel.text = "Hello " + self.person.firstName + " " + self.person.lastName
80 | }
81 | }
82 |
83 | let model = Person(firstName: "Wasin", lastName: "Thonkaew")
84 | let vc = GreetingViewController()
85 | vc.person = model
86 |
87 | PlaygroundPage.current.liveView = vc.view
88 |
--------------------------------------------------------------------------------
/mvc/MVC.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/mvc/MVC.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mvc/MVC.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mvc/README.md:
--------------------------------------------------------------------------------
1 | # MVC
2 |
3 | `Model` + (`View + Controller`)
4 |
5 | in Apple design
6 |
7 | # Code
8 |
9 | * UI elements and its auto-layout mechanism are done via code-only through constraints (`NSLayoutConstraint(item:, attribute:, relatedBy:, toItem:, attribute:, multiplier:, constant:)`).
10 | * Show UIView on screen via `PlaygroundPage.current.liveView = yourVC.view`
11 | * You can also set up constraint via [Visual Format Language](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html) but just for personal taste, I found that using `NSLayoutConstraint(item:, attribute:, relatedBy:, toItem:, attribute:, multiplier:, constant:)` is more robust and offer complete functionality.
--------------------------------------------------------------------------------
/mvp/MVP.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * MVP iOS Design Pattern
3 | *
4 | */
5 |
6 | import UIKit
7 | import PlaygroundSupport
8 |
9 | struct Person { // Model
10 | let firstName: String
11 | let lastName: String
12 | }
13 |
14 | protocol GreetingView: class {
15 | func setGreeting(greeting: String)
16 | }
17 |
18 | protocol GreetingViewPresenter {
19 | init(view: GreetingView, person: Person)
20 | func showGreeting()
21 | }
22 |
23 | class GreetingPresenter : GreetingViewPresenter {
24 | weak var view: GreetingView?
25 | let person: Person
26 |
27 | required init(view: GreetingView, person: Person) {
28 | self.view = view
29 | self.person = person
30 | }
31 |
32 | func showGreeting() {
33 | let greeting = "Hello " + self.person.firstName + " " + self.person.lastName
34 | self.view?.setGreeting(greeting: greeting)
35 | }
36 | }
37 |
38 | class GreetingViewController : UIViewController, GreetingView {
39 | var presenter: GreetingViewPresenter!
40 | var showGreetingButton: UIButton!
41 | var greetingLabel: UILabel!
42 |
43 | override func viewDidLoad() {
44 | super.viewDidLoad()
45 |
46 | self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
47 |
48 | self.setupUIElements()
49 | self.layout()
50 | }
51 |
52 | func setGreeting(greeting: String) {
53 | self.greetingLabel.text = greeting
54 | }
55 |
56 | func setupUIElements() {
57 | self.title = "Test"
58 |
59 | self._setupButton()
60 | self._setupLabel()
61 | }
62 |
63 | private func _setupButton() {
64 | self.showGreetingButton = UIButton()
65 | self.showGreetingButton.setTitle("Click me", for: .normal)
66 | self.showGreetingButton.setTitle("You badass", for: .highlighted)
67 | self.showGreetingButton.setTitleColor(UIColor.white, for: .normal)
68 | self.showGreetingButton.setTitleColor(UIColor.red, for: .highlighted)
69 | self.showGreetingButton.translatesAutoresizingMaskIntoConstraints = false
70 | self.showGreetingButton.addTarget(self, action: #selector(didTapButton(sender:)), for: .touchUpInside)
71 | self.view.addSubview(self.showGreetingButton)
72 | }
73 |
74 | private func _setupLabel() {
75 | self.greetingLabel = UILabel()
76 | self.greetingLabel.textColor = UIColor.white
77 | self.greetingLabel.textAlignment = .center
78 | self.greetingLabel.translatesAutoresizingMaskIntoConstraints = false
79 |
80 | self.view.addSubview(self.greetingLabel)
81 | }
82 |
83 | func layout() {
84 | self._layoutButton()
85 | self._layoutLabel()
86 |
87 | self.view.layoutIfNeeded()
88 | }
89 |
90 | private func _layoutButton() {
91 | // layout button at the center of the screen
92 | let cs1 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
93 | let cs2 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.0, constant: 1.0)
94 |
95 | self.view.addConstraints([cs1, cs2])
96 | }
97 |
98 | private func _layoutLabel() {
99 | // layout label at the center, bottom of the screen
100 | let cs1 = NSLayoutConstraint(item: self.greetingLabel, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
101 | let cs2 = NSLayoutConstraint(item: self.greetingLabel, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: -10)
102 | let cs3 = NSLayoutConstraint(item: self.greetingLabel, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 0.70, constant: 0)
103 |
104 | self.view.addConstraints([cs1, cs2, cs3])
105 | }
106 |
107 | @objc func didTapButton(sender: UIButton) {
108 | self.presenter.showGreeting()
109 | }
110 | }
111 |
112 | // Assembling of MVP
113 | // Note: Very important that these following lines will be within View when actually creating normal XCode project and follow design here.
114 | let model = Person(firstName: "Wasin", lastName: "Thonkaew")
115 | let view = GreetingViewController()
116 | let presenter = GreetingPresenter(view: view, person: model)
117 | view.presenter = presenter
118 |
119 | PlaygroundPage.current.liveView = view.view
120 |
--------------------------------------------------------------------------------
/mvp/MVP.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/mvp/MVP.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mvp/MVP.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mvp/MVP.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/mvp/README.md:
--------------------------------------------------------------------------------
1 | # MVP
2 |
3 | `Model` + `UIViewController` + `Presenter`
4 |
5 | or
6 |
7 | `Model` + `Passive View` + `Presenter`
8 |
9 | Usually regard
10 | * `UIViewController` -> `UIView`
11 | * `Presenter`` -> `UIViewController` or simply Controller in general term
12 |
13 | `Passive View` as compared to `UIViewController` in MVC in which the latter is more active in making activity compared to former which will be called by `Presenter` to update its state.
14 |
15 | # Code
16 |
17 | * `Model` structure is defined isolatedly in the same way as did in MVC
18 | * There are 2 new protocols defined: one for `Passive View`, and another one for `Presenter`.
19 | * `Passive View` protocol provides functionality (aim at `Presenter`) to update its state
20 | * `Presenter` protocol provides functionality to initialize itself using both `Passive View` and model as parameters, and others to control or update its in-controlled `Passive View`
21 | * The possible reason that `Presenter` needs to be embedded inside `Passive View` is because `Passive View` is actually `UIViewController` which normally is the default entry of to show on screen or operate in iOS sense. It's embedded there to allow code to make use of it.
22 | * `Passive View` won't update its state by itself, but it provides how it is going to show UI element on screen. Instead it's up to `Presenter` to update the state. You can see in `GreetingViewController` class that it handles all UI layouting including constraints, and how to show on screen, but doesn't care about the states (values) of individual UI element which is `UILabel` in this case.
--------------------------------------------------------------------------------
/mvvm/MVVM.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * MVVM iOS Design Pattern
3 | *
4 | */
5 |
6 | import UIKit
7 | import PlaygroundSupport
8 |
9 | struct Person { // Model
10 | let firstName: String
11 | let lastName: String
12 | }
13 |
14 | protocol GreetingViewModelProtocol: class {
15 | var greeting: String? { get }
16 | var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set }
17 | init(person: Person)
18 | func showGreeting()
19 | }
20 |
21 | class GreetingViewModel : GreetingViewModelProtocol {
22 | let person: Person
23 |
24 | var greeting: String? {
25 | didSet {
26 | self.greetingDidChange?(self)
27 | }
28 | }
29 |
30 | var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
31 |
32 | required init(person: Person) {
33 | self.person = person
34 | }
35 |
36 | func showGreeting() {
37 | self.greeting = "Hello " + self.person.firstName + " " + self.person.lastName
38 | }
39 | }
40 |
41 | class GreetingViewController : UIViewController {
42 | var viewModel: GreetingViewModelProtocol? {
43 | didSet {
44 | self.viewModel?.greetingDidChange = { [unowned self] viewModel in
45 | self.greetingLabel.text = viewModel.greeting
46 | }
47 | }
48 | }
49 | var showGreetingButton: UIButton!
50 | var greetingLabel: UILabel!
51 |
52 | override func viewDidLoad() {
53 | super.viewDidLoad()
54 |
55 | self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
56 |
57 | self.setupUIElements()
58 | self.layout()
59 | }
60 |
61 | func setupUIElements() {
62 | self.title = "Test"
63 |
64 | self._setupButton()
65 | self._setupLabel()
66 | }
67 |
68 | private func _setupButton() {
69 | self.showGreetingButton = UIButton()
70 | self.showGreetingButton.setTitle("Click me", for: .normal)
71 | self.showGreetingButton.setTitle("You badass", for: .highlighted)
72 | self.showGreetingButton.setTitleColor(UIColor.white, for: .normal)
73 | self.showGreetingButton.setTitleColor(UIColor.red, for: .highlighted)
74 | self.showGreetingButton.translatesAutoresizingMaskIntoConstraints = false
75 | self.showGreetingButton.addTarget(self, action: #selector(didTapButton(sender:)), for: .touchUpInside)
76 | self.view.addSubview(self.showGreetingButton)
77 | }
78 |
79 | private func _setupLabel() {
80 | self.greetingLabel = UILabel()
81 | self.greetingLabel.textColor = UIColor.white
82 | self.greetingLabel.textAlignment = .center
83 | self.greetingLabel.translatesAutoresizingMaskIntoConstraints = false
84 |
85 | self.view.addSubview(self.greetingLabel)
86 | }
87 |
88 | func layout() {
89 | self._layoutButton()
90 | self._layoutLabel()
91 |
92 | self.view.layoutIfNeeded()
93 | }
94 |
95 | private func _layoutButton() {
96 | // layout button at the center of the screen
97 | let cs1 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
98 | let cs2 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.0, constant: 1.0)
99 |
100 | self.view.addConstraints([cs1, cs2])
101 | }
102 |
103 | private func _layoutLabel() {
104 | // layout label at the center, bottom of the screen
105 | let cs1 = NSLayoutConstraint(item: self.greetingLabel, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
106 | let cs2 = NSLayoutConstraint(item: self.greetingLabel, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: -10)
107 | let cs3 = NSLayoutConstraint(item: self.greetingLabel, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 0.70, constant: 0)
108 |
109 | self.view.addConstraints([cs1, cs2, cs3])
110 | }
111 |
112 | @objc func didTapButton(sender: UIButton) {
113 | guard let vm = self.viewModel else { return }
114 |
115 | vm.showGreeting()
116 | }
117 | }
118 |
119 | // Assembling of MVVM
120 | let model = Person(firstName: "Wasin", lastName: "Thonkaew")
121 | let view = GreetingViewController()
122 | let viewModel = GreetingViewModel(person: model)
123 | view.viewModel = viewModel
124 |
125 | PlaygroundPage.current.liveView = view.view
126 |
--------------------------------------------------------------------------------
/mvvm/MVVM.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/mvvm/MVVM.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mvvm/MVVM.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mvvm/MVVM.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/mvvm/README.md:
--------------------------------------------------------------------------------
1 | # MVVM
2 |
3 | `Model` + `View Model` + `View`
4 |
5 | Usually regard
6 | * `View Model` -> the handler or controller of model
7 | * `View` -> ``UIViewController`` or simply Controller in general term
8 |
9 | # Code
10 |
11 | * It's very similar to MVP but with binding setup in code. Like a supervisoning version of MVP.
12 | * `View` has more activities to do compared to MVP. Instead of `View` to provide functionality for `View Model` to update its state. `View` will do by itself via binding which is setup in code.
13 | * Binding can be regarded as reactive programming approach. Users can utilize reactive library out there i.e. [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa), or [RxSwift](https://github.com/ReactiveX/RxSwift). Or just go via callback approach.
14 | * `View Model` is added as part of `View` can be viewed for a reason as because `View Model` is supervisoning of `View`, but it's not so strong reason.
15 | * Anyway from previous point, separating `View Model` and `View` from each other is to have better testability. `View Model` doesn't have to know about `View`. Thus now we have separated component which can be further tested separately.
16 | * I think MVP already done a great job at distribution, but MVVM does it better to remove completely a requirement for `View Model` to have a knowledge about its `View`.
17 |
--------------------------------------------------------------------------------
/viper/README.md:
--------------------------------------------------------------------------------
1 | # VIPER
2 |
3 | `Entity` + `Interactor` + `Presenter` + `View` + `Router`
4 |
5 | # Code
6 |
7 | * The most of distribution for responsibility but the most of high maintenance cost
8 | * `Model` now is `Entity` but with `Interactor` to act as its data provider
9 | * `Presenter` hooks up with both `View` and `Interactor`.
10 | * `Presenter` hooks up with `Interactor` to get notified for its `Entity`'s states update
11 | * `Presenter` hooks up with `View` (owned by) to allow functionality for `View` to get its representable data to show
12 | * `Router` is responsible for switching the screen (UIViewController)
--------------------------------------------------------------------------------
/viper/VIPER.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * VIPER iOS Design Pattern
3 | *
4 | */
5 |
6 | import UIKit
7 | import PlaygroundSupport
8 |
9 | struct Person { // Entity
10 | let firstName: String
11 | let lastName: String
12 | }
13 |
14 | struct GreetingData { // another layer of Data
15 | let greeting: String
16 | let subject: String
17 | }
18 |
19 | protocol GreetingProvider {
20 | func provideGreetingData()
21 | }
22 |
23 | protocol GreetingOutput: class {
24 | func receiveGreetingData(greetingData: GreetingData)
25 | }
26 |
27 | class GreetingInteractor: GreetingProvider {
28 | weak var output: GreetingOutput!
29 |
30 | func provideGreetingData() {
31 | let person = Person(firstName: "Wasin", lastName: "Thonkaew")
32 | let subject = person.firstName + " " + person.lastName
33 | let greeting = GreetingData(greeting: "Hello", subject: subject)
34 | self.output.receiveGreetingData(greetingData: greeting)
35 | }
36 | }
37 |
38 | protocol GreetingViewEventHandler {
39 | func didTapShowGreetingButton()
40 | }
41 |
42 | protocol GreetingView: class {
43 | func setGreeting(greeting: String)
44 | }
45 |
46 | class GreetingPresenter: GreetingOutput, GreetingViewEventHandler {
47 | weak var view: GreetingView!
48 | var greetingProvider: GreetingProvider!
49 |
50 | func didTapShowGreetingButton() {
51 | self.greetingProvider.provideGreetingData()
52 | }
53 |
54 | func receiveGreetingData(greetingData: GreetingData) {
55 | let greeting = greetingData.greeting + " " + greetingData.subject
56 | self.view.setGreeting(greeting: greeting)
57 | }
58 | }
59 |
60 | class GreetingViewController : UIViewController, GreetingView {
61 | var eventHandler: GreetingViewEventHandler!
62 | var showGreetingButton: UIButton!
63 | var greetingLabel: UILabel!
64 |
65 | override func viewDidLoad() {
66 | super.viewDidLoad()
67 |
68 | self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
69 |
70 | self.setupUIElements()
71 | self.layout()
72 | }
73 |
74 | func setGreeting(greeting: String) {
75 | self.greetingLabel.text = greeting
76 | }
77 |
78 | func setupUIElements() {
79 | self.title = "Test"
80 |
81 | self._setupButton()
82 | self._setupLabel()
83 | }
84 |
85 | private func _setupButton() {
86 | self.showGreetingButton = UIButton()
87 | self.showGreetingButton.setTitle("Click me", for: .normal)
88 | self.showGreetingButton.setTitle("You badass", for: .highlighted)
89 | self.showGreetingButton.setTitleColor(UIColor.white, for: .normal)
90 | self.showGreetingButton.setTitleColor(UIColor.red, for: .highlighted)
91 | self.showGreetingButton.translatesAutoresizingMaskIntoConstraints = false
92 | self.showGreetingButton.addTarget(self, action: #selector(didTapButton(sender:)), for: .touchUpInside)
93 | self.view.addSubview(self.showGreetingButton)
94 | }
95 |
96 | private func _setupLabel() {
97 | self.greetingLabel = UILabel()
98 | self.greetingLabel.textColor = UIColor.white
99 | self.greetingLabel.textAlignment = .center
100 | self.greetingLabel.translatesAutoresizingMaskIntoConstraints = false
101 |
102 | self.view.addSubview(self.greetingLabel)
103 | }
104 |
105 | func layout() {
106 | self._layoutButton()
107 | self._layoutLabel()
108 |
109 | self.view.layoutIfNeeded()
110 | }
111 |
112 | private func _layoutButton() {
113 | // layout button at the center of the screen
114 | let cs1 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
115 | let cs2 = NSLayoutConstraint(item: self.showGreetingButton, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.0, constant: 1.0)
116 |
117 | self.view.addConstraints([cs1, cs2])
118 | }
119 |
120 | private func _layoutLabel() {
121 | // layout label at the center, bottom of the screen
122 | let cs1 = NSLayoutConstraint(item: self.greetingLabel, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 1.0)
123 | let cs2 = NSLayoutConstraint(item: self.greetingLabel, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: -10)
124 | let cs3 = NSLayoutConstraint(item: self.greetingLabel, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 0.70, constant: 0)
125 |
126 | self.view.addConstraints([cs1, cs2, cs3])
127 | }
128 |
129 | @objc func didTapButton(sender: UIButton) {
130 | self.eventHandler.didTapShowGreetingButton()
131 | }
132 | }
133 |
134 | // Assembling of VIPER without Router]
135 | let view = GreetingViewController()
136 | let presenter = GreetingPresenter()
137 | let interactor = GreetingInteractor()
138 | view.eventHandler = presenter
139 | presenter.view = view
140 | presenter.greetingProvider = interactor
141 | interactor.output = presenter
142 |
143 | PlaygroundPage.current.liveView = view.view
144 |
--------------------------------------------------------------------------------
/viper/VIPER.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/viper/VIPER.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/viper/VIPER.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/viper/VIPER.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------