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.

  1. We define some routes.
  2. inside prepareTransition we return a new screen (not always) and how:
  3. We can bind the new view controller to a View Model — Manage Screen Flow.
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>

Initializing view controllers

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)
	}
}