SwiftUI – это молодая и пока что не совсем изученная технология. С одной стороны, большое пространство для творчества и исследования, а с другой – неизвестность, нестабильность и проблемы. Так ли просто писать на SwiftUI, как показывают на WWDC? Я расскажу о сложностях, с которыми столкнулся лично я во время написания собственного приложения. Оно полностью написано на SwiftUI и выложено в App Store. Какие проблемы могут встретиться во время разработки? Давайте разбираться. Главная и самая большая проблема, которая накладывает ограничения почти на любой архитектурный подход. Мое приложение построено на MVI, поэтому рассматривать буду в рамках этой архитектуры. Подробнее об MVI я писал в публикации MVI и SwiftUI – одно состояние Сначала сделаю небольшое отступление: В MVI есть модуль, который отвечает за бизнес логику, называется Intent. У View на Intent есть ссылка. @ObservableObject нужен для того, чтобы можно было отслеживать все, что происходит в классе и сообщать об этом View, а тот, в свою очередь, опираясь на новые данные, меняет отображение UI элементов. А теперь к проблеме. View в SwiftUI обладает особенностью — когда требуется перерисовать View, он  пересоздается, в таких случаях init вызывается повторно. Как правило, View пересоздается, когда у другой View, которая стоит по иерархии выше, меняются данные. Давайте рассмотрим пример ниже, где одна из View постоянно пересоздается. Если открыть экран NextView, то конструктор у него будет вызываться каждую секунду. В консоли мы увидим:  Каждый раз когда вызывается init, все объекты внутри View пересоздаются и модуль, отвечающий за бизнес логику (в нашем случае Intent), также будет создан заново, все данные в нем сбросятся. В любой момент экран может вернуться к своему первоначальному состоянию, т. е. к состоянию на момент открытия. Чтобы этого не происходило, @ObservedObject можно заменить на @StateObject и тогда Intent перестает зависеть от жизненного цикла View.  Как это работает?  Каждый раз когда View пересоздается, у всех модулей в нем вызывается конструктор (в MVI это Intent и Model, в MVVM это будет ViewModel). После того, как Intent инициализирован, он помещается в контейнер @StateObject; если там уже лежит объект Intent, то новый созданный Intent удаляется. @StateObject очень похож на Singleton, только он удаляется из памяти. Учитывая, что конструктор будет вызываться у Intent постоянно, даже если он будет @StateObject, в init у Intent не стоит писать запросы и любую другую логику кроме инициализации объектов.   @StateObject доступен с версии iOS 14. Допустим, во время инициализации мы будем подкладывать уже готовый объект. Одна проблема закроется, но возникнут другие: При передаче данных в Intent при инициализации будут возникать вопросы (существуют ли сейчас данные, если они уже есть, нужно ли их перезаписывать и т. д.) Singleton мы не можем уничтожить и он будет держаться в памяти всю сессию приложения  При создании второго, третьего и других последующих экранов у нас будет один Singleton на все эти экраны.  SwiftUI в iOS 14 сильно расширен, чтобы не бороться с этими проблемами и получать больше возможностей, есть смысл делать минимальную версии iOS 14 для экранов SwiftUI. В общем, проблема множественного вызова конструктора создает много неудобств и накладывает большие ограничения на архитектуру для iOS 13. Мы не знаем, когда View удален и пользователь его не видит, или наоборот, когда View впервые показан. Есть мнение что, что onAppear и onDisappear вызываются один раз, когда экран создается и когда он удаляется. Это не так. И тот и другой метод может вызваться более одного раза у одного экрана.  Давайте посмотрим пример При открытии и закрытии экрана внутри NavigationView эти методы срабатывают более одного раза. В консоли мы увидим:  Как правило, NavigationView указывается у одного экрана, первого, а у всех последующих уже нет. onAppear и onDisappear будут срабатывать в последующих экранах больше одного раза. Это надо держать в голове при реализации каких-либо архитектурных решений. Как организовать навигацию в приложении со SwiftUI? Этот вопрос стоит особо остро, так как навигация тут работает по другим правилам, и весь накопленный опыт сообщества работы с UIKit тут не работает. Можно найти немало статей и видео, посвященных теме навигации в SwiftUI. Все, что я видел, или неудобно, или громоздко. Я написал свой вариант, чтобы переходы делались ровно так, как нам показывали на WWDC. Это оказалось несложно, всю логику навигации нужно было вынести в отдельный "some View".  Во время разработки приложения этот вариант роутера показал себя очень хорошо, даже лучше, чем MVI. Так как это большая тема, под сполером я оставлю пример, а сам подход, возможно, опишу в другой статье.  Property wrapper (State, Binding, ObservedObject и др.) дают SwiftUI реактивности и делает удобным обновление UI. А если мы хотим вынести эти свойства в отдельный класс. Тогда создается класс, подписывается под протокол ObservableObject и после этого его можно использовать во View А если нужно этот класс со свойствами перенести в другой модуль, другой класс? Тут возникают сложности. Давайте рассмотрим пример с Intent. Наш класс со свойствами будет находиться там, но при этом все изменения свойств должен видеть View. В примере, UI элемент Text не будет получать актуальные данные и пользователь будет видеть надпись "Loaded", даже после того как все функции будут вызваны. Даже если поменять на  Работать не будет. ObservableObject у Model передает событие в Intent о том, что у него что-то изменилось, а не во View.  Для того, чтобы View узнал что в Model что-то поменялось, событие нужно передать из Intent дальше. Чтобы заработало, нам нужно написать дополнительный код в конструкторе Intent и сам класс подписать под протокол ObservableObject.  Когда у Model что-то меняется, вызывается метод objectWillChange, который извещает Intent о том, что в Model есть изменения. Intent этот ивент получает и вызывает у себя метод objectWillChange.send(), передавая View изменения. Протокол ObservableObject у Intent нужен для того,чтобы можно было вызывать objectWillChange. Резимирую. Если захочется вынести свойства UI  в отдельный класс и держать его не во View, нужно будет проксировать события. Не все, что можно сделать в UIKit, можно сделать в SwiftUI. Многих системных элементов в SwiftUI просто нет. В таких случаях приходится изобретать велосипед. А то, что есть, плохо кастомизируется. Вот несколько примеров проблем с UI элементами: Готового аналога UISearchBar в SwiftUI нет, придется писать логику поведения с нуля, если понадобится поисковая строка элемента. Когда нужен UIPageControll. В SwiftUI что-то подобное можно сделать из TabBar, но кастомизировать его не получится, придется написать свой UIPageControll. PikerView: нельзя уменьшить высоту элемента, он будет выходить за границы. На рисунке ниже, черной рамкой показан заданный размер PikerView. Видно, что размер элемента меньше стандартного, а фактически визуально с элементом ничего не происходит. TextEditor: нельзя поменять background, только белый.   И это далеко не все! Не все элементы можно изменить под свои нужды. Таких мелочей встречается очень много. Приходится или мириться, или писать свой элемент, который по поведению похож на системный. Так как системные элементы плохо поддаются кастомизации, любой шаг в дизайне не по гайдлайнам Apple будет приносить множество проблем.  Можно ли коммерческий проект полностью переводить на SwiftUI?  Точно нет. Будут проблемы с реализацией дизайнов, будут возникать архитектурные сложности. Так как технология свежая, придется много исследовать и сталкиваться с проблемами. С другой стороны, скорость разработки UI на SwiftUI в разы выше, чем UIKit, и открываются широкие возможности работы с анимациями – в SwiftUI очень красивые анимации и делать их очень просто.  Частично перевести на SwiftUI можно простые экраны, экраны категории Welcome или информационные. Здесь SwiftUI показывает себя хорошо, проблемы минимальны, а визуально с анимациями выглядит лучше, чем UIKit.  Также рекомендую попробовать на своих личных проектах, не очень больших и сложных, где нет жестких требований к дизайну.  iOS Developer