Все началось с того, что наша команда, отвечающая за поиск в SSENSE, захотела сделать автозаполнение в реальном времени и предложения по мере ввода пользователем своего поискового запроса. Динамический характер этого предполагаемого интерфейса, наряду с возможным использованием огромного количества повторно используемых компонентов, побудил нас попробовать использовать SwiftUI в существующем приложении UIKit. В этой статье излагаются некоторые важные выводы из опыта, такие как взаимодействие с UIKit, взаимодействие архитектуры MVVM SwiftUI с MVC и раннего характера технологии SwiftUI.
SwiftUI
SwiftUI — это фреймворк, использующий декларативный синтаксис, аналогичный React и Flutter, для быстрого создания повторно используемых компонентов пользовательского интерфейса на всех платформах Apple. Это огромный шаг вперед для разработки под iOS по сравнению с UIKit, который призван значительно ускорить время разработки. Загвоздка в том, что он доступен только с iOS 13 и выше.
В связи с тем, что количество пользователей iOS 12 в нашем приложении быстро сокращается, отказ от iOS 12 уже начал для нас маячить на горизонте, чтобы можно было использовать SwiftUI с iOS 13. Время было подходящим, чтобы начать интеграцию SwiftUI в нашу кодовую базу. Если эксперимент пройдет успешно, мы сможем продолжить использование SwiftUI в нашем приложении в долгосрочной перспективе.
Хотя SwiftUI гораздо лучше работает в iOS 14, нам не потребовались какие-либо новые функции SwiftUI из iOS 14 для нового поиска (такие как Grid и TextEditor). Несмотря на сложную природу экрана поиска в целом, сами Subview представляли собой простые списки, поэтому мы были уверены, что сможем выполнить работу с минимальным развертыванием в iOS 13.
Интеграция
Переход к SwiftUI также был сделан для принятия шаблона проектирования MVVM (Model-View-ViewModel) вместо шаблона проектирования MVC, который использовался в то время. Хотя мы не могли просто перепроектировать все наше приложение для MVVM, мы все же смогли интегрировать новый поиск в существующее приложение.
В приведенном ниже примере показано, как мы обернули наш новый SwiftUI SearchView в UIHostingController, чтобы предоставить готовый к использованию UIViewController в нашем приложении с MVC и UIKit:
У этого подхода также было скрытое преимущество, заключающееся в возможности избежать использования SwiftUI для навигации, что является одним из самых слабых мест SwiftUI, особенно когда речь идет о кастомных панелях навигации. В результате мы смогли продолжить использование собственных панелей навигации без дополнительных усилий, несмотря на то, что все содержащиеся представления были на 100% чистым SwiftUI.
Важным этапом внедрения новой технологии является документирование ваших результатов и способов их использования. Мы с самого начала хотели быть в курсе того, как мы будем использовать SwiftUI. Будь то локализованные строки или соглашение об именах замыканий для действий кнопок, поддержание документации по мере продвижения вперед было нашим приоритетом. Мы также решили включить в документацию набор рекомендуемых ресурсов, поскольку большая часть команды была новичком в SwiftUI.
Тестирование
Сначала мы боялись, что тестирование будет трудным, но библиотека тестирования снепшотов, которую мы уже использовали, отлично с этим справилась. Во всяком случае, наши тесты снепшотов стали еще лучше. Прелесть SwiftUI заключается в возможности очень легко создавать модульные и повторно используемые компоненты представлений, что значительно упростило нам написание тестов:
Функция toViewController() — это расширение, которое мы создали для протокола View из SwiftUI, которое возвращает обернутое представление SwiftUI в UIViewController, чтобы его можно было использовать в тестах снепшотов. Поскольку наши View сами по себе не принимают никаких зависимостей, эта ответственность теперь ложится на модели представлений, и мы смогли написать гораздо больше кратких и модульных тестов. Прелесть поддержки MVVM-архитектуры с помощью SwiftUI заключается в том, что мы также можем независимо тестировать функциональность наших View моделей. Эти тесты View моделей также стали намного более краткими и модульными, чем многие из наших тестов функциональных View контроллеррв, которые у нас есть в настоящее время.
Тестирование функций View представляло дополнительную проблему, поскольку их нельзя было вызвать извне. Однако на самом деле это было воспринято как положительный момент, так как это побудило нас изолировать функциональность в модели представления, а не в самом представлении.
Ловушки
Конечно, использование недавно выпущенного фреймворка не лишено подводных камней, и мы определенно столкнулись с некоторыми из них.
Хотя я упоминал ранее, что наличие минимальной цели развертывания в виде iOS 13, а не iOS 14, не было проблемой для движения вперед, в ранней версии фреймворка все еще были минорные недостатки, которые нам пришлось обойти. Во-первых, в представлении Text не было модификатора для корпуса текста в iOS 13, что было проблематично при использовании LocalizedStringKey. Чтобы обойти это, нам нужно было заранее решить, какой будет буквенный корпус в наших необработанных строках, и обработать их соответствующим образом.
Еще одним примером отсутствия функциональности в итерации SwiftUI для iOS 13 является возможность программно установить представление TextField в качестве первого респондента. Представление TextField в SwiftUI на самом деле ограничено во многих отношениях, пока вы не достигнете цели развертывания iOS 15 — мы даже не смогли установить тип возврата клавиатуры. К счастью, SwiftUI достаточно гибок, чтобы позволить нам при необходимости перейти к UIKit, используя UIViewRepresentable:
В некотором смысле мы можем рассматривать UIViewRepresentable как противоположность UIHostingController. Хотя переход к UIKit для основного компонента не был идеальным, он все же позволил нам использовать SwiftUI на более высоком уровне. Не имело значения, был ли этот подкомпонент построен с использованием SwiftUI или UIKit под капотом, поскольку мы абстрагировались от этого при создании компонентов более высокого уровня:
Двигаемся вперед
Хотя изначально мы столкнулись с некоторыми трудностями при переходе на SwiftUI, все они имели разумные решения. Даже с этими ловушками и обходными путями мы смогли невероятно быстро разработать эту новую функцию поиска благодаря декларативному характеру SwiftUI. Можно с уверенностью сказать, что наш эксперимент со SwiftUI оказался успешным, и теперь мы все очень хотим использовать SwiftUI в нашем приложении в будущем.
Что делает вещи еще более захватывающими, так это то, что iOS 14 поддерживает все те же устройства, что и iOS 13, поэтому мы с оптимизмом надеемся, что сможем отказаться от поддержки iOS 13 раньше, чем от iOS 12. Это не только означает возможность отказаться от некоторых обходных путей, которые нам пришлось использовать, но также откроет возможность создавать гораздо более сложные представления с помощью SwiftUI в дальнейшем, благодаря новым View, сделанным в iOS 14, таким как Grid.
При этом мы сейчас находимся на перепутье с точки зрения решения, как мы собираемся развивать использование SwiftUI с точки зрения архитектуры. Мы придерживаемся MVVM? Будем ли мы развивать это и двигаться в направлении MVVM-C? В качестве альтернативы, воспользуемся ли мы этим как возможностью заняться чем-то новым, например Composable? Это то, что мы все еще пытаемся понять, так как начали экспериментировать с Composable. В любом случае, команда невероятно рада начать это новое путешествие с использованием SwiftUI.