Skip to content

Commit 713a80f

Browse files
authored
Merge pull request #289 from mattrubin/generic-view-controllers
Generic helper method for modal view controllers
2 parents fc6c538 + 45b56fa commit 713a80f

File tree

1 file changed

+62
-58
lines changed

1 file changed

+62
-58
lines changed

Authenticator/Source/RootViewController.swift

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@ class RootViewController: OpaqueNavigationController {
7171
fatalError("init(coder:) has not been implemented")
7272
}
7373

74-
fileprivate func presentViewController(_ viewController: UIViewController) {
75-
presentViewControllers([viewController])
76-
}
77-
7874
fileprivate func presentViewControllers(_ viewControllersToPresent: [UIViewController]) {
7975
// If there is currently no modal, create one.
8076
guard let navController = modalNavController else {
@@ -101,37 +97,35 @@ class RootViewController: OpaqueNavigationController {
10197
}
10298
}
10399

104-
protocol ModelBasedViewController {
100+
protocol ModelBased {
105101
associatedtype ViewModel
106102
associatedtype Action
107103

108104
init(viewModel: ViewModel, dispatchAction: @escaping (Action) -> Void)
109105
func update(with viewModel: ViewModel)
110106
}
111107

112-
extension TokenScannerViewController: ModelBasedViewController {}
113-
extension TokenFormViewController: ModelBasedViewController {}
114-
extension InfoListViewController: ModelBasedViewController {}
115-
extension InfoViewController: ModelBasedViewController {}
116-
117-
extension RootViewController {
118-
private func reify<ViewController: ModelBasedViewController>(viewModel: ViewController.ViewModel, dispatchAction: @escaping (ViewController.Action) -> Void) -> ViewController {
119-
return reify(modalNavController?.topViewController, viewModel: viewModel, dispatchAction: dispatchAction)
120-
}
121-
122-
private func reify<ViewController: ModelBasedViewController>(_ existingViewController: UIViewController?, viewModel: ViewController.ViewModel, dispatchAction: @escaping (ViewController.Action) -> Void) -> ViewController {
123-
if let viewController = existingViewController as? ViewController {
124-
viewController.update(with: viewModel)
125-
return viewController
126-
} else {
127-
let viewController = ViewController(
128-
viewModel: viewModel,
129-
dispatchAction: dispatchAction
130-
)
131-
return viewController
132-
}
108+
typealias ModelBasedViewController = UIViewController & ModelBased
109+
110+
extension TokenScannerViewController: ModelBased {}
111+
extension TokenFormViewController: ModelBased {}
112+
extension InfoListViewController: ModelBased {}
113+
extension InfoViewController: ModelBased {}
114+
115+
private func reify<ViewController: ModelBasedViewController>(_ existingViewController: UIViewController?, viewModel: ViewController.ViewModel, dispatchAction: @escaping (ViewController.Action) -> Void) -> ViewController {
116+
if let viewController = existingViewController as? ViewController {
117+
viewController.update(with: viewModel)
118+
return viewController
119+
} else {
120+
let viewController = ViewController(
121+
viewModel: viewModel,
122+
dispatchAction: dispatchAction
123+
)
124+
return viewController
133125
}
126+
}
134127

128+
extension RootViewController {
135129
func update(with viewModel: Root.ViewModel) {
136130
tokenListViewController.update(with: viewModel.tokenList)
137131

@@ -140,52 +134,62 @@ extension RootViewController {
140134
dismissViewController()
141135

142136
case .scanner(let scannerViewModel):
143-
let scannerViewController: TokenScannerViewController = reify(
144-
viewModel: scannerViewModel,
145-
dispatchAction: compose(Root.Action.tokenScannerAction, dispatchAction)
146-
)
147-
presentViewController(scannerViewController)
137+
presentViewModel(scannerViewModel,
138+
using: TokenScannerViewController.self,
139+
actionTransform: Root.Action.tokenScannerAction)
148140

149141
case .entryForm(let formViewModel):
150-
let formController: TokenFormViewController = reify(
151-
viewModel: formViewModel,
152-
dispatchAction: compose(Root.Action.tokenEntryFormAction, dispatchAction)
153-
)
154-
presentViewController(formController)
142+
presentViewModel(formViewModel,
143+
using: TokenFormViewController.self,
144+
actionTransform: Root.Action.tokenEntryFormAction)
155145

156146
case .editForm(let formViewModel):
157-
let editController: TokenFormViewController = reify(
158-
viewModel: formViewModel,
159-
dispatchAction: compose(Root.Action.tokenEditFormAction, dispatchAction)
160-
)
161-
presentViewController(editController)
147+
presentViewModel(formViewModel,
148+
using: TokenFormViewController.self,
149+
actionTransform: Root.Action.tokenEditFormAction)
162150

163151
case let .info(infoListViewModel, infoViewModel):
164-
updateWithInfoViewModels(infoListViewModel, infoViewModel)
152+
if let infoViewModel = infoViewModel {
153+
presentViewModels(infoListViewModel,
154+
using: InfoListViewController.self,
155+
actionTransform: Root.Action.infoListEffect,
156+
and: infoViewModel,
157+
using: InfoViewController.self,
158+
actionTransform: Root.Action.infoEffect)
159+
160+
} else {
161+
presentViewModel(infoListViewModel,
162+
using: InfoListViewController.self,
163+
actionTransform: Root.Action.infoListEffect)
164+
}
165165
}
166166
currentViewModel = viewModel
167167
}
168168

169-
private func updateWithInfoViewModels(_ infoListViewModel: InfoList.ViewModel, _ infoViewModel: Info.ViewModel?) {
170-
guard let infoViewModel = infoViewModel else {
171-
let infoListViewController: InfoListViewController = reify(
172-
viewModel: infoListViewModel,
173-
dispatchAction: compose(Root.Action.infoListEffect, dispatchAction)
174-
)
175-
presentViewController(infoListViewController)
176-
return
177-
}
169+
private func presentViewModel<ViewController: ModelBasedViewController>(_ viewModel: ViewController.ViewModel, using _: ViewController.Type, actionTransform: @escaping ((ViewController.Action) -> Root.Action)) {
170+
let viewController: ViewController = reify(
171+
modalNavController?.topViewController,
172+
viewModel: viewModel,
173+
dispatchAction: compose(actionTransform, dispatchAction)
174+
)
175+
presentViewControllers([viewController])
176+
}
178177

179-
let infoListViewController: InfoListViewController = reify(
178+
// swiftlint:disable:next function_parameter_count
179+
private func presentViewModels<A: ModelBasedViewController, B: ModelBasedViewController>(
180+
_ viewModelA: A.ViewModel, using _: A.Type, actionTransform actionTransformA: @escaping ((A.Action) -> Root.Action),
181+
and viewModelB: B.ViewModel, using _: B.Type, actionTransform actionTransformB: @escaping ((B.Action) -> Root.Action)) {
182+
let viewControllerA: A = reify(
180183
modalNavController?.viewControllers.first,
181-
viewModel: infoListViewModel,
182-
dispatchAction: compose(Root.Action.infoListEffect, dispatchAction)
184+
viewModel: viewModelA,
185+
dispatchAction: compose(actionTransformA, dispatchAction)
183186
)
184-
let infoViewController: InfoViewController = reify(
185-
viewModel: infoViewModel,
186-
dispatchAction: compose(Root.Action.infoEffect, dispatchAction)
187+
let viewControllerB: B = reify(
188+
modalNavController?.topViewController,
189+
viewModel: viewModelB,
190+
dispatchAction: compose(actionTransformB, dispatchAction)
187191
)
188-
presentViewControllers([infoListViewController, infoViewController])
192+
presentViewControllers([viewControllerA, viewControllerB])
189193
}
190194
}
191195

0 commit comments

Comments
 (0)