SwiftUI или UIKit для legacy-приложений: как выбрать сейчас
Выбор между SwiftUI и UIKit для legacy-приложения зависит от типа экрана, опыта команды и объёма кастомного UI. Используйте эту схему, чтобы выбрать практичное сочетание.

Почему в legacy-приложениях этот выбор превращается в путаницу
Выбирать между SwiftUI или UIKit кажется просто, пока не откроешь реальное legacy-приложение. У большинства старых продуктов стек уже смешанный по умолчанию. Одна часть была сделана пять лет назад, другая была быстро доработана в прошлом квартале, а на нескольких экранах до сих пор есть код, который никто не хочет трогать перед релизом.
Именно поэтому команды почти никогда не переводят всё сразу. Полная перепись звучит красиво, но может сломать roadmap. Команда перестаёт выпускать продуктовые изменения, QA тонет в проверках, а люди неделями пересобирают экраны, которые и так работали достаточно хорошо.
Сложность обычно не в видимом UI. Она в старой структуре под ним. Экран может выглядеть как хороший кандидат для SwiftUI, а потом выясняется, что там кастомная навигация, общие UIKit-вью, цепочки delegate, старая логика состояния и особые случаи, добавлявшиеся годами из-за багфиксов. Пересборка такого стека может обойтись гораздо дороже, чем ожидалось.
Кастомные вью делают выбор ещё запутаннее. Некоторые legacy-экраны делают странные, но важные вещи: вложенный скролл, сложные жесты, динамическое изменение размеров или старую анимацию, привязанную к бизнес-правилам. UIKit часто удерживает такие экраны в стабильном состоянии с меньшим риском, даже если SwiftUI выглядит приятнее для новой работы.
Ответ ещё и меняется от экрана к экрану. Страница настроек и сложный checkout-flow не должны попадать в одну корзину. Один можно быстро перенести почти без последствий. Другой при неудачной переписи может сломать аналитику, пограничные случаи или конверсию.
Простой пример хорошо это показывает. Команда может переписать экран профиля за неделю, а потом потерять месяц на старом booking-flow, потому что он завязан на кастомные контейнеры и годы мелких исправлений. Поэтому план UI по экранам обычно лучше, чем решение «с нуля и целиком».
Legacy-приложения становятся запутанными потому, что выбор фреймворка редко связан со вкусом. Он связан с риском, скоростью и тем, сколько старого поведения приложению всё ещё нужно сохранить.
Сначала разберите экраны, а потом выбирайте фреймворк
Начните с инвентаризации того, что у вас уже есть. Спор SwiftUI или UIKit становится намного проще, когда каждый экран записан и у каждого есть понятная задача.
Большинство команд пропускают этот шаг и слишком рано начинают обсуждать фреймворки. Обычно это приводит к общим правилам вроде «всё новое переносим на SwiftUI» или «старое приложение оставляем на UIKit», но оба правила разваливаются, как только смотришь на реальные экраны.
Достаточно простой таблицы. Добавьте к каждому экрану несколько тегов:
- тип: форма, список, дашборд, настройки, медиа, checkout, админка
- бизнес-важность: приносит деньги, влияет на конверсию или создаёт нагрузку на поддержку
- технический багаж: старые SDK, кастомная навигация, кастомные жесты, необычные вью
- риск изменений: стабильный, хрупкий или уже известный как проблемный
- сроки: планируется в этом году, позже или вообще не планируется
Такое разбиение сразу показывает, какие экраны на самом деле похожи. Страница настроек и форма профиля часто относятся к одной группе. Экран камеры, экран карты и экран с глубокими кастомными жестами — к другой. Дашборд может выглядеть простым, но если он зависит от старого кода графиков или особых переходов, его не стоит ставить в один ряд с обычным списком.
Деньги и нагрузка на поддержку важнее, чем вкусовые предпочтения в дизайне. Если экран биллинга, onboarding-flow или страница восстановления доступа каждую неделю создаёт обращения, отметьте его сразу. Даже если под капотом там некрасиво, команда скоро будет к нему прикасаться, значит, для него нужен чёткий выбор миграции.
То же самое относится к экранам, завязанным на старые библиотеки или особые вью. Именно там смешанные стеки часто имеют смысл.
И ещё один фильтр экономит много времени: отделите экраны, которые вы будете менять в этом году, от тех, которые пока трогать не станете. В legacy-приложении может быть 70 экранов, но в следующем цикле релизов важны только 10–15. Именно они должны определять план.
Экраны, которые обычно первыми подходят для SwiftUI
Начинайте с экранов, которые в основном показывают и редактируют данные по знакомому шаблону. Страницы настроек, страницы профиля и простые формы редактирования — хорошие первые кандидаты, потому что они используют контролы, которые Apple уже даёт. У них также обычно понятная логика: загрузить данные, изменить поле, сохранить, закрыть экран.
SwiftUI обычно быстрее окупается на экранах, собранных из стандартных списков, переключателей, пикеров, sheet, alert и простых detail-экранов. Когда состояние легко описать — загрузка, готово, сохранение, ошибка — код часто получается короче и проще для обновления. В legacy-приложении это важнее, чем стремление к полной переписи.
Новые сценарии тоже хорошие кандидаты. Свежий onboarding-путь, форма обратной связи или лёгкий экран подписки могут сильно меняться в ближайшие месяцы. SwiftUI ускоряет изменения в верстке, а это помогает, когда продуктовые решения ещё двигаются.
Хорошие ранние кандидаты часто включают:
- экраны аккаунта и настроек
- страницы профиля и предпочтений
- простые формы создания и редактирования
- новые сценарии, которые, как ожидается, будут часто дорабатывать
- экраны с modal sheet и стандартной навигацией
Частота изменений важна не меньше, чем тип экрана. Если команда ожидает, что копирайт, отступы, порядок полей или пустые состояния будут меняться каждый спринт, SwiftUI может сэкономить время. Часто это и есть практический ответ на вопрос SwiftUI или UIKit в старом приложении.
Первый тест держите небольшим. Добавьте один экран на SwiftUI в текущее приложение, выпустите его и посмотрите, что произойдёт в реальной работе. Так вы узнаете, насколько хорошо команда справляется со сменой состояния, ревью и багфиксами, не ставя на кон всё приложение ради теории.
Экраны, которые обычно дольше остаются в UIKit
Некоторые экраны дорого переносить, даже если к изменениям уже готово остальное приложение. Если экран на UIKit уже хорошо справляется со сложными жестами, таймингом и edge cases, лучше оставить его там. Переписывание логики касаний, которая и так работает, часто создаёт баги, которые пользователи замечают сразу.
UIKit по-прежнему имеет смысл для экранов, которые сильно завязаны на старые API Apple или глубокую кастомизацию вью. Сценарии камеры, экраны карты с наложениями, rich text editor и collection view с необычной раскладкой часто проще делать на UIKit, особенно если команда уже хорошо знает этот код.
- Экраны со слоями жестов, drag handles или кастомным скроллом
- Сценарии камеры и карты с большим количеством системного взаимодействия
- Экраны редактирования rich text с кастомными toolbar или поведением выделения
- Плотные сетки и списки со сложными правилами раскладки
- Старые сценарии с кастомными переходами между экранами
Кастомные переходы требуют особой осторожности. Если в приложении есть checkout, booking или sign-up flow с анимациями, которые уже работают, не трогайте их в загруженный релизный период. Такие экраны часто влияют на выручку или объём обращений в поддержку, поэтому даже небольшой регресс может стоить дороже, чем сэкономит перепись.
Частая ошибка в споре SwiftUI или UIKit — считать каждый старый экран техническим долгом. Некоторые старые экраны просто уже закончены. Если checkout-экран стабильный, быстрый и достаточно простой в поддержке, лучшим решением в этом квартале может быть оставить его в UIKit и потратить время на что-то другое.
Переносите такие экраны только тогда, когда перепись действительно экономит время. Обычно это значит, что текущий код тормозит каждый релиз, мешает новым функциям или снова и снова требует одной и той же болезненной правки. Если ничего из этого не происходит, оставлять UIKit — это не избегать прогресса. Это просто здравое продуктовое решение.
Один простой тест помогает: спросите, почувствуют ли пользователи пользу уже в этом году. Если ответ «нет», оставьте стабильный экран в UIKit и сначала перенесите что-то более простое.
Привяжите план к вашей команде
Выбор фреймворка кажется техническим, но обычно именно команда решает, сработает он или нет. Если два человека могут спокойно выпускать чистый код на SwiftUI, а шесть человек умеют безопасно менять UIKit, это важнее, чем теоретические симпатии команды.
Начните с обычной проверки навыков. Посчитайте, кто сегодня может собирать и отлаживать экраны на SwiftUI без замедления спринта. Потом посчитайте, кто достаточно хорошо знает текущий код на UIKit, чтобы менять его без поломки старых потоков, странных правил навигации или экранных исправлений, которые так и не попали в документацию.
Если эти числа сильно расходятся, не заставляйте команду делать большой прыжок. Худший сценарий — одновременно учить новый UI-фреймворк и переписывать рабочие экраны под давлением дедлайна. Команда тратит время на обе проблемы сразу, и качество быстро падает.
Помогает простая оценочная таблица:
- Кто может выпускать код на SwiftUI почти без помощи на ревью
- Кто умеет поддерживать стабильность UIKit в текущем кодовой базе
- Кто уже работал со смешанными экранами, используя оба фреймворка
- Кто сможет менторить остальных на первых миграциях
Для первого смешанного экрана лучше дать в пару одного разработчика SwiftUI и одного разработчика UIKit. Такое сочетание быстро выявляет слепые зоны. Сторона SwiftUI двигает новый интерфейс вперёд, а сторона UIKit защищает навигацию, жизненный цикл, хуки аналитики и старые edge cases, которые часто пропускают.
Найм тоже важен. Если текущая команда в основном работает с UIKit и на вашем рынке найма ситуация такая же, план «сначала SwiftUI» может выглядеть современно, но всё равно оставить вас без людей, которые смогут что-то выпустить уже в этом квартале. С другой стороны, если SwiftUI-специалистов можно нанять быстрее, чем экспертов по UIKit, это меняет план.
Хороший план UI по экранам всегда опирается на ту команду, которая у вас есть сейчас, и на ту, которую реально можно добавить. Вкус важен. Выпуск продукта важнее.
Посмотрите на кастомное поведение, которое нужно в этом году
Roadmap важнее вкуса. Legacy-приложение может какое-то время жить с двумя UI-фреймворками, но команда почувствует на себе каждый сложный экран, который вы выберете в этом году.
Сначала выпишите UI-работу, которую уже хочет продукт. Не прячьте её под фразой «обновить приложение». Назовите реальные изменения: новая страница тарифов, редактируемый дашборд, drag to reorder, живые графики, ввод с камеры, кастомные переходы или split editor. Обычно такой список быстрее завершает спор SwiftUI или UIKit, чем абстрактные плюсы и минусы.
SwiftUI быстрее всего окупается там, где экраны меняются часто, но ведут себя по обычным правилам. Настройки, формы, onboarding, страницы аккаунта, простые списки и промо-экраны обычно подходят хорошо. Если дизайн хочет менять компоновку каждые несколько недель, SwiftUI часто экономит время.
UIKit по-прежнему больше подходит, когда roadmap зависит от необычного поведения. Команды обычно замедляются на SwiftUI, если им нужно точное управление жестами, сложная анимация слоями, непростой скролл, строгие правила ввода текста или экран, который должен один в один совпадать со старым потоком. Всё это можно сделать и в SwiftUI, но сложность быстро растёт, как только взаимодействие перестаёт быть стандартным.
Не оценивайте только время сборки. Считайте полный счёт:
- время QA на старых устройствах и неудобных размерах экранов
- исправления доступности для VoiceOver, Dynamic Type и порядка фокуса
- багфиксы после первого релиза
- переделки, если продукт через пару месяцев изменит направление
Этот пункт часто упускают. Экран, который делается пять дней, всё равно может стоить ещё три недели, если QA поздно найдёт баги жестов, проблемы доступности и ошибки вёрстки.
Проверяйте выбор ещё раз, когда приоритеты меняются. Если продукт убирает сложное взаимодействие и просит более быстрые изменения в верстке, переводите этот экран ближе к SwiftUI. Если простой на вид экран превращается в кастомный инструмент редактирования, UIKit может сэкономить вам болезненную перепись.
Простой способ решать экран за экраном
Начинайте с аудита приложения, а не с плана переписи. В legacy-приложении один экран может быть аккуратной формой с простым состоянием, а следующий — экраном с кастомными жестами, старыми костылями вёрстки и годами багфиксов. Если вы решаете SwiftUI или UIKit для всего продукта одним махом, вы обычно упускаете эту разницу.
Дайте каждому экрану простой балл. Гигантская таблица не нужна. Достаточно общего документа с короткой заметкой по каждому экрану.
- Сложность UI: простой список и форма или тяжёлая кастомная вёрстка и анимация
- Бизнес-риск: обычная страница настроек или поток, влияющий на выручку и регистрацию
- Частота изменений: редко трогают или обновляют каждый спринт
- Нативные зависимости: базовые контролы или глубокое использование камеры, карт, web view и кастомной навигации
Этот балл подскажет, с чего начать. Выберите один пилотный экран с низким бизнес-риском и средним трафиком. Страница настроек, экран редактирования профиля или дашборд только для просмотра часто лучше, чем checkout, чат или экран, полный кастомных взаимодействий.
Перед тем как трогать экран, вынесите общие модели, сеть и бизнес-правила из UI-слоя. Этот шаг важнее самого выбора фреймворка. Если код представления содержит слишком много логики, любая миграция быстро становится грязной. Если экран общается с чистым модельным слоем, UI можно поменять гораздо спокойнее.
После пилота измерьте, что изменилось. Отслеживайте число багов, время сборки и время ревью нового экрана в течение пары спринтов. Также смотрите, как быстро команда может внести небольшое изменение в интерфейс. Если новый экран обновляется легче и не создаёт новых багов, переходите к следующему похожему экрану.
Если пилот не экономит время, остановитесь и скорректируйте план. Смешанный подход лучше, чем гордая перепись, которая съедает квартал и оставляет самые сложные кастомные UIKit-экраны нетронутыми.
Пример: legacy-приложение со смешанными экранами
Представьте приложение для оптовых заказов, которое существует уже много лет. В нём есть логин, настройки, списки заказов, дашборд с графиками и фильтрами, а также поток сканирования штрихкодов, которым пользуются сотрудники склада. Вот где выбор SwiftUI или UIKit быстро становится практическим вопросом. Одному приложению могут понадобиться оба варианта.
Начните со стабильных и даже немного скучных экранов — в хорошем смысле. Логин, настройки и небольшие формы редактирования обычно хорошо переносятся на SwiftUI первыми. У них понятная вёрстка, ограниченное состояние и меньше edge cases. Команда может пересобрать эти экраны, выпустить их внутри текущего приложения и многому научиться, не рискуя ежедневной работой.
С дашбордом всё иначе. Если там есть кастомные графики, закреплённые секции, сложная логика обновления и много обработки жестов, оставить его в UIKit на время часто безопаснее. То же самое относится к сканированию штрихкодов, если поток зависит от управления камерой, тайминга, наложений или особенностей железа. Такие экраны обычно и скрывают самую грязную часть.
И вам не нужен жёсткий разрыв. Многие команды получают лучшие результаты, встраивая новые экраны на SwiftUI в текущую UIKit-навигацию. Пользователь сохраняет ту же структуру приложения. Команда избегает полной переписи. Риск релиза остаётся ниже.
Реалистичный план может выглядеть так:
- Релиз 1: перенести логин и настройки
- Релиз 2: перенести профиль и простые формы редактирования
- Оставить дашборд и поток сканирования в UIKit
- Отслеживать крэши, UI-баги и скорость сборки после каждого релиза
После двух релизов пересмотрите результат. Команда стала выпускать быстрее? Дизайнеры стали ловить меньше багов в верстке? Навигация осталась простой в поддержке? Если да, выбирайте следующую группу экранов с низким риском. Если нет, замедлитесь и исправьте шероховатости, прежде чем переносить что-то крупнее.
Ошибки, которые съедают время
Старый код — это не то же самое, что плохой код. Команды теряют месяцы, когда переписывают стабильный экран только потому, что он выглядит устаревшим или кому-то не нравится стиль. Если экран быстро загружается, пользователи его понимают, а число багов остаётся низким, пока оставьте его в покое. Потратьте это время на экраны, которые мешают новой работе.
Навигация вызывает проблемы ещё быстрее. Смешанное приложение может использовать SwiftUI или UIKit без драмы, но только если правилами управляет один человек или небольшая группа. Если одна функция использует UIKit coordinators, другая — SwiftUI navigation stacks, а никто не решает, где какой подход уместен, приложение начинает казаться случайным. Deep links ломаются, поведение кнопки «назад» становится странным, а мелкие изменения занимают слишком много времени.
Бизнес-логика — ещё одна тихая утечка времени. Команда делает новый экран на SwiftUI, оставляет старый экран на UIKit для части пользователей, а потом копирует форматирование, валидацию или правила состояния в оба места. На один спринт это кажется быстрым решением. А дальше превращается в двойную поддержку на весь год. Поместите общую логику в один слой и дайте обоим UI-фреймворкам вызывать один и тот же код.
QA-работу часто откладывают как будто «на потом». Это ошибка. Миграция меняет порядок фокуса, VoiceOver-метки, анимации, Dynamic Type и edge cases на старых устройствах. Простой экран настроек может перейти без особого риска. Платёжный поток или плотная форма требуют настоящего времени на тестирование. Если QA и accessibility не учтены в оценке с первого дня, план уже неверный.
Один тяжёлый экран тоже может исказить всё решение. Представьте legacy-приложение с двадцатью экранами списка и деталей, плюс один странный редактор с кастомными жестами, офлайн-синхронизацией и годами особых случаев. Если команда сначала возьмётся за этот редактор и застрянет, она может решить, что вся миграция была ошибкой. Это плохая выборка. Оценивайте план по всему набору экранов, а не по самому уродливому.
Работает более короткое правило:
- оставляйте стабильные экраны, которые уже делают свою работу
- выберите одного владельца навигации и один набор правил
- выносите общую логику за пределы UI-слоя
- закладывайте время на QA и доступность с самого начала
- проверяйте стратегию на обычных экранах, а не только на самом сложном
Такой подход экономит больше времени, чем полная перепись, которая на бумаге выглядит аккуратно, но тормозит команду на полгода.
Быстрые проверки перед тем, как принять решение
Если ваша команда не может ответить на несколько базовых вопросов за одну встречу, выбор всё ещё слишком размыт. Обычно это тот момент, когда команды спорят о SwiftUI или UIKit в теории, а не решают, что именно нужно выпускать.
Сначала смотрите на приложение, которое у вас уже есть, а не на то, которое хотелось бы иметь. Назовите несколько экранов, которые явно относятся к каждой стороне. Простой экран настроек или экран аккаунта только для просмотра часто хорошо подходит для SwiftUI первым. Экран с тяжёлыми жестами, старыми кастомными вью или сложной навигацией может пока остаться в UIKit.
Используйте этот короткий чек-лист перед решением:
- Может ли команда назвать 2–3 экрана, которые явно подходят для SwiftUI, и 2–3 экрана, которые должны остаться в UIKit?
- Понимаете ли вы, какие экраны должны оставаться простыми и стабильными в этом квартале, потому что они влияют на выручку, нагрузку на поддержку или ежедневное использование?
- Выбрали ли вы один пилотный экран, один запасной вариант и одну точку остановки, где вы делаете паузу и оцениваете результат?
- Смогут ли дизайн и QA работать со смешанным стеком, не замедляя релизы и не пропуская edge cases?
- Уберёт ли это решение работу в ближайшие 6–12 месяцев, или оно только создаст больше работы по миграции прямо сейчас?
Пилот, запасной вариант и точка остановки важнее, чем многие думают. Пилот даёт один контролируемый тест. Запасной вариант не даёт застрять, если перепись затянется. Точка остановки заставляет по-настоящему пересмотреть результат, а не надеяться на лучшее.
Дизайн и QA должны участвовать в решении с самого начала. Смешанные стеки могут работать хорошо, но они добавляют мелкие издержки: дублирование паттернов, разное поведение анимаций и больше тестовых случаев на один и тот же пользовательский сценарий.
Хорошее решение должно облегчать следующие два релиза. Если оно просто выглядит аккуратнее на whiteboard, подождите. Для миграции legacy iOS-приложения такой простой фильтр экономит время, деньги и много переделок.
Что делать дальше в смешанном плане миграции
Превратите аудит экранов в простую матрицу «да или нет». Без усложнений. Для каждого экрана ответьте на несколько прямых вопросов: часто ли он меняется, нужен ли ему тяжёлый кастомный функционал, достаточно ли команда знает SwiftUI, и не заблокирует ли этот экран другую работу, если вы начнёте трогать его сейчас.
После такого быстрого прохода обычно получаются три группы:
- Переписывать на SwiftUI сейчас, если экран простой, активный и, скорее всего, скоро снова изменится.
- Обернуть его, если экран в целом работает, но окружающему flow нужен более современный код.
- Оставить в UIKit, если он стабилен, глубоко кастомный или слишком рискованный для изменений до выхода других задач.
- Отложить решение, если у продуктовой команды по экрану ещё есть открытые вопросы.
Именно здесь многие команды экономят время. Они перестают спорить о SwiftUI или UIKit в целом и принимают решение по одному экрану за раз.
Не считайте первый план миграции окончательным. Сразу назначьте дату ревью до первого смешанного релиза. Часто достаточно 4–6 недель, чтобы увидеть, где команда стала работать быстрее, где появились баги и какие обёрнутые экраны начали казаться неудобными. Потом обновите матрицу на основе реальных данных, а не догадок.
Смешанному плану также нужен один владелец. Кто-то должен удерживать правила простыми, следить за исключениями и не давать разовым решениям по фреймворкам расползаться по приложению.
Если вам нужен взгляд со стороны, Oleg Sotnikov может провести ревью приложения, команды и продуктового roadmap в роли Fractional CTO. Такой обзор особенно полезен, когда в кодовой базе годы истории и никто не хочет перепись, которая растянется на месяцы.
Хороший план миграции не обязан выглядеть амбициозно на бумаге. Он должен быть достаточно понятным, чтобы команда могла следовать ему в плотном релизном цикле.