Личная поваренная книга SwiftUI-рецептов
Рассказ о том, как видеть все тестовые View при запуске приложения и иметь возможность выбрать, с чем работать.
4К открытий4К показов
Рассказывает Александр, старший iOS-разработчик Noveo
Когда начинаешь исследовать какую-то обширную тему в программировании, количество скачанных проектов и созданных черновиков постепенно превышает все мыслимые и немыслимые пределы. А потом и вовсе всё перемешивается и теряется. Вроде помнил, что ты с этим работал, а где, когда? Я пробовал работать в Playground’ах, но они, на мой взгляд, не такие стабильные, как обычный проект, отваливается подсветка, нет возможности нормально делать Debug. С недавних пор я завел единый проект для исследования SwiftUI, и все небольшие вещи закидываю туда. Это помогает держать все в одном месте, к тому же поиск внутри проекта намного удобней. Хоть SwiftUI и предоставляет Preview для быстрого просмотра View и даже позволяет их отлаживать, все же этого не всегда хватает. Хочется и на устройстве проверить. А если держать все эти View внутри одного проекта, надо при создании новой вьюхи проставлять ее как основную в SceneDelegate
, что довольно быстро начинает утомлять. Как было бы круто, если бы мы могли видеть все наши тестовые View при запуске приложения и имели возможность выбрать, с чем работать. Фантастика, скажете вы? Отнюдь ?
Задачу, думаю, можно решить более чем одним путем. Навскидку — прикрутить Sourcery, но интересно было решить ее без вспомогательных инструментов.
Итак, что из себя представляет View и её Preview:
Как мы видим, предпросмотр для View обеспечивается структурой, которая имплементирует PreviewProvider
. Если кто не знал, можно даже внутри одного файла создавать сколько угодно структур/классов, которые будут имплементировать PreviewProvider
, и все они отобразятся в зоне предпросмотра. Может пригодиться, если захотим разбить наш ContentView_Previews
на несколько с разными настройками (хотя можно это же сделать и внутри одной структуры, имплементирующей PreviewProvider
, но речь не об этом).
Что из себя представляет PreviewProvider
? Это протокол
Главное, что можно извлечь из кода, — это не простой протокол, а PAT: Protocol with Associated Type, что сразу усложняет дело. Я перепробовал много вариантов, как обеспечить нужную функциональность с минимальными усилиями.
Начнем с того, как вообще можно подобные вещи делать в real time? В Objective-C мы могли делать все что угодно с помощью reflection — получать список всех классов, исследовать их свойства. В Swift это все дело сильно ограничили, и Mirror не даст нам всего необходимого. Поэтому пришлось смотреть в сторону objc_getClassList
: это метод из рантайма Obj-C, который позволяет получить список всех классов. К сожалению, такого нет для Swift-структур, поэтому оставалось только обходится тем, что дали.
Разберем решение по частям.
Нормально работать с системным протоколом PreviewProvider
не получится из-за того, что он PAT, поэтом создадим Erase-версию этого протокола:
- Как видно, я стер тип у
Previews
, создав оберткуanyPreviews
, которая будет возвращатьAnyView
. Я не очень люблю такие штуки, потенциальная потеря производительности, но т.к. это не production-код, то на это можно закрыть глаза. name
— свойство, возвращающее имя нашей View, как оно будет отображатьcя в списке; учитывая, что все Preview имеют автоматом генерируемые именаViewName_Previews
, можно_Previews
отрезать.- Я добавил свойство
starred
, т.к. число View будет все увеличиваться, а начиная работать с новым куском кода, хочется увидеть его сверху в списке. Это можно сделать, переопределив у превьюхи для новой View это свойство, возвращаяtrue
.
Сам список выглядит довольно просто.
Все View сортируются по имени и разбиваются на 2 списка, starred и обычные. Выглядеть все будет примерно так:
Ну, и в SceneDelegate
просто меняем основную вьюху:
let contentView = PreviewsList()
Остался последний момент: как же сделать так, чтобы наши Preview попали в этот список.
- поменять
struct
наclass
; - добавить поддержку
PreviewHolder
.
Т.е. вместо
станет
Опционально можно переопределять name
и starred
.
Это решение написано за пару часов, чтобы по-быстрому испытать идею. При желании его можно наворотить по полной, проставляя теги, дату создания для Preview, показывать список не основным, а в Debug-окне, что позволит использовать его даже на боевом проекте (не забываем отключать в Release-сборке). В общем, все зависит от вашей фантазии ?
Скачать проект с базовой реализацией можно здесь: Github
4К открытий4К показов