Сравнение SwiftUI и UIKit для производительности и архитектуры iOS-приложений видеоконференций

Главное

Чистый SwiftUI редко становится правильным выбором для видеоконференций. Самый быстрый путь к работающему приложению в 2026 году — гибрид: SwiftUI для обрамления и элементов управления, UIKit для видео-полотна и рендереров.

SwiftUI потребляет примерно на 13% больше памяти и на 5–10% больше CPU, чем UIKit, на сопоставимых сетках видео. Это начинает играть роль, когда вы переходите рубеж в ~12 одновременных плиток или ориентируетесь на старые устройства.

Видео-поверхность должна быть на UIKitAVSampleBufferDisplayLayer, Metal-вью и рендереры WebRTC являются подклассами UIView, поэтому вы оборачиваете их в UIViewRepresentable и защищаете от пересоздания при каждой перерисовке.

Баги в цикле состояния убивают больше видео-приложений, чем низкий FPS. Никогда не храните кадры в @State; используйте @Observable (iOS 17+) с локальным чтением и пакетируйте обновления через Combine.

Фора Софт выпустила 625+ продуктов реального времени с видео. Если вам нужен консультационный созвон по выбору SwiftUI или UIKit для вашего приложения, мы пришлём матрицу решений в течение 48 часов после первого 30-минутного звонка.

Почему этот playbook написала Фора Софт

Мы создаём продукты с видео реального времени на iOS с тех пор, как на платформе появились первые сборки WebRTC. За 21 год мы выпустили более 625 видео- и стриминговых продуктов: от приложений для звонков один на один вроде Speakk до классных платформ с тысячами одновременных плиток вроде BrainCert, и от социальных видеосетей вроде ChillChat до облачных переговорных вроде ProVideoMeeting. Когда в 2019 году вышел SwiftUI, мы попытались перейти на него агрессивно — и на собственном опыте поняли, где он силён, где ломается под нагрузкой реального времени, а где UIKit по-прежнему отрабатывает свой хлеб.

Этот playbook — то, что мы рассказали бы основателю, CTO или руководителю инженерной команды на 30-минутном звонке. Это не общее SwiftUI — будущее; это прагматичный гид по решениям, основанный на приложениях, которые реально пережили продакшен: реальные устройства, реальные сети, реальные бюджеты и реальные пользователи, которые не терпят зависшую плитку во время продающей встречи или медицинской консультации. Каждую рекомендацию мы подкрепляем цифрами, SDK, которые используем каждый день, и ошибками, которые мы совершили, чтобы вам не пришлось их повторять.

Если вам нужно второе мнение по выбору фреймворка или оценка по вашему видео-приложению, наш процесс Agent Engineering превращает 30-минутный звонок в пронумерованный план за дни, а не недели. Свяжитесь с нами и принесите свой самый сложный крайний случай.

Не можете решить, что выбрать — SwiftUI или UIKit для видео-приложения?

Расскажите про целевые устройства, количество участников и срок запуска. Мы предложим гибридную архитектуру и реалистичные сроки.

Позвоните нам → Напишите нам →

Вердикт — на чём реально выпускают видеозвонки в 2026 году

Короткий ответ: каждое серьёзное iOS-приложение для видеоконференций, которое мы видели в продакшене в 2026 году, либо построено только на UIKit, либо является гибридом SwiftUI и UIKit. Приложения для звонков на чистом SwiftUI существуют, но это прототипы, внутренние инструменты или демо для звонков один на один — не платформы, которые обслуживают тысячи одновременных плиток на стабильной частоте кадров. Zoom, Microsoft Teams и Google Meet под капотом по-прежнему рендерят видео-поверхности на UIKit. Сама Apple выпускает FaceTime на внутренних фреймворках, которые ближе к UIKit, чем к SwiftUI.

Причина архитектурная, а не модная: рендеринг видео в iOS происходит на AVSampleBufferDisplayLayer, Metal-слоях или WebRTC-вью RTCMTLVideoView — и всё это подклассы UIView. SwiftUI предоставляет их через UIViewRepresentable, но каждый такой мост — это шов, который может пересоздать рендерер, отбросить кадры или вступить в конфликт с механизмом сравнения SwiftUI. В чат-приложении этот шов незаметен. В классе на 20 плиток это разница между возвратом денег и потерей корпоративного клиента.

Это не делает SwiftUI плохим выбором. Это делает его правильным выбором для 70% интерфейса, которые не являются живым видео — чат, списки участников, настройки, онбординг, экраны, связанные с CallKit, прелобби. Наше правило: SwiftUI снаружи встречи, UIKit (или обёрнутый UIKit) внутри неё. Это правило позволяет вам двигаться быстро и при этом сохранять плавность встреч.

Выбирайте гибрид SwiftUI и UIKit, когда: вам нужно выпустить быстро, в ваших встречах 2–24 одновременных видеоплитки, а команда готова написать хотя бы одну обёртку UIViewRepresentable вокруг видео-рендерера.

Где SwiftUI хорош, а где ломается

SwiftUI зарабатывает себе репутацию на всём, что не является высокочастотной работой с пикселями. Онбординг, логин, экраны панели управления, списки чатов, панели участников, настройки, выбор устройств, субтитры, реакции, оценки в конце звонка — везде здесь декларативный синтаксис SwiftUI сокращает время разработки на 30–40% и делает предпросмотр повседневным инструментом. @Observable (iOS 17), SF Symbols, встроенный тёмный режим и Dynamic Type — это выигрыш в продуктивности, который ваша команда почувствует уже в первую неделю.

SwiftUI ломается под нагрузкой видео на пути высокочастотных обновлений. Если вы храните покадровые или попартисипантные битмапы в @State, вы запустите каскадные проходы сравнения и увидите, как насыщается главный поток. Если вы наивно поместите UIViewRepresentable внутрь родительского представления, которое часто перерисовывается, ваш рендерер будет уничтожаться и пересоздаваться при каждой перерисовке — пользователи увидят мерцание, чёрные кадры или откат на одно аудио. Мы отлаживали такие баги на выпущенных приложениях, и эти проблемы никогда не появляются в первую неделю проекта; они всплывают через три месяца, после того как количество участников переваливает за 8 или 12.

Второй режим отказа — платформенные функции, которые ещё не догнали. AVPictureInPictureController, кастомная блокировка ориентации, кастомный UI CallKit, тонкая разводка жестов между pinch-to-zoom на плитке и swipe-to-dismiss на родителе — всё это до сих пор требует UIKit как запасного люка. SwiftUI с каждым годом подбирается ближе, но для продакшен-видео-приложения вы всё равно коснётесь UIKit хотя бы в одной из этих точек.

Где SwiftUI отрабатывает свой хлеб

1. Обрамление приложения. Стеки навигации, табы, модальные шторки и кнопки тулбара превращаются в однострочники. NavigationSplitView и NavigationStack справляются с раскладками iPad и Mac Catalyst без отдельных сторибордов.

2. Чат и списки участников. Списки с ForEach по модели участника — чистые, быстрые и хорошо сравниваются. Перетаскивание для сортировки, swipe-действия, поиск и pull-to-refresh достаются бесплатно.

3. Реакции, оверлеи и аннотации. Анимированные оверлеи поверх видеоплитки — emoji-реакции, бейджи поднятой руки, ярлычки с именами — это места, где блистают .animation и matchedGeometryEffect.

4. Предпросмотр и обзоры дизайна. С макросами #Preview ваш дизайнер может прокликать конечный автомат звонка, не собирая и не запуская приложение. Только это экономит неделю за квартал на команде из пяти человек.

5. Доступность. VoiceOver, Dynamic Type и уменьшенная анимация работают из коробки. UIKit требует дополнительного кода под каждое; SwiftUI получает их практически бесплатно.

Где SwiftUI ломается под нагрузкой видео

1. Циклы состояния на пути видео. Хранение CVPixelBuffer или CMSampleBuffer в @State вызывает полную перерисовку на каждый кадр. Даже плитка с 15 fps может насытить главный поток. Решение — держать кадры полностью вне SwiftUI: видео-слой рендерится сам, а SwiftUI владеет только раскладкой.

2. Пересоздание UIViewRepresentable. Каждая перерисовка родителя может заново вызвать makeUIView и снести нижележащий слой. С этим борются стабильной идентичностью (.id(participant.id)), equatable-моделями и тем, что выносят representable за пределы пути чтения состояния родителя.

3. LazyVGrid с множеством плиток. Для более чем ~20 участников LazyVGrid лениво создаёт и уничтожает плитки по мере прокрутки. С прикреплённым живым видео это означает, что видео останавливается и стартует на краях прокрутки. UICollectionView с предзагрузкой справляется с этим аккуратнее.

4. Конфликты жестов. Pinch-to-zoom на плитке, drag-to-dismiss, tap-to-focus — модификаторы жестов SwiftUI сталкиваются с распознавателями жестов UIKit внутри вашего рендерера. Координаторы и simultaneousGesture решают большинство случаев, но каждый из них — отдельная сессия отладки.

5. Управление ориентацией на одном экране. Зафиксировать один экран в альбомной ориентации, пока остальная часть приложения остаётся в портретной, по-прежнему требует подкласса UIHostingController и переопределений. Чистый SwiftUI в iOS 17/18 не делает это чисто.

Где UIKit по-прежнему выигрывает на видео-поверхностях

UIKit на десять лет зрелее SwiftUI, и нигде это не видно так явно, как внутри звонка. Каждый коммерческий видео-SDK — LiveKit, Agora, Twilio, Zoom, Daily, 100ms, Vonage — поставляет свой рендерер как UIView. Каждый фреймворк Apple на пути видео — AVFoundation, VideoToolbox, Metal, AVKit, UI провайдера CallKit — ориентирован на UIKit в первую очередь. Когда во встрече что-то идёт не так, ответы на Stack Overflow, примеры с WWDC и инженерные блоги, которые вы будете искать, — все будут на UIKit.

Производительность — вторая причина. Замеренные бенчмарки, опубликованные в 2025 году, показывают, что UIKit использует примерно на 13% меньше памяти и на 5–10% меньше CPU, чем SwiftUI, на сопоставимых сетках участников. На звонке один на один разница незаметна. На 12-человечной встрече в классе на iPhone 12 это разница между стабильными 30 fps и тепловым тротлингом до 15 fps через 20 минут. На старых устройствах (iPhone X, iPad mini 5) разрыв расширяется до 20% и больше.

UIKit также даёт вам прямые и предсказуемые жизненные циклы. viewDidLoad, viewWillAppear и viewDidDisappear дают вам точные точки входа, чтобы запустить или остановить камеру, прикрепить удалённый рендерер или освободить MTKView. .onAppear и .task в SwiftUI близки, но есть крайние случаи (переключение табов, закрытие модалок, уход в фон), где они срабатывают непредсказуемо и заставляют вас бороться с дублирующимися треками или брошенными сессиями.

Выбирайте UIKit, когда: вы рендерите живое видео на 24+ fps, ваша встреча должна поддерживать 12+ одновременных плиток на iPhone 13 или старше, либо вы интегрируетесь напрямую с AVCaptureSession, Metal или низкоуровневым видео-API WebRTC.

Гибридный шаблон, который реально используют выпущенные приложения

Примерно 70% профессиональных iOS-команд, выпускающих видео-продукты в 2026 году, используют ту или иную версию одного и того же шаблона: каркас приложения — SwiftUI, экран звонка — SwiftUI-контейнер, а сами видеоплитки — рендереры UIKit, обёрнутые в UIViewRepresentable. Состояние течёт через общую для обоих слоёв модель звонка с @Observable. Результат сочетает скорость разработки SwiftUI на 70% поверхности со стабильностью UIKit на тех 30%, которые важнее всего.

Архитектурное правило, которое мы даём каждой команде, — простое трёхслойное разделение. Слой первый — чистая Swift-модель домена звонка: комната, локальный участник, удалённые участники, треки, состояние медиа — без импортов UIKit или SwiftUI. Слой второй — @Observable-фасад, который читает интерфейс. Слой третий — интерфейс, где SwiftUI владеет раскладкой, а UIKit владеет пикселями. Если держать модель домена свободной от фреймворков, её можно юнит-тестировать, переиспользовать на macOS через Catalyst и заменить стек интерфейса позже, если Apple изменит правила игры.

Обёртка UIViewRepresentable вокруг видео-рендерера должна быть минимальной. Создавайте вью один раз, задавайте идентификатор участника через updateUIView, никогда не пересоздавайте Metal-слой и используйте EquatableView или явный .id(), чтобы SwiftUI не сносил её. Помещайте representable в LazyVGrid только если количество участников ниже ~20, и используйте UICollectionView для всего, что больше.

// Минимальный безопасный UIViewRepresentable для плитки WebRTC
struct VideoTile: UIViewRepresentable, Equatable {
    let participantID: String
    let renderer: RTCVideoRenderer  // кэшируется снаружи через VM

    func makeUIView(context: Context) -> RTCMTLVideoView {
        let view = RTCMTLVideoView()
        view.videoContentMode = .scaleAspectFill
        return view
    }

    func updateUIView(_ view: RTCMTLVideoView, context: Context) {
        // Подключаем трек только один раз на участника; CallModel владеет кэшем
        CallModel.shared.attach(participantID: participantID, to: view)
    }

    // Equatable не даёт SwiftUI заново вызывать representable
    static func == (lhs: VideoTile, rhs: VideoTile) -> Bool {
        lhs.participantID == rhs.participantID
    }
}

Бенчмарки производительности бок о бок

Цифры ниже агрегируют опубликованные бенчмарки и наши внутренние замеры на сетках из 4, 12 и 24 плиток, на которых работает WebRTC SFU на iPhone 12, 14 Pro и 15 Pro. Абсолютные значения зависят от разрешения плитки и кодека, поэтому относитесь к ним как к относительным дельтам. Вывод устойчивый: SwiftUI достаточно дёшев для обрамления и маленьких встреч, заметен на среднем масштабе и становится обузой на большом масштабе без UIKit-обходов.

Сценарий Метрика Чистый UIKit Гибрид Чистый SwiftUI
4 плитки, iPhone 14 Pro Устойчивый FPS 30 30 29–30
4 плитки, iPhone 14 Pro Пиковая память ~92 МБ ~105 МБ ~140 МБ
12 плиток, iPhone 12 Устойчивый FPS 28–30 26–30 18–24 (тротлинг)
12 плиток, iPhone 12 CPU на 20-й минуте 38% 42% 58%
24 плитки, iPhone 15 Pro Устойчивый FPS 30 30 22–27 (джиттер)
24 плитки, iPhone 15 Pro Пиковая память ~240 МБ ~260 МБ ~340 МБ
Только чат и обрамление Скорость разработки База +30% +35–40%

Стоит выделить два паттерна. Во-первых, гибрид находится в пределах погрешности измерения относительно чистого UIKit по устойчивому FPS — накладные расходы SwiftUI живут в основном на старте и в проходах сравнения, а не на горячем пути видео. Во-вторых, чистый SwiftUI на масштабе медленный не потому, что SwiftUI сам по себе медленный, а потому, что шаблоны по умолчанию (хранение кадров в состоянии, пересоздание representable) работают против вас. Дисциплинированное приложение на чистом SwiftUI с аккуратным @Observable-скопингом может закрыть большую часть разрыва — но к этому моменту вы уже изобрели заново шаблоны UIKit, так что честным ответом остаётся гибрид.

Для подробной настройки производительности iOS за рамками выбора фреймворка прочитайте наш сопутствующий гид о том, как оптимизировать iOS-приложения по скорости и стабильности — там рассказано про работу с Instruments, утечки памяти и оптимизацию времени запуска, и всё это умножается на выбор фреймворка.

Сравнительная матрица по возможностям

Когда цифры бенчмарков улягутся, большинство ваших решений всё равно сведётся к разрывам в возможностях — какой фреймворк даёт нужную вам функцию сегодня без обходных путей. Матрица ниже — это шпаргалка, которую мы выдаём новым сотрудникам перед их первым проектом по видео-приложению.

Возможность SwiftUI нативно Нужен мост UIKit Трудоёмкость на UIKit (дни)
Превью камеры Нет AVCaptureVideoPreviewLayer 0,5–1
Удалённая плитка WebRTC Нет RTCMTLVideoView 1–2
Picture-in-Picture Нет AVPictureInPictureController + делегат 3–5
Входящий звонок CallKit Нет CXProvider + PushKit 3–7
Блокировка ландшафтной ориентации на экран Нет Переопределение UIHostingController 1
Прокручиваемая сетка из 20+ плиток LazyVGrid (нестабилен на масштабе) Рекомендуется UICollectionView 2–4
Анимированные оверлеи на плитке Да 0
Dynamic Type и VoiceOver Да (по умолчанию) 0
Фильтры и LUT на Metal Нет MTKView + CIContext 3–8

Паттерн становится очевиден, когда сканируете правый столбец: почти каждая возможность, специфичная для видео, требует моста на UIKit, и почти каждая не-видео возможность достаётся бесплатно в SwiftUI. Это и есть структурный аргумент за гибридный подход — не вкус, не запас прочности на будущее, а голая форма API платформы в 2026 году.

Нужно второе мнение по вашему экрану звонка на SwiftUI?

Мы замеряем сетки участников, ловим циклы состояния и проводим гибридные рефакторинги. Большинство аудитов находят главную причину выпадения кадров за один звонок.

Позвоните нам → Напишите нам →

Управление состоянием для живых сеток участников

Самое крупное решение по производительности и корректности в SwiftUI-приложении с видео — это форма состояния. Неправильная модель кормит петлю обратной связи: одно свойство участника меняется, SwiftUI пересравнивает всю сетку, каждый UIViewRepresentable заново вызывает updateUIView, видео замирает на долю секунды, пользователь винит ваше приложение. Сделайте модель правильно — и та же сетка удержит 30 fps на протяжении 60-минутной сессии.

В iOS 17 и новее мы настойчиво рекомендуем макрос @Observable вместо ObservableObject и @Published. @Observable отслеживает чтения на уровне свойств внутри тела каждого вью, поэтому тулбар, который читает только isMuted, не перерисовывается, когда меняется activeSpeakerID. Для целей с iOS до 17 имитируйте это несколькими мелкими классами ObservableObject, локализованными по зоне ответственности.

Три правила, которые держат сетки плавными

1. Никогда не храните данные кадров в состоянии SwiftUI. Видео-рендерер владеет своим буфером пикселей. SwiftUI владеет только размером плитки, её позицией и идентичностью участника.

2. Локализуйте состояние до наименьшей изменяющейся единицы. Отдельная ParticipantViewModel на плитку, сама являющаяся @Observable, выигрывает у единого CallModel, который публикует всех участников по любому событию.

3. Пакетируйте высокочастотные события. Обновления активного говорящего, индикаторы уровня звука и бейджи качества сети срабатывают много раз в секунду. Дросселируйте их через Combine (.throttle(for: .milliseconds(250))) или через AsyncSequence с оператором сэмплирования, прежде чем они доберутся до вью-модели.

Подключение AVFoundation, Metal и WebRTC к SwiftUI

95% iOS-приложений с видео покрывают три конвейера рендеринга. Каждый из них родной для UIKit и для каждого есть безопасный шаблон, чтобы прокинуть его в SwiftUI.

AVCaptureVideoPreviewLayer (превью локальной камеры). Оберните UIView, у которого класс слоя — AVCaptureVideoPreviewLayer, прокиньте сессию через инициализатор и установите videoGravity = .resizeAspectFill. Никогда не храните сессию в @State; держите её в синглтоне или вью-модели с явными методами старта и остановки.

AVSampleBufferDisplayLayer (свои декодированные кадры). Когда вы декодируете кадры сами — например, при фильтрации на Metal или тональном маппинге HDR — используйте AVSampleBufferDisplayLayer.enqueue(_:). Это также предпочтительный API для кастомных рендереров WebRTC. Прокидывайте через UIViewRepresentable.

RTCMTLVideoView / LiveKit VideoView (конвейер SFU). Все серьёзные WebRTC SDK в 2026 году поставляют рендерер на базе MTKView. Оборачивайте его, но кэшируйте подключения по идентификатору участника, чтобы перерисовки SwiftUI не отсоединяли и не подсоединяли трек заново. Эта одна оптимизация кэширования отвечает за большинство багов плитка мерцает, которые мы отлаживали.

AVPlayerLayer (воспроизведение VOD внутри звонка). Повторы, режим совместного просмотра и вебинары часто требуют предзаписанного потока рядом с живым видео. Нативный для SwiftUI VideoPlayer работает, но для управления адаптивным битрейтом или кастомных оверлеев откатывайтесь на AVPlayerLayer через representable.

Безопасный сниппет превью камеры

final class PreviewView: UIView {
    override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
    var previewLayer: AVCaptureVideoPreviewLayer {
        layer as! AVCaptureVideoPreviewLayer
    }
}

struct CameraPreview: UIViewRepresentable {
    let session: AVCaptureSession  // принадлежит вью-модели, не пересоздаётся

    func makeUIView(context: Context) -> PreviewView {
        let v = PreviewView()
        v.previewLayer.session = session
        v.previewLayer.videoGravity = .resizeAspectFill
        return v
    }
    func updateUIView(_ view: PreviewView, context: Context) {}
}

PiP, CallKit и ориентация — хитрые куски платформы

Три платформенные функции затаскивают UIKit даже в почти-SwiftUI-приложение. О них стоит сказать явно, потому что каждый основатель, с которым мы встречаемся, предполагает, что SwiftUI закрывает их в 2026 году. Он не закрывает.

Picture-in-Picture. AVPictureInPictureController требует делегат и слой-источник — и то и другое из UIKit. Оберните свою видео-поверхность в координатор и проложите управление PiP в SwiftUI через вью-модель. Без PiP пользователи, которые переключаются на мессенджер или календарь во время звонка, теряют видео, и удержание для мобильных продуктов для встреч обваливается.

CallKit. Интерфейс входящего звонка и обработка на экране блокировки идут через CXProvider и CXCallController — это системный UI, управляемый UIKit-делегатом. SwiftUI обслуживает экран после ответа, но само событие ответа или отклонения остаётся на территории UIKit. В сочетании с PushKit для VoIP-пушей это подсистема UIKit, которая останется в вашем приложении навсегда.

Ориентация на одном экране. Зафиксировать один экран (звонок) в альбомной ориентации, пока остальная часть приложения остаётся в портретной, по-прежнему требует подкласса UIHostingController, который переопределяет supportedInterfaceOrientations. Полноценного решения на чистом SwiftUI в iOS 18 нет.

Внешняя камера и Continuity Camera. Если ваше приложение поддерживает Continuity Camera на iPadOS и macOS, либо внешние USB-камеры, то AVCaptureDevice.DiscoverySession и уведомления о смене устройства устроены в форме UIKit/AppKit. Вы обрабатываете их в сервисном слое, а не в SwiftUI.

Сетка на 20 плиток — референсная архитектура

Вот архитектура, которую мы разворачиваем, когда клиент просит экран конференций на 20+ плиток без теплового тротлинга на устройствах класса iPhone 13. Это сжатый результат примерно дюжины продакшен-внедрений в образовании, телемедицине и корпоративных встречах.

Контейнер. SwiftUI-вью владеет раскладкой и предоставляет органы управления — переключатель раскладки (сетка/говорящий), реакции, mute, выход. Она читает единственную @Observable CallViewModel, но только те свойства, которые реально отображает.

Сетка. Для менее чем 20 активных плиток — LazyVGrid со стабильным .id(participant.id) на каждой плитке. Для 20 и больше — UICollectionView внутри UIViewControllerRepresentable с предзагрузкой и diffable data sources. Переиспользование ячеек в UIKit предсказуемее ленивых стеков SwiftUI на масштабе.

Плитка. Каждая плитка — это VideoTile(participantID:), Equatable UIViewRepresentable. Нижележащий MTKView держит сервисный слой в пуле и переиспользует между участниками — никогда не выделяется покадрово.

Оверлеи. Ярлычок с именем, бейдж mute, кольцо активного говорящего, реакции — чистый SwiftUI, сидит поверх плитки в ZStack. Каждый читает отдельные маленькие модели, локализованные на участника.

Подписки. Модель звонка подписывается только на верхние N активных треков (обычно N=9 в режиме говорящего, N=20 в сетке) и сообщает SFU, какие слои simulcast присылать. Это трюк, который позволяет классу на 200 участников держать 30 fps: пользователь видит максимум 20 плиток, SFU присылает только эти потоки, а устройство никогда не декодирует больше, чем показывает.

Управление температурой и энергией. Пробросьте ProcessInfo.thermalState в модель звонка. Когда тепловое состояние достигает .serious или .critical, понижайте плитки не-говорящих до режима только-аудио и сокращайте слой simulcast до 180p. Пользователи скорее согласятся видеть меньше лиц, чем смотреть, как телефон выключается.

Мини-кейс — чему нас научил выпуск ChillChat и Speakk

ChillChat — это социальная видео-сеть, где незнакомцы встречаются в тематических комнатах до 8 участников. Мы построили iOS-приложение на SwiftUI для всех не-встречных поверхностей — онбординг, каталог комнат, профили, инструменты модерации — и спрятали живые плитки за рендерером UIKit. С аккуратным @Observable-скопингом и попартисипантным .id() мы держим 30 fps на iPhone 11 и новее, при этом срезав примерно 4 недели с графика благодаря предпросмотру и декларативным спискам SwiftUI.

Speakk — мессенджер в стиле WhatsApp с видеозвонками один на один и в небольших группах. Здесь поверхность звонка проще — одна или две плитки — так что мы продвинули SwiftUI дальше, оставив на UIKit только превью камеры и удалённый рендерер. Компромисс: больше скорости фич в чате, но нам всё равно понадобились CallKit, PushKit и PiP, а это всё UIKit. Урок: в основном SwiftUI всё равно означает, что вы держите три UIKit-подсистемы.

В обоих продуктах паттерном, который значил больше всего, оказался единый сервис CallController, который владел AVFoundation, WebRTC-треками и жизненным циклом рендерера. Слой интерфейса — SwiftUI или UIKit — только спрашивал дай вью для участника X и никогда не трогал медиа напрямую. Эта одна архитектурная граница сделала оба приложения тестируемыми, позволила нам поменять видео-SDK (с Twilio на LiveKit на одном из них) без переписывания интерфейса и удержала баг-репорты подальше от графа состояний SwiftUI.

Если вам нужен похожий архитектурный обзор по вашему приложению, 30-минутного консультационного созвона обычно достаточно, чтобы вытащить два главных риска в вашем текущем стеке и наименьший рефакторинг, который стабилизирует экран звонка.

Фреймворк решений — выберите стек за пять вопросов

Пройдитесь по этим пяти вопросам по порядку. Каждый из них либо подтвердит вашу текущую интуицию, либо подтолкнёт к другому разделению.

В1. Сколько одновременных видеоплиток должно быть на экране? 1–2 плитки: SwiftUI-ориентированный подход подойдёт. 3–12 плиток: гибрид. 12+ плиток или прокручиваемые сетки: UIKit-ориентированный подход или UICollectionView внутри representable.

В2. Какое у вас минимально поддерживаемое устройство? iPhone 13 и новее: подойдёт любой выбор. iPhone 11/12 или iPad mini 5: гибрид или UIKit. iPhone 8/X на iOS 16: безопасный выбор по умолчанию — UIKit.

В3. Нужны ли вам PiP, CallKit или ориентация на отдельных экранах? Любой из этих пунктов означает, что UIKit входит в вашу кодовую базу. Закладывайте слой хостинга UIKit с первого дня, а не дотягивайте его задним числом.

В4. Сколько времени до первого запуска? 8 недель или меньше: гибрид с SwiftUI-ориентированным обрамлением выигрывает по скорости. 16+ недель: выбирайте разделение, которое минимизирует долгосрочную поддержку — часто это UIKit-ориентированный подход на экране встречи.

В5. Насколько опытна ваша iOS-команда в Metal, AVFoundation и WebRTC? Если ответ — невысоко, оставайтесь с SDK (LiveKit, Daily, 100ms), который даёт готовый рендерер, и используйте SwiftUI только для обрамления. Если же вы катите собственный видео-конвейер, сторона UIKit растёт быстро.

Подводные камни, которых стоит избегать

1. Хранение кадров в @State. Одного @State var currentFrame: CVPixelBuffer? достаточно, чтобы обвалить производительность на сетке из 4 плиток. Держите кадры вне SwiftUI — всегда.

2. Пересоздание UIViewRepresentable при каждой перерисовке. Если ваш representable определён inline внутри вью, которое перерисовывается, вы получаете новый вызов makeUIView каждый раз. Вынесите структуру наружу, сделайте её Equatable и прикрепите .id(participant.id).

3. LazyVGrid внутри ScrollView без стабильных ID. Прокручиваете сетку — плитки уничтожаются и пересоздаются, видео стартует заново. Всегда прикрепляйте стабильные идентичности и предзагружайте треки следующего ряда до того, как пользователь долистает.

4. Выпуск без трейсов Instruments. Если у вас нет трейса time-profiler в режиме до и после по 10-минутному звонку, в следующем релизе вы выпустите регрессию производительности. Сессия с WWDC25 Optimize SwiftUI Performance with Instruments стоит ваших 60 минут.

5. Игнорирование теплового и энергетического состояния. Звонок, который перегревает телефон за 10 минут, проваливает корпоративные пилоты и убивает рейтинг в App Store. Слушайте ProcessInfo.thermalState и isLowPowerModeEnabled; деградируйте плавно.

KPI, которые стоит измерять после запуска

1. KPI качества. Медианный FPS принимаемого видео на плитку (цель: ≥ 24 на iPhone 11 и новее), оценка MOS для аудио (цель: ≥ 4,0), число замираний на 30-минутный звонок (цель: < 2) и время до первого кадра после подключения (цель: < 1,5 секунды).

2. Бизнес-KPI. Доля успешных звонков (цель: > 95%), доля отказов на прелобби (цель: < 10%), удержание на неделю для пользователей, у которых был хотя бы один звонок (цель: > 40%), и средняя длительность звонка. Это те цифры, которые волнуют вашу продуктовую команду; инженерные решения в итоге проявляются в них.

3. KPI надёжности. Доля пользователей без падений (цель: > 99,5%), доля сессий с зависанием главного потока (цель: < 0,5%) и потребление памяти p95 на 30-минутном звонке с 8 плитками (цель: < 300 МБ на iPhone 12). Выкатите эти дашборды на той же неделе, на которой выкатываете первый экран встречи.

Когда НЕ выбирать SwiftUI

Три случая, когда мы подталкиваем клиентов к UIKit-only даже в 2026 году. Первый: если вам обязательно поддерживать iOS 12 или старше — всё ещё распространённый сценарий в корпоративных развёртываниях с MDM-залоченными устройствами — SwiftUI недоступен. Второй: если ваше приложение — нишевый инструмент для вещания с тяжёлыми Metal-конвейерами (AR-фильтры, виртуальные фоны, композитинг оверлеев, LUT), весь стек рендеринга настолько UIKit-ориентирован, что SwiftUI почти ничего не даёт и стоит вам ещё одного шва для отладки.

Третий: если у вашей команды десять лет опыта на UIKit и ноль на SwiftUI, а до критичного запуска у вас 12 недель, кривая обучения не стоит риска. SwiftUI прощает в приложениях со списками и беспощаден в видео-приложениях на дедлайне. Мы видели, как опытные UIKit-команды теряли по три недели на один баг с циклом состояния. Начинайте гибрид после запуска, а не во время.

Влияние на стоимость и сроки

Выбор фреймворка меняет ваши сроки сильнее, чем большинство основателей ожидает. На обрамлении (70% экранов, которые не являются экраном встречи) SwiftUI экономит около 30–40% времени разработки по сравнению с UIKit. На экране встречи он обычно отъедает часть этой экономии обратно, пока вы боретесь с состоянием и проблемами representable. В сухом остатке для гринфилд-приложения для звонков на iOS это выигрыш по скорости в 15–20%, если гибрид дисциплинированный, и нулевой результат или регрессия, если команда наивно пытается использовать чистый SwiftUI на встрече.

Затраты на поддержку расходятся позже. UIKit-only приложения проще отлаживать, когда что-то ломается в звонке, потому что каждый ответ на форуме сформулирован под UIKit. SwiftUI-приложения дешевле развивать под продуктовые фичи вроде новых раскладок чата, реакций и доработок онбординга. Гибридный подход не самый дешёвый ни по одному измерению, но это наименее рискованный выбор на горизонте продукта в 24 месяца.

Поскольку мы объединяем Agent Engineering с нашей iOS-командой, оценки Фора Софт обычно выходят быстрее и дешевле, чем у традиционных подрядчиков, на обрамлении, и примерно на одном уровне на движке встречи — это та часть, где физика всё ещё доминирует. Если вам нужен реалистичный диапазон по вашему скоупу, поделитесь с нами на звонке целевым числом участников, минимальным устройством и сроком запуска, и мы пришлём пронумерованную оценку в течение 48 часов.

Нужна пронумерованная оценка для вашего видео-приложения?

Расскажите про целевые устройства, число участников и срок запуска. Мы пришлём проработанный план в течение 48 часов после звонка.

Позвоните нам → Напишите нам →

FAQ

Может ли SwiftUI в одиночку справиться с корпоративными видеоконференциями на 20+ участников?

Технически да, прагматически нет. На 20+ плитках механизм сравнения SwiftUI и переиспользование LazyVGrid вступают в конфликт с видео-конвейером, и выпущенные корпоративные приложения прячут сетку за UICollectionView ради предсказуемости. UIKit-полотно внутри SwiftUI-оболочки — стандартный шаблон.

Совместим ли SwiftUI со старыми версиями iOS для видеоконференций?

SwiftUI выходит с iOS 13+, но фичи, важные для видео-приложений (@Observable, современная навигация, улучшенная производительность раскладки), требуют iOS 17 или новее. Для приложения, которое должно работать на iOS 12 или старше — всё ещё распространённый сценарий в корпоративных парках устройств — UIKit остаётся единственным вариантом.

Как SwiftUI и UIKit соотносятся по расходу батареи во время звонков?

Наши замеры показывают, что SwiftUI потребляет на 8–15% больше батареи, чем UIKit, на 30-минутных звонках с 4+ плитками, и связано это с дополнительными проходами сравнения и перерисовками вью. На звонке один на один разница незаметна. На звонке на 12 человек это может вылиться в 10–15 минут меньше времени разговора на одном заряде, что важно для корпоративных командировочников.

Нужно ли писать собственный рендерер, или можно использовать SDK?

Если только ваш продукт не специализированный инструмент для вещания, используйте SDK. LiveKit, Daily, 100ms, Twilio, Agora и Vonage поставляют оптимизированные UIKit-рендереры. Вы оборачиваете их в UIViewRepresentable и фокусируете инженерные усилия на продукте, а не на видео-сантехнике. Мы разбираем компромиссы между SDK подробно в нашем гиде по альтернативам Agora.io.

Какая кривая обучения у UIKit-разработчиков при переходе на SwiftUI?

От двух до четырёх недель для базовой беглости в списках и формах. От восьми до двенадцати недель, прежде чем разработчик интуитивно понимает, когда использовать @State, когда @Observable, а когда Binding на чувствительных к производительности поверхностях. Слой моста (UIViewRepresentable, координаторы) добавляет ещё от четырёх до шести недель. Закладывайте полный квартал, прежде чем команда выпустит продакшен-грейд видео на SwiftUI.

Есть ли готовые UI-компоненты для видеоконференций в SwiftUI?

Да — Stream, LiveKit и 100ms поставляют дружественные SwiftUI библиотеки компонентов для плиток, элементов управления и списков участников. Это хорошая стартовая точка для MVP. Для брендированного продукта вы всё равно будете их сильно кастомизировать, но дефолтный интерфейс экономит примерно две-три недели бойлерплейта.

Как обрабатывать Picture-in-Picture в SwiftUI?

Оберните AVPictureInPictureController в координатор, прикреплённый к тому же UIViewRepresentable, что и ваш видео-рендерер. Управляйте состоянием PiP из SwiftUI через @Observable-модель. Нативного API PiP для SwiftUI в iOS 18 нет, и попытки впихнуть его без UIKit — источник большинства баг-репортов PiP не запускается.

Стоит ли мигрировать существующее UIKit-приложение с видео на SwiftUI?

Редко — как полная переписка; почти всегда — как постепенная миграция обрамления и настроек с сохранением экрана встречи нетронутым. Полная переписка зрелого UIKit-приложения для звонков занимает 6–12 месяцев и обычно выходит с регрессией производительности. Гибридная миграция сохраняет стабильные части и даёт ускорение там, где оно нужно.

Архитектура iOS

Playbook по MVVM-C для iOS на 2026 год

SwiftUI @Observable, координаторы, DI и Swift 6 concurrency — архитектурный слой за каждым гибридным видео-приложением.

Swift 6

Разработка на iOS со Swift 6 — видеочат нового поколения

Строгая конкурентность, защита от data race и как новая модель Swift вписывается в видеочаты на SwiftUI или UIKit.

Стек WebRTC

Альтернатива Agora.io в 2026 году

Кастомный WebRTC с LiveKit, mediasoup, Jitsi и Janus — что подобрать к фронтенду на SwiftUI или UIKit.

Производительность

Как оптимизировать iOS-приложения по скорости и стабильности

Работа с Instruments, время запуска, утечки памяти — KPI, которые умножаются на выбор фреймворка в видео-приложениях.

Управление зависимостями

Swift Package Manager для видео-приложений

Как держать LiveKit, WebRTC, Starscream и аналитические SDK воспроизводимыми и с бинарным кэшем — гид разработчика на 2025 год.

Готовы выпустить видео-приложение, которое реально масштабируется?

Вопрос SwiftUI или UIKit не идеологический, а архитектурный. Выпускайте обрамление на SwiftUI, выпускайте полотно встречи на UIKit за тонким representable и склейте их дисциплинированной @Observable-моделью. Это разделение даёт вам 30–40% скорости на большей части приложения, сохраняя при этом ту часть, по которой вас оценивают пользователи — живое видео — плавной, предсказуемой и переносимой между SDK.

В Фора Софт мы использовали этот шаблон на 625+ выпущенных продуктах, от социального видео вроде ChillChat до корпоративного обучения вроде BrainCert. Если вам нужен проработанный план для вашего приложения — число участников, минимальное устройство, срок запуска, честные компромиссы — приходите со своим самым сложным крайним случаем на 30-минутный звонок. Agent Engineering превращает этот звонок в пронумерованную оценку за 48 часов.

Принесите самый сложный вопрос по видео-приложению на 30-минутный звонок

Расскажите про целевые устройства, число участников и срок запуска. Мы пришлём набросок гибридной архитектуры и проработанную оценку в течение 48 часов.

Позвоните нам → Напишите нам →

  • Технологии