Мы в студии перезапустили платформу видеоконтента для «Синхронизации», теперь она классно работает и на десктопе, и на телефоне. Но было еще две задачки:
Так что мы стали думать, как быстро запустить приложение для iOS, не разрабатывая его с нуля.
Дисклеймер: если у вас есть много денег и времени на разработку — нативное приложение будет классным вариантом. Если один из пунктов не выполняется — есть альтернативы, о них и поговорим.Так что мы стали думать, как быстро запустить приложение для iOS, не разрабатывая его с нуля.
Основа «Синхронизации» — видеоконтент. Независимо от технологий, просмотр видео должен быть удобным. Как минимум, нам нужны:
Какие есть варианты в таком случае:
PWA и PWA в тонкой обертке не решали поставленной задачи: в PWA, установленном на iOS, не работает Picture in Picture (хотя в обычном вебе все ок), и в обоих вариантах сложно сделать надежный офлайн-просмотр. В случае с True Native Apps очень дорогая разработка. Поэтому в итоге мы выбирали между Hybrid App или Cross-Platform SDK.
Flutter и React Native — понятные варианты. Много готовых модулей, trade off известны. Выбираем технологию, нанимаем разработчиков, делаем кроссплатформенное приложение. Но нам не давала покоя мысль: «А что, если все-таки можно сделать из нашей новой веб-платформы первую версию приложения?». Профиты от этого решения колоссальные: приложение могут поддерживать фронтендеры, любая новая фича сразу появляется и вебе, и на iOS, а в перспективе — и на Android.Так что мы стали думать, как быстро запустить приложение для iOS, не разрабатывая его с нуля.
Hybrid App — экзотическая технология, риски высокие. Стоит ли играть в эту игру? Поговорили с заказчиком, посомневались, взвесили все за и против, собрали техническое демо с ключевыми фичами, и решили — кто не рискует, тот не пьет шампанское, пробуем CapacitorJS! Спойлер — у нас получилось, а с каким сложностями столкнулись по пути, расскажем дальше.

CapacitorJS выглядит убедительно: современный дизайн, слоган «cross-platform native runtime for web apps», внушительный список официальных плагинов. GitHub репозиторий подозрительно чист — мало открытых issues, регулярные релизы, активные мейнтейнеры. Все это создает иллюзию зрелого продукта.
Реальность раскрывается постепенно. Официальные плагины покрывают только базовый функционал: камера, storage, push-уведомления. Для всего остального приходится писать нативный код.
Issues объясняется просто: мало кто использует CapacitorJS в продакшене без всей экосистемы Ionic, а те, кто использует, решают проблемы костылями и не документируют их. Красивый лендинг продает мечту о кроссплатформенной разработке, но умалчивает, что CapacitorJS — всего лишь тонкая обертка над WebView.
CapacitorJS позиционируется, как замена Cordova и мост между веб и нативом. На деле это просто WebView с минимальной обвязкой. Для bounce-эффекта на iOS приходится лезть в нативный код: self.bridge?.webView?.scrollView .bounces = true. Поддержка safe-areas требует CSS-костылей и молитв о совместимости со всеми устройствами.
WebView из коробки не готов к продакшену. Каждая платформа требует своих настроек, причем часть из них недокументирована. Разработка веб-приложения превращается в постоянное переключение между Xcode и Android Studio для починки платформенных особенностей.
Команда npx cap add ios создает иллюзию простоты. На самом деле, из коробки создается только один Target в Xcode, а для разработки нужен отдельный, и начинается ручная синхронизация настроек между ними.
CapacitorJS умеет самостоятельно управлять permissions приложения, но в его плагинах Info.plist придется править руками. Ionic-расширение для VSCode генерирует иконки, создавая лишние файлы больших размеров, когда для иконок нужно всего лишь пять штук. Плюс абсурдно использовать Ionic-утилиты, когда в проекте только ядро. В итоге все равно приходится загружать ресурсы прямо в Xcode, как и в обычной нативной разработке.
Простой таб-бар внизу экрана превратился в 1500 строк Swift-кода. CapacitorJS живет в UIKit, а современная iOS-разработка — это SwiftUI. Интеграция требует UIHostingController, ручного управления constraints и надежды, что следующая версия iOS не сломает эту конструкцию.
Полноэкранный Splash Screen с edge-to-edge дизайном превращается в головоломку. Черные полосы под Status Bar, прыгающий контент при появлении клавиатуры, несовместимость с системными жестами. Финальное решение — черный div под iOS status bar и молитва, чтобы Apple не изменила высоту статус-бара в следующем iPhone.
Экосистема плагинов CapacitorJS — археологические раскопки. Большинство плагинов сообщества либо заброшены, либо обещают «iOS support is coming soon» (спойлер: не coming). В описаниях не указано, какие платформы поддерживаются — это выясняется после установки, когда приходит «Plugin isn’t implemented on iOS».
Официальные плагины создают иллюзию надежности, но одна и та же настройка ведет себя по-разному на iOS и Android. Документация умалчивает, что большинство опций работает только на Android. С базовым функционалом все в порядке, но любая кастомизация требует костылей или форка. Проще написать свой плагин с нуля, чем починить чужой.
На бумаге API для плагинов выглядит элегантно: отправил сообщение, получил ответ. На практике это простой и неповоротливый event bus, где любая асинхронность превращается в callback hell. Документация показывает примеры в стиле «Hello, World!», но реальная логика не вписывается в эту модель.
Race conditions становятся нормой жизни. Запрос отправлен из JS, нативный код выполняется, пользователь переходит на другой экран — callback потерян. Дебаг состоит из расстановки console.log на стороне JS и нативных логов на стороне Swift, после чего начинается сопоставление временных меток в попытке понять место сбоя.
Каждая платформа живет по своим законам. Например, Android уменьшает WebView при появлении клавиатуры, ломая верстку с 100svh. Исправление в четыре этапа: отключить ресайз WebView, добавить слушатель клавиатуры через Capacitor API, управлять padding вручную, фиксить сломанный скролл к инпуту.
В iOS для работы с видео-плеером плагинов нет и приходится писать код на AVFoundation для HLS-стриминга, создавать нативный плеер на AVKit для оффлайн-просмотра, настраивать флаги в Xcode для picture-in-picture и фонового воспроизведения. Это решение невозможно перенести на Android — там совершенно другая архитектура. CapacitorJS обещает кроссплатформенность, но получается два разных приложения с общим HTML-интерфейсом.
Safari Web Inspector для iOS работает только на Mac, Chrome DevTools для Android теряет breakpoints после reload. Нативная часть дебажится отдельно в Xcode или Android Studio, и связать два контекста практически невозможно.
Проблемы, которые не возникают в симуляторе, но происходят на реальных устройствах, превращаются в детектив. Симулятор работает идеально, iPhone крашится. Логи доступны только при подключении кабелем к Mac. Главный инструмент дебага — alert() и надежда на лучшее.
После всех кругов ада, тонн нативного кода и исправленных багов остается последнее испытание — App Store. Эксперты пугали, что приложения на веб-технологиях могут вообще не пропустить в AppStore, мол ими слишком часто злоупотребляют нелегальные онлайн-казино. Мы с такими сложностями не столкнулись.
Единственная наша трудность с Apple — очень строгая политика в отношении приема платежей не через AppStore (без IAP). Формулировки гайдлайнов Apple написаны так, что их можно трактовать как угодно.
Guideline 3.1.3(a) обещает возможность сделать приложение без In-App Purchases, только для бесплатного контента. На практике это квест с секретными правилами, которые раскрываются методом проб и ошибок. Более 20 ревью, десятки отклонений за «недостаточно reader app», хотя из приложения вырезали больше половины. Мало убрать IAP — нужна правильная авторизация, отключенная регистрация, магические фразы в описании.
В результате долгой переписки с командой ревью Apple, мы назначили созвон и очень приятный инженер из Apple на простом и понятном русском объяснил, что делать можно, а что — нельзя. Мы реализовали его «рекомендации» и приложение сразу пропустили.
Есть деньги и время — лучше всего натива.
Хочется сэкономить — используем react native. Ну, или flutter, if that's your thing.
Но если у вас есть готовый нормальный сайт и крутой программист, то за пару дней можно собрать приложение на CapacitorJS.
Ради бога, не пытайтесь притащить нативную навигацию и прочие нативные компоненты в WebView-приложение — это боль. Конкретно у нас разработчику было интересно, и он затащил это на чистом энтузиазме. Нам повезло, но повторять этот эксперимент не стоит!