В этой статье я расскажу про то, как работает создание интерфейса в SwiftUI при помощи ViewBuilder
В этой статье вы прочитаете:
- Почему у ViewBuilder такой формат
- Как создать свой контейнер для View
- 3 способа преобразовать результат ViewBuilder в массив
Примеры изображений и кода из этой статьи доступны на GitHub
Почему у ViewBuilder такой формат
Если посмотреть на протокол View, то видно, что body - это переменная, которая возвращает объект, реализующий протокол View, который должен в итоге получить конкретный тип. И следующий код будет прекрасно работать. Следующий код будет работать
Но Apple стремится упростить синтаксис для SwiftUI, поэтому у body есть два префикса - ViewBuilder и MainActor.
ViewBuilder - это обертка вокруг блока, который создает View. Он дает возможность вернуть несколько View, описывая их подряд, по строчкам, не используя дополнительные артефакты в виде массивов.
На выходе создается объект типа AnyView<TupleView<(...)>>. Т.е. следующие два блока кода эквивалентны
Как создать свой контейнер для View
Для того, чтобы создать вертикальный стек с общим стилем элементов, который можно переиспользовать(с фоном, границей, тенью) можно создать свой контейнер и применить стили ко всем внутренним элементам(Включая Spacer())
Для этого необходимо объявить Generic структуру, в которой параметром шаблона будет Content типа View. Необходимо объявить свойство content с типом ViewBuilder, которое будет блоком-фабрикой.
А применить стиль можно с помощью обертки этого content в необходимый существующий контейнер и в Group - специальную конструкцию, которая применяет все свои модификаторы к тому, что находится внутри
Также Group можно использовать, чтобы применить какой-то стиль к View, созданному по условию
ViewExtractor и как преобразовать содержимое ViewBuilder в массив
Увы, способ выше не позволяет каким-то образом изменить порядок дочерних элементов, добавить между ними какие-то объекты или использовать какое-то форматирование, зависящее от количества элементов.
Первый путь
Библиотека ViewExtractor. Она дает достаточно простой синтаксис
В ней используется тайное знание о том, как устроен SwiftUI, _VariadicView.Tree хранит к себе элементы из ViewBuilder. Но это использование приватного API и Apple может заблокировать такое приложение или оно может сломаться после обновления операционной ситсемы.
Второй путь
Манипуляции c памятью в расширении над ExtensionView. Суть в том, чтобы взять из TupleView дочерние объекты и сказать, что эта область памяти - массив.
Но Mirror работает не очень быстро, т.к. отключает часть оптимизаций на объекте, чтобы добраться до его элементов. А withUnsafeBytes может обрушить приложение, если в будущей версии iOS изменится структура хранения в памяти.
Из-за того, что ViewBuilder все оборачивает в AnyView, content[index] не сможет использоваться для сравнения типа, но index и количество объектов позволяют добавить разделители, если необходимо.
Третий путь
Можно создать контейнер, который будет принимать на вход массив элементов определенного протокола.
Плюсы
- Можно сортировать и фильтровать объекты
- Сохраняется информация о типах и можно изменять отдельные параметры
- Доступен индекс объекта и их количество
Минусы
- Синтаксис требует квадратных скобок и запятых
- Каждый класс должен соответствовать протоколу через extension
- Иногда ломается компилятор и выдает не ту ошибку, которая случилась
Придется отказаться от синтаксиса ViewBuilder, но такой способ не отвалится внезапно от обновления iOS и даст возможность сортировать и фильтровать объекты, а также обращаться к их типам.
Заключение
В этой статье разобрана суть ViewBuilder, почему он такой, как создать свой контейнер и как превратить вывод ViewBuilder в массив тремя способами.
Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.
Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.