Привет! Меня зовут Александр Соломатов. Я iOS Chapter Lead в СберМаркете, моя команда занимается внедрением продуктовых фич в мобильное приложение. С 2020 года мы живём в продакшене со SwiftUI и я хочу поделиться опытом съеденных кактусов при использовании этого фреймворка. Эта статья для тех, кто размышляет над переходом с UIKit на SwiftUI, но не может решить, нужно ему это или нет. Не буду убеждать всё переписать под чистую, но подсвечу плюсы и минусы. Поехали!

Как всё начиналось

Долгое время у нас существовало MVP-приложение на Xamarin. В определённый момент, как это бывает во многих компаниях, появилась необходимость разделить приложение на две платформы и перейти на натив:

  • теперь нет необходимости искать какие-либо кроссплатформенные решения для реализации сложного функционала;

  • это позволяет избежать возникновения багов, которые могут прилететь из сторонних библиотек;

  • есть возможность грамотно продумать архитектуру приложения для будущего тестирования и масштабирования.

На WWDС19 Apple представила фреймворк SwiftUI, который позволял перейти к декларативному стилю написания кода, что позволяло разработчику не терять много времени на написание UI-составляющей своих приложений. Было понятно, что у Apple большие планы на него и про UIKit можно начинать забывать. Спойлер: на деле оказалось, что мы не можем полностью отказаться от UIKit. Во всяком случае пока что.

В 2020 году, взвесив все за и против, было принято решение пойти в прод со SwiftUI. Это повлекло за собой поднятие minimumDeploymentTarget до 13.0 — первая SwiftUI начинала работать с этой версии. С этим проблем не было, так как пользователи с iOS до 13-й версии продолжили бы пользоваться приложением на Xamarin. AppStore позволяет использовать обе версии приложения пока, рано или поздно, все пользователи не обновят операционную систему и приложение.

В ходе разработки нам пришлось поднять iOS до 14-й версии, так как SwiftUI 2.0, который был представлен в этом же году решил часть детских проблем первой версии. Переход мы сделали незадолго до релиза в AppStore в апреле 2021 года.

Сложности внедрения SwiftUI

Со старта реализации приложения мы столкнулись с несколькими нюансами SwiftUI, из-за которых нам не удалось полностью уйти от UIKit. Хочется подробнее остановиться на них и рассказать о решениях, к которым пришли.

Паттерны

Мы пришли к использованию сразу двух паттернов: VIPER+MVVM.

MVVM нужен для SwitfUI, причём сама Apple продвигает его использование, т.к. он отлично подходит для связки реактивного функционала фреймворка Combine и view в SUI.

VIPER мы используем для модулей и для дальнейшего масштабирования.  Он удовлетворяет наши потребности в плане командной разработки и тестирования.

Связаны два этих подхода при помощи ViewState, в который передаются модели и паблишатся(publish) события. У нас между Presenter и View, в VIPER-модуле существует объект ViewState, который наследуется от ObservableObject, это уже часть Combine. И этот объект ViewState используется в нашей View. Соответственно, когда мы изменяем какой-то параметр в этой модели, то View перерисовывает ту часть, в которой этот параметр используется. А Presenter передаёт во ViewState напрямую изменения модели, то есть меняет параметры. 

Навигация

Мы реализовали навигацию на UIKit. То, что предлагал SwiftUI нас не устраивало. NavigationLink был очень деревянным, поэтому выбор пал в сторону функционала UIKit, связанного с представлением контроллеров, плюс координаторы. 

Navigation Bar

Мы создали свой NavigationBar в нескольких вариациях на SUI. Возможно вы знаете, что реализация NavigationBar в UIKit слишком простая и с ней практически ничего нельзя сделать, максимум перекрасить бекграунд и добавить пару кнопочек. Здесь SwiftUI даёт полную свободу и мы можем нарисовать практически всё, что захочется. То есть наш NavigationBar будет являться частью View и не будет отдельным компонентом. 

Да, мы понимали, что у нас не будет каких-то анимаций между экранами при переходе, но на деле это оказалось не так страшно. Сейчас приложение выглядит довольно приятно с тем NavigationBar, который мы собрали.

Поля ввода и Keyboard Avoiding

Поля ввода и Keyboard Avoiding мы сделали на UIKit. SwiftUI не предлагает (по крайней мере не предлагал на момент выхода SUI 2.0) практически ничего для работы с полями. Сами поля есть, но отслеживать firstResponder со SwiftUI невозможно. Поэтому пришлось под капотом пользоваться реализацией UIKit и привести поля к тому виду, с которым может работать SwiftUI. Только к третьей версии SwiftUI начал появляться функционал, чтобы в перспективе полноценно перейти с  одного фреймворка на другой.

О проблеме избегания клавиатуры мой коллега Валерий Скворцов подготовил полноценный доклад. Если коротко, то мы пришли к решению, где под капотом сочетаются SwiftUI и UIKit.

PropertyWrappers

Также мы испытали много проблем с неправильным использованием PropertyWrappers, такие как @State, @Binding, @ObservedObject, @Published. Все эти проблемы, мы задокументировали для команды.

Как пример:

  • @ObservedObject: Создавать внутри самой View нельзя. Это приведет к созданию нового экземпляра класса на каждый init View.

  • @StateObject: Если View внутри NavigationLink, то @StateObject проинициализируется при заходе на экран, а не при создании View для NavigationLink.

  • @Published: View реагирует на изменение @Published переменных у ObservableObject, если они удерживаются самой View. Изменения вложенных ObservableObject не отслеживаются. Это происходит потому, что изменение @Published дергает паблишер objectWillChange, на который реагирует View.

Сейчас все новички, которые к нам приходят, обязательно должны прочитать документацию, чтобы не наделать таких же ошибок.

К чему мы пришли

Мы поняли, что SwiftUI не может полностью заменить UIKit. Фреймворки сильно отличаются друг от друга. У каждого есть свои плюсы и минусы. 

SUI:

+ Декларативный подход к написанию UI. Очень удобно и быстро пишется.

+ Тесная связь с Combine.

— Пока ещё сырой.

UIKit:

+ Привычен и стабилен.

+ Даёт больше свободы действий над стандартными (из коробки) UI элементами.

— Сложные UI элементы порой пишутся с болью.

SwiftUI пока что молод и зелен, но он активно развивается и к третьей версии им уже можно пользоваться с комфортом. С его помощью можно действительно быстро верстать вьюшки, но некоторые моменты не совсем очевидные и не всегда всё работает так, как надо. 

Как пример: есть компоненты типа VStack и LazyVStack. Мы потратили не мало времени, чтобы разобраться в каком случае что лучше использовать. И если засунуть VStack в LazyVStack можно, то наоборот уже начинаются проблемы с отрисовкой больших списков и непредсказуемое поведение.

Также мы обновили minimumDeploymentTarget до версии 14.5. Благодаря этому мы смогли избавиться от некоторых неприятных крашей, которые были связаны со ScrollView. Например при использовании метода .scrollTo() в ScrollViewReader.

Стоит ли переходить на SwiftUI сейчас?

Если вы собираетесь писать новый проект, то я рекомендую этот фреймворк.

Если это старый проект, то можно переписать некоторые компоненты на UIKit, вынося их, для начала, в отдельные ViewController’ы, и добавляя с помощью addSubview в родительский VC, а после уже полностью переписывать на SUI и оборачивать в UIHostingController. А далее уже собирать паззл из нескольких маленьких View в одну побольше.

Сам по себе SwiftUI не страшен, но есть ещё неизведанные территории и непонятные, но, к счастью, редкие баги. И в целом даже с теми минусами, которые есть сейчас, можно справиться, если внимательно читать документацию от Apple. А по скорости разработки SwiftUI по большей части обходит UIKit, именно написанием UI компонентов, хотя сначала нужно набить руку и познакомиться с фреймворком. 

Tech-команда СберМаркета завела соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и YouTube.

Комментарии (3)


  1. themen2
    17.11.2022 20:11

    Спасибо за обзор


  1. AdnreiZubr
    17.11.2022 21:04
    +1

    спасибо за интересную статью!


  1. tercteberc
    17.11.2022 21:04

    А как же проблемы с производительностью у SUI List-ов (с разными ячейками)? ????