Created
March 29, 2023 19:30
-
-
Save srstanic/f90ffee797259fe051b5588ffb85ca8c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Presenter | |
typealias SomeViewModel = String // this is just a placeholder for your view model type | |
protocol SomeView { | |
func showSomething(viewModel: SomeViewModel) | |
} | |
protocol SomeViewOutput { | |
func onUserAction() | |
func onSomeOtherUserAction() | |
} | |
typealias ServiceResult = String // this is just a placeholder for your service result type | |
protocol SomeUsefulService { | |
func doSomeUsefulWork(completion: (Result<ServiceResult, Error>) -> Void) | |
} | |
final class SomePresenter: SomeViewOutput { | |
private let someUsefulService: SomeUsefulService | |
private let view: SomeView | |
private let output: SomeSceneOutput | |
init( | |
someUsefulService: SomeUsefulService, | |
view: SomeView, | |
output: SomeSceneOutput | |
) { | |
self.someUsefulService = someUsefulService | |
self.view = view | |
self.output = output | |
} | |
// MARK: SomePresenterInput | |
func onUserAction() { | |
someUsefulService.doSomeUsefulWork { [view] result in | |
view.showSomething(viewModel: .from(result)) | |
} | |
} | |
func onSomeOtherUserAction() { | |
output.onSomeActionHandledOutsideTheScopeOfSomeScene() | |
} | |
} | |
extension SomeViewModel { | |
static func from(_ serviceResult: Result<ServiceResult, Error>) -> Self { | |
// map ServiceResult to SomethingViewModel | |
return "Loaded" | |
} | |
} | |
protocol SomeSceneOutput { | |
func onSomeActionHandledOutsideTheScopeOfSomeScene() | |
} | |
import UIKit | |
// ViewController | |
final class SomeViewController: UIViewController, SomeView { | |
var viewOutput: SomeViewOutput | |
init(viewOutput: SomeViewOutput) { | |
self.viewOutput = viewOutput | |
super.init(nibName: nil, bundle: nil) | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// setup views and UIControls to call onUserInteraction() and onSomeOtherUserInteraction() | |
} | |
@objc private func onUserInteraction() { | |
viewOutput.onUserAction() | |
} | |
@objc private func onSomeOtherUserInteraction() { | |
viewOutput.onSomeOtherUserAction() | |
} | |
// MARK: SomeView | |
func showSomething(viewModel: SomeViewModel) { | |
// configure views to display view model data | |
} | |
} | |
// Composer | |
protocol SomeSceneComposing { | |
func composeSomeScene(output: SomeSceneOutput) -> UIViewController | |
} | |
final class SomeSceneComposer: SomeSceneComposing { | |
private let someUsefulService: SomeUsefulService | |
init(someUsefulService: SomeUsefulService) { | |
self.someUsefulService = someUsefulService | |
} | |
func composeSomeScene(output: SomeSceneOutput) -> UIViewController { | |
let viewProxy = WeakReferenceProxy<SomeViewController>() | |
let somePresenter = SomePresenter( | |
someUsefulService: someUsefulService, | |
view: viewProxy, | |
output: output | |
) | |
let someViewController: SomeViewController = SomeViewController(viewOutput: somePresenter) | |
viewProxy.object = someViewController | |
return someViewController | |
} | |
} | |
extension WeakReferenceProxy: SomeView where ReferenceType: SomeView { | |
func showSomething(viewModel: SomeViewModel) { | |
object?.showSomething(viewModel: viewModel) | |
} | |
} | |
// Coordinator | |
protocol SomeCoordinating { | |
func transitionToSomeScene(in navigationController: UINavigationController) | |
} | |
protocol SomeOtherCoordinating { | |
func transitionToSomeOtherScene(over presentingViewController: UIViewController) | |
} | |
final class SomeCoordinator: SomeCoordinating { | |
weak var someScene: UIViewController? | |
private let createSomeSceneComposer: () -> SomeSceneComposing | |
private let createSomeOtherCoordinator: () -> SomeOtherCoordinating | |
init( | |
createSomeComposer: @escaping () -> SomeSceneComposing, | |
createSomeOtherCoordinator: @escaping () -> SomeOtherCoordinating | |
) { | |
self.createSomeSceneComposer = createSomeComposer | |
self.createSomeOtherCoordinator = createSomeOtherCoordinator | |
} | |
func transitionToSomeScene(in navigationController: UINavigationController) { | |
let composer = createSomeSceneComposer() | |
let someScene = composer.composeSomeScene(output: self) | |
navigationController.pushViewController(someScene, animated: true) | |
self.someScene = someScene | |
} | |
private func transitionToSomeOtherScene() { | |
guard let presentingViewController = self.someScene else { return } | |
let someOtherCoordinator = createSomeOtherCoordinator() | |
someOtherCoordinator.transitionToSomeOtherScene(over: presentingViewController) | |
} | |
} | |
extension SomeCoordinator: SomeSceneOutput { | |
func onSomeActionHandledOutsideTheScopeOfSomeScene() { | |
transitionToSomeOtherScene() | |
} | |
} | |
// WeakReferenceProxy | |
final class WeakReferenceProxy<ReferenceType: AnyObject> { | |
weak var object: ReferenceType? | |
init(_ object: ReferenceType? = nil) { | |
self.object = object | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment