Доклад: SwiftUI In A Nutshell / Тёма Пстыго (Авито)

Доклад: SwiftUI In A Nutshell / Тёма Пстыго (Авито)

Введение в Swift UI

Приветствие и представление темы

  • Всем привет, меня зовут Миша. Мы начинаем пятнадцатый сезон Подлодки AOSCW, посвященный Swift UI.
  • Прошло много времени с момента последнего сезона по Swift UI, фреймворк активно развивается и появляются новые таргеты.
  • В этом сезоне мы сосредоточимся на практическом использовании Swift UI в продакшене, делясь опытом спикеров.

Представление участников

  • Михаил Харитончик представляет себя и Александра Андрюхина, который также участвует в обсуждении.
  • Тёма Пстыга из компании Авито расскажет о своем опыте работы с Swift UI и его развитии.

Основные концепции Swift UI

Декларативный подход

  • Обсуждение базовых концепций Swift UI: как перестроить мышление от императивного к декларативному подходу.
  • В отличие от UIKit, где акцент на событиях (тапы, перемещения), в Swift UI мы думаем о состоянии интерфейса как функции от состояния модели.

Сравнение с теорией графов

  • В UIKit мы рассматриваем переходы между состояниями (рёбра графа), тогда как в Swift UI акцент на самих состояниях (вершины графа).
  • Это упрощает логику разработки: вместо n² возможных переходов у нас n состояний.

Практические примеры использования

Пример реализации интерфейса

  • В UIKit необходимо вручную пересчитывать layout; в Swift UI мы просто объявляем желаемый результат.
  • Декларативный стиль позволяет избежать ручной настройки фреймов и анимаций — это делает разработку более интуитивной.

Двусторонняя привязка данных

  • Пример с текстовым полем: данные движутся как от пользователя к модели, так и обратно.
  • В UIKit требуется явное управление механизмами передачи данных; в Swift UI это происходит автоматически через привязку состояния.

Как работает двусторонний бандинг в SwiftUI?

Декларативный подход и бандинг

  • В SwiftUI используется декларативный подход, который включает механизм бандинга — двусторонний канал связи между моделью и пользовательским интерфейсом (UI).
  • Бандинг позволяет автоматически обновлять UI при изменении данных в модели и наоборот, что исключает необходимость в дополнительных механизмах синхронизации.

Концепция Identity в SwiftUI

  • Обсуждается концепция Identity, которая помогает понять, как SwiftUI управляет сложными представлениями.
  • Пример с логотипом подлодки демонстрирует, как изменение состояния (offset) влияет на отрисовку UI.

Анимации и перерисовка

  • При изменении значения offset происходит повторная отрисовка body view, что приводит к перемещению логотипа по экрану.
  • Использование модификатора анимации позволяет сделать переходы более плавными, хотя механизм анимации может быть неочевиден.

Иерархия объектов и их идентификация

  • Каждый объект в UI имеет уникальный адрес памяти (Object Identifier), что дает ему жизненный цикл.
  • Структуры не имеют конкретного адреса памяти; вместо этого SwiftUI использует типы представлений для идентификации объектов во время компиляции.

Непрозрачные типы и их использование

  • Непрозрачные типы (opaque types), используемые в SwiftUI, скрывают конкретные реализации типов от разработчика.
  • На примере простого вертикального стека показано, как компилятор обрабатывает различные уровни вложенности представлений.

Генерация типов во время компиляции

  • Компилятор фиксирует всю иерархию представлений на этапе компиляции, создавая структуру для последующей отрисовки.
  • Применение различных модификаторов к элементам управления меняет их родительские структуры и генерирует новые типы.

Заключение о структуре представлений

  • В результате применения различных модификаторов формируется сложная структура представлений с четко определенной иерархией.

Структура и анимация в SwiftUI

Понимание фреймов и их свойств

  • Фреймы в SwiftUI имеют фиксированные свойства, такие как alignment, которые задаются на этапе компиляции. Изменение этих свойств не влияет на структуру фрейма.
  • При изменении alignment структура остается неизменной, что означает, что идентичность элементов также сохраняется.

Работа со списками в SwiftUI

  • В некоторых случаях необходимо предоставить дополнительную информацию о списках элементам, так как их размер и содержание неизвестны на этапе компиляции.
  • Пример с использованием списка: первый элемент может быть статичным (например, заголовком), для которого не требуется указывать идентификатор.

Идентификация элементов списка

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

Проблемы с анимацией при использовании self как ID

  • Если использовать self как ID, происходит полный релоад коллекции вместо ожидаемой анимации перемещения элементов.
  • Это может вызвать нежелательные эффекты, если применяются модификаторы API или события жизненного цикла.

Рекомендации по использованию уникальных идентификаторов

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

Анимация переходов между состояниями

  • Пример использования анимаций для переключения между состояниями (например, солнце и луна). Транзишены позволяют создать плавные визуальные эффекты при изменении состояния.

Проблемы с применением transition в SwiftUI

Понимание работы transition

  • Транзиция не применяется к вьюхам, которые уже находятся на экране, так как они не покидают иерархию. Применение транзиции происходит только при входе или выходе вьюхи из иерархии.
  • Если явно указать модификатор ID для разных состояний, это позволит применять транзицию, даже если визуально текст остается тем же.

Лейаут и его особенности

  • Обсуждение лейаута начинается с примера приложения для подлодки. Дизайнер требует добавить паддинг к логотипу на сплешскрине.
  • В SwiftUI есть встроенный паддинг, но разработчик решает создать кастомный паддинг для лучшего понимания работы лейаута.

Реализация кастомного паддинга

  • Для реализации кастомного паддинга необходимо использовать протокол layout и реализовать два метода: sizeThatFits и placeSubviews.
  • Метод sizeThatFits должен возвращать размер лейаута с учетом предложенных ограничений по размеру и сабвьюхам внутри него.

Логика работы методов

  • В методе sizeThatFits используется guard для обработки только первой сабвьюхи. Это упрощает применение паддинга.
  • Предложенный размер уменьшается на величину инсетов (паддинга), чтобы правильно разместить сабвьюху внутри лейаута.

Работа с proposed view size

  • Метод adjustedProposal проверяет наличие ширины и высоты у предложенного размера. Если они отсутствуют, система позволяет занять пространство по желанию.
  • Метод placeSubviews использует скорректированный размер для финальной отрисовки вьюшек, обеспечивая правильное размещение элементов внутри контейнера.

Кооперативность лейаута и работа с Geometry Reader

Основные концепции кооперативности лейаута

  • Важно понимать, что мы не можем задать положение и размер сабвьюхи напрямую. Мы можем лишь указать её положение через метод place at, а размер будет определяться самой сабвьюхой на основе переданного пропозла.
  • Кооперативность проявляется в том, что супервьюха может предложить параметры, но окончательное решение о размере принимает сама сабвьюха.
  • При отладке возникла проблема с расположением логотипа: он сместился вверх экрана. Это было связано с неправильным пониманием координатной системы.
  • Оказалось, что Bounс — это прямоугольник в пространстве родителя, и его Origin не всегда равен нулю. Это важно учитывать при работе с позиционированием элементов.
  • Для решения проблемы необходимо скорректировать Origin у Bounce, используя точку Inset Leading Inset Stop.

Использование Geometry Reader

  • Geometry Reader позволяет определять контент как функцию от своего размера, что полезно для сложных математических расчетов при создании лейаута.
  • Он удобен для случаев, когда размеры элементов строго ограничены по высоте или ширине. Например, если дизайнер задаёт фиксированную высоту ячеек.
  • Geometry Reader можно использовать как оверлей или фон для других вьюх; он занимает пространство в соответствии с размерами применяемой вьюхи.
  • В редких случаях Geometry Reader может занять всё оставшееся пространство после фиксированных элементов (например, заголовка).
  • Не рекомендуется использовать Geometry Reader, когда размер всей вьюхи зависит от контента внутри него; это может привести к проблемам с вычислением размеров.

Проблемы и ограничения использования Geometry Reader

  • Если текст должен занимать половину доступного пространства, использование Geometry Reader может быть затруднительным из-за нелинейного поведения текста по ширине и высоте.
  • Текст будет изменять свои размеры: уменьшение ширины приведет к увеличению высоты и наоборот. Это создает проблему "курицы и яйца".
  • Невозможно сделать так, чтобы размер текста определял размер Geometry Reader одновременно; это требует тщательного планирования структуры интерфейса.
  • Важно учитывать эти ограничения при проектировании интерфейсов для обеспечения корректного отображения контента без неожиданных изменений размеров.

Конфигурация вьюх в Swift

Основные способы конфигурирования

  • В UIKit существует три основных способа конфигурировать вьюхи: аргументы конструктора, мутабельные свойства и паттерн делегата для сложного поведения.
  • Аргументы конструктора используются для фиксированных параметров, таких как текст или цвет текста, а также для изменяемых свойств.

Конструкторы в Swift

  • В Swift конструкторы занимают более важное место по сравнению с UIKit. Первый способ — это использование конструкторов для типизации поведения компонентов.
  • Примером может служить компонент на Авито, который имеет различные режимы выбора элементов (выбор нескольких, одного или ноль).

Типизация поведения через конструкторы

  • Выбор конструктора определяет поведение всей вьюхи. Например, если используется конструктор для выбора одного элемента, то состояние "выбран несколько" невозможно.
  • Это позволяет избежать создания дополнительных полей состояния и упрощает логику работы с компонентами.

Разделение непересекающегося поведения

  • Второй пример использования конструкторов — разделение непересекающегося поведения. Например, circular progress bar имеет четыре режима отображения.
  • Каждый режим реализован через отдельный конструктор с уникальной сигнатурой, что документирует юзкейсы компонента.

Гибкая кастомизация компонентов

  • Конструкторы могут использоваться как инструмент гибкой кастомизации компонентов. Это позволяет избежать необходимости расширять существующие компоненты при изменении дизайна.
  • Примером является стандартный компонент toggle с двумя основными конструкторами: один принимает строку, другой — абстрактный view builder.

Модификация через окружение

  • Вторая группа модификаций — это конфигурация через окружение (environment). Это словарь параметров дерева вьюх.
  • Изменения в корне поддерева передаются всем дочерним элементам по наследству, что удобно для массового изменения параметров группы вьюх.

Применение Environment в SwiftUI

Примеры использования

  • Обсуждение необходимости изменения компонентов и примеры их применения, включая адаптацию вьюшки с солнышком и луной к цветовой схеме приложения (тёмная/светлая тема).
  • В нижней части структуры используется одна и та же вьюха, где переопределён environment, что влияет на все дочерние элементы.

Передача стилей через Environment

  • Применение модификатора button style к конкретной кнопке, который передаётся всем дочерним элементам через родительский View.
  • Упрощение работы с состоянием загрузки для трёх компонентов без добавления аргументов во все конструкторы. Использование environment для переиспользуемого поведения.

Интуитивность использования

  • Модификатор устанавливает состояние загрузки в environment, что позволяет интуитивно понимать его применение ко всем компонентам, которые могут загружаться.

Паттерн Builder в SwiftUI

Основные характеристики

  • Паттерн Builder не отличается от предыдущих примеров с environment, но имеет свои детали. Он позволяет создавать методы на структурах для возврата копий этих структур.
  • Чейнинг модификаторов возможен благодаря возвращаемому типу self, что упрощает создание сложных интерфейсов.

Ограничения и преимущества

  • Скоуп модификатора ограничивается конкретным типом View. Это удобно при наличии множества точек кастомизации без добавления дополнительных аргументов.

Пример Tab Group с использованием Builder

Настройка табов

  • Компонент Tab Group отображает скролящуюся полосу с табами. Каждый таб может иметь свой собственный параметр настройки counters.

Причины выбора Builder

  • Counters являются специфичными для каждого Tab Group и не имеют смысла вне этого контекста.
  • Generic ограничения делают невозможным использование environment для передачи значений counters между разными Tab Groups.

Стандартные компоненты и производительность

Применение стандартных библиотек

  • Рассмотрение стандартного компонента image с паттерном builder для изменения размера изображения. Этот подход делает поведение специфичным только для данного компонента.

Сравнение Swift и SwiftUI

Сложности в сравнении

  • Сравнение Swift с другими инструментами затруднено из-за отсутствия необходимых инструментов для анализа.
  • Тайминги и анимации в SwiftUI отличаются, что делает сравнение сложным.

Инструменты для улучшения производительности

  • Основной инструмент для дебаггинга - метод self print changes, который не имеет хорошей документации.
  • Этот метод позволяет отслеживать изменения и причины вызова body вьюхи, что полезно для выявления нежелательных изменений.

Профилировщик Xcode Instruments

  • Профилировщик предоставляет шаблоны под SwiftUI, позволяя отслеживать количество вызовов body.
  • Он также отслеживает изменения динамических свойств, таких как state binding и environment.

Пример использования методов

Описание кейса

  • Пример основан на реальном проекте, где пользователю нужно отправить фидбэк через текстовое поле с анимацией.
  • Включает выбор эмоций пользователем и кнопку для упрощения дебага.

Анализ производительности

  • Используя метод self print changes, видно, что каждый этап вызывает изменения в body, что приводит к повторному пересчету лейаута.
  • В тестовом сценарии зафиксировано 299 вызовов body по всем вьюхам.

Оптимизация структуры кода

Изменение архитектуры

  • Логика была разделена: слайдер с эмоджиками вынесен в отдельную вьюху, а текстовое поле - в другую.
  • Новый контент-вью стал проще и меньше по размеру.

Результаты оптимизации

  • Метод self print changes показал только один вызов при первом рендеринге нового контент-вью.
  • Инструменты показали снижение количества вызовов до 184, что составляет уменьшение на 40% по сравнению с предыдущим вариантом.

Обсуждение оптимизации в Swift UI

Передача данных и бандинг

  • В поле текст передаётся только через бандинг, а сама вьюха не использует его напрямую. Это также касается рейтинга, который меняется, но не отслеживается в Content View.

Оптимизация Swift UI

  • Swift UI способен оптимизировать обновления интерфейса, понимая, что структура родительского элемента не изменится. Он пересчитывает только вложенные элементы.

Тестирование интерфейсов

  • Обсуждается важность тестирования пользовательского интерфейса (UI-тесты), включая использование стандартных Apple UI-тестов для проверки элементов на экране.

Проблемы с фреймворком тестирования

  • Возникают проблемы с иерархией элементов в UI-тестах, которая отличается от той, что используется в UIKit или Swift UI. Это приводит к нестабильным тестам.

Фреймворк Mixbox

  • Mixbox — это разработанный фреймворк для более стабильного тестирования. Он создает иерархию элементов на стороне приложения и позволяет использовать более очевидную логику для тестирования.

Интеграция с Swift UI

  • Несмотря на отсутствие доступа к кофреймам в Swift UI, Mixbox позволяет проводить тестирование через систему UI hosting view и accessibility.

Снапшот-тесты

  • Использование снапшот-тестов для проверки компонентов дизайн-системы помогает избежать неожиданных изменений во внешнем виде элементов.

Проблемы интеграции со SnapShot Testing

  • При интеграции снапшот-тестирования могут возникнуть сложности из-за изоляции методов класса Image Render. Это требует изменения сигнатур функций.

Ограничения отображения вьюх

  • Вьюхи, использующие UI hosting view для отображения, могут неправильно рендериться или отображаться как жёлтые прямоугольники из-за особенностей реализации Apple.

Заключение и ресурсы

  • Участникам предоставляется QR-код со ссылкой на проект с примерами использования обсуждаемых технологий. Все примеры будут доступны для изучения после презентации.

Вопросы и ответы

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

Влияние вложенности вью на производительность

Вопрос о производительности

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

Иерархия вью

  • При использовании UI debugger можно увидеть, что количество отрисовываемых прямоугольников меньше, чем количество вьюшек, которые наслаиваются друг на друга.
  • SwiftUI может использовать более простые примитивы для отрисовки элементов, скрывая сложность иерархии от разработчика.

Переход к новой парадигме верстки

  • Павел Иванов поднимает вопрос о смене парадигмы верстки для разработчиков, привыкших к UIKit.
  • Разработчики могут испытывать трудности с новыми концепциями, такими как падинги и спейсеры в SwiftUI.

Обсуждение структуры лейаута

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

Заключение

  • В завершение обсуждения упоминается предстоящий доклад Ромы Мерзаяна о внедрении SwiftUI в продакшен.
Video description

Доклад поможет перестроить мышление с UIKit-ового на SwiftUI-ное. На наглядных примерах узнаем, как работает лэйаут, в чем соль декларативного подхода и как не напороться на некоторые подводные камни. А еще затронем темы перформанса и тестирования. Контакты спикера: https://t.me/apstygo Материалы из доклада: https://github.com/apstygo-avito/podlodka-swiftui-in-a-nutshell-examples Понравилось видео и хочешь узнать что-то еще про Podlodka iOS Crew? Забирай весь плейлист на https://podlodka.io/crew-records или покупай годовой доступ ко всей Библиотеке контента.