Skip to content

Instantly share code, notes, and snippets.

@srstanic
Created March 29, 2023 19:30
Show Gist options
  • Save srstanic/f90ffee797259fe051b5588ffb85ca8c to your computer and use it in GitHub Desktop.
Save srstanic/f90ffee797259fe051b5588ffb85ca8c to your computer and use it in GitHub Desktop.
// 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