The way we manage screen stacks, transitions, and structure is with the coordinator pattern using the XCoordinator library. We use it alongside view models, which we bind to view controllers.
prepareTransition
we return a new screen (not always) and how:
present
& dismiss
.push
& pop
inside NavigationCoordinator
s.embed
& switchTo
.import XCoordinator
enum SomeRoute: Route {
case onboarding, help
}
final class SomeCoordinator: ViewCoordinator<SomeRoute> {
private lazy var viewModel = SomeViewModel(router: weakRouter)
override func prepareTransition(for route: SomeRoute) -> ViewTransition {
switch route {
case .onboarding:
let coord = OnboardingCoordinator()
return .switchTo(coord, in: viewController)
case .help:
let vc = HelpViewController()
vc.bind(to: viewModel)
return .present(vc)
}
}
}
<aside>
🛠 Note: switchTo
is a custom extension. Check HelperKit — Standard Extensions.
</aside>
Sometimes a view controller needs some properties to be set before appearing on the screen. Using enums with associated values we can create routes that take arguments and initialize the controller with them:
import XCoordinator
enum SomeRoute: Route {
case profile(id: UUID)
case searchResults(query: String, results: [String])
}
override func prepareTransition(for route: SomeRoute) -> ViewTransition {
switch route {
case .profile(let id):
let vc = ProfileViewController()
vc.id = id
return .present(vc)
case .searchResults(let query, let results):
let vc = SearchResultsViewController()
vc.query = query
vc.results = results
return .present(vc)
}
}