Ответ
В чистой реализации MVVM+Router (или Coordinator) ответственность за навигацию делегируется ViewModel. Это позволяет соблюдать принцип единой ответственности.
Почему ViewModel, а не ViewController?
- ViewModel содержит бизнес-логику и знает, когда должен произойти переход (например, после успешной загрузки данных или нажатия кнопки).
- ViewController (View) остаётся «глупым»: он только отображает состояние из ViewModel и передаёт ей пользовательские события. Он не должен принимать решения о навигации.
- Router/Coordinator инкапсулирует как выполняется переход (например,
presentилиpush).
Пример взаимодействия:
// ViewModel решает о переходе
class LoginViewModel {
private let router: LoginRouter
func loginButtonTapped() {
// Проверка данных...
router.showHomeScreen() // Команда на навигацию
}
}
// Router выполняет переход
class LoginRouter {
weak var viewController: UIViewController?
func showHomeScreen() {
let homeVC = HomeViewController()
viewController?.navigationController?.pushViewController(homeVC, animated: true)
}
}
// ViewController только передаёт событие
class LoginViewController: UIViewController {
var viewModel: LoginViewModel!
@IBAction func loginTapped(_ sender: Any) {
viewModel.loginButtonTapped()
}
}
Такой подход делает код тестируемым (можно замокать Router) и отделяет логику от UIKit.
Ответ 18+ 🔞
Да ты посмотри, какая красота получается, когда за навигацию отвечает ViewModel, а не этот вечно зажравшийся ViewController! Это ж как в хорошем ресторане: шеф-повар (ViewModel) решает, что и когда готовить, а официант (ViewController) просто таскает тарелки и улыбается. А координатор — это вообще метрдотель, который знает, куда какую тарелку поставить.
А че собственно ViewModel, а не ViewController?
- ViewModel — это мозги операции. Она знает, когда пора переходить на другой экран: данные загрузились, кнопку нажали, звёзды сошлись. Вся логика тут.
- ViewController — это просто говорящая голова, марионетка. Его задача — показать, что ему скажут, и доложить наверх, если юзер тыкнул куда не надо. Решения о навигации? Да он, блядь, даже дорогу до туалета сам найти не должен!
- Router/Coordinator — это наш личный штурман. ViewModel кричит «НАДО ТУДА!», а этот чувак уже знает, как именно туда пролезть:
presentилиpush, может ещё с пиротехникой и фанфарами.
Смотри, как это в коде выглядит, ёпта:
// ViewModel — тут решают
class LoginViewModel {
private let router: LoginRouter // Привязали штурмана
func loginButtonTapped() {
// Тут проверяем логин-пароль, хуле...
router.showHomeScreen() // И командуем: "Вперёд, нахуй!"
}
}
// Router — тут исполняют
class LoginRouter {
weak var viewController: UIViewController? // Знает, откуда плывём
func showHomeScreen() {
let homeVC = HomeViewController() // Цель назначения
viewController?.navigationController?.pushViewController(homeVC, animated: true) // И пошёл пуш!
}
}
// ViewController — тут только тыкают и слушаются
class LoginViewController: UIViewController {
var viewModel: LoginViewModel! // Привязали мозги
@IBAction func loginTapped(_ sender: Any) {
viewModel.loginButtonTapped() // Просто стучимся к мозгу: "Чувак, нажали!"
}
}
Вот такой расклад, блядь. Код становится тестируемым — можно подсунуть фейкового роутера и смотреть, вызывает его вьюмодель или нет. И главное — вся эта UIKit-овская мутотень с переходами заперта в одном месте, а не размазана по всем контроллерам, как дерьмо по стенке. Красота, в рот меня чих-пых!