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