Как решать задачи перевода модулей на SwiftUI
Что проверяют
Умеете ли вы переводить экраны на SwiftUI постепенно, не останавливая релизы, и знаете ли мосты между UIKit и SwiftUI. Следом спрашивают, что делать с навигацией и какой минимальной версией iOS ограничен проект.
Как отвечать
Каркас из четырёх шагов:
- оценка: минимальная версия iOS решает, что доступно.
NavigationStackиUIHostingConfigurationпоявились в iOS 16, макрос@Observableв iOS 17. При таргете ниже часть привычных решений придётся закрывать обёртками; - мосты: SwiftUI-экран встраивается в UIKit через
UIHostingController, ячейка списка черезUIHostingConfiguration, а старый UIKit-компонент (камера, сложный редактор, карта) остаётся внутри SwiftUI черезUIViewRepresentableилиUIViewControllerRepresentable; - порядок: снизу вверх. Сначала листовые экраны и отдельные ячейки, потом экраны целиком. Навигацию на первом этапе удобно оставить в UIKit-координаторах, а SwiftUI получает замыкания для переходов;
- логика: бизнес-логику и сеть не переписывают. Если модуль был на MVVM или VIPER, ViewModel или Presenter становится источником состояния для
View, и меняется только слой отображения.
Проверку перехода стоит строить на снапшот-тестах: снимок старого и нового экрана должен совпадать на нескольких размерах и с крупным шрифтом Dynamic Type. Для быстрой итерации есть #Preview, но превью не заменяет прогон на устройстве, особенно на длинных списках.
Отдельно назовите риски: производительность больших списков, анимации и жесты, которые в UIKit делались вручную, и доступность.
Где спотыкаются
Предлагают переписать модуль целиком за один релиз. Такой рефакторинг надолго блокирует фичи, а сравнить поведение старого и нового экрана не с чем.
Путают @StateObject и @ObservedObject. Модель, созданная через @ObservedObject прямо во вью, пересоздаётся при каждом обновлении родителя, и состояние экрана теряется.
Переносят логику прямо в body: запросы, форматирование, вычисления. body вызывается часто и должен только описывать интерфейс, всё остальное живёт в модели.