На основе дизайна Dribbble
Недавно, когда я работал над реализацией SwiftUI дизайна, найденного на Dribbble, мне пришла в голову идея расширить этот проект несколькими классными фильтрами, чтобы сузить список результатов.
Я решил, что представление фильтра будет состоять из двух отдельных параметров фильтра, в каждом из них будет несколько параметров для выбора. Но потом я столкнулся с проблемой. Когда я работал с UIKit, я всегда реализовывал такое представление как UICollectionView
с определенным UICollectionViewFlowLayout
. Но как я могу сделать это в SwiftUI?
Давайте посмотрим на мою реализацию гибкого средства выбора со SwiftUI!
Выбираемый протокол
Самая важная часть средства выбора заключается в том, что мы можем выбрать некоторые необходимые параметры благодаря этому компоненту представления. Вот почему в начале я создал протокол Selectable
.
Все объекты, соответствующие этому протоколу, должны реализовать два свойства: displayedName
— имя, которое будет отображаться в средстве выбора, и isSelected
— логическое значение, указывающее, выбрана ли данная опция или нет.
Более того, чтобы можно было создавать объекты Selectable
, просто сопоставляя массив значений String, пользовательское init
с displayedName
в качестве аргумента должно быть предоставлено объектом, соответствующим Selectable.
Протоколы Identifiable
и Hashable
гарантируют, что мы сможем легко создать представление SwiftUI с циклом ForEach
. Кроме того, все объекты, соответствующие протоколу Selectable
, будут реализовывать постоянное значение id
, хранящее значение UUID
.
Я намеренно опускаю реализацию объекта, соответствующего протоколу Selectable
, так как считаю это очевидным. Если нет, то вы сможете проверить это в моем репозитории GitHub, упомянутом в конце этой статьи.
Настройка
Моей целью было не только создать реализацию гибкого средства выбора, но и сделать его максимально настраиваемым.
Вот почему FlexiblePicker
будет создан с использованием универсального типа T
, который соответствует протоколу Selectable
. Таким образом, впоследствии будет легче повторно использовать этот компонент, поскольку он будет независимым от типа.
Перед реализацией самого сборщика я записал все настраиваемые свойства. Следующим шагом было создание расширений String для вычисления ширины и высоты определенного строкового значения.
Поскольку моя реализация дает возможность изменить размер и вес шрифта, оба расширения, упомянутые ранее, принимают в качестве аргумента UIFont
, используемый гибким средством выбора.
Поскольку мои строковые расширения для вычисления размера заданной строки принимают в качестве входных данных UIFont
, мне нужно было преобразовать все веса UIFont
в эквиваленты SwiftUI.
Вот почему я ввел перечисление FontWeight
, состоящее из всех возможных случаев, названных в честь UIFont
весов.
Кроме того, это перечисление имеет два свойства: одно возвращает вес UIFont
, а второе — вес шрифта SwiftUI. Таким образом, мы просто предоставляем конкретный случай перечисления FontWeight
нашему FlexiblePicker
.
Логика гибкого подборщика
После этого я наконец был готов начать работу над реализацией FlexiblePicker
.
Прежде всего, мне нужна была функция для вычисления и возврата ширины всех данных, переданных на вход. Я сделал это, сопоставив все входные значения с кортежами, состоящими из входного значения и самой ширины.
Внутри карты я использовал функцию уменьшения, чтобы суммировать все ширины, связанные с заданным входным значением (ширина текста, ширина границы, отступы текста, интервалы).
Теперь, когда функция для расчета ширины готова, мы можем перебрать все входные данные и разбить их на отдельные массивы. Каждый массив состоит из элементов, которые могут поместиться в одно и то же HStack
. Логика проста. У нас есть массивы:
singleLineResult
массив — отвечает за хранение элементов, которые помещаются в определенную строкуallLinesResult
массив — отвечает за хранение всех массивов элементов (каждый массив является эквивалентом строки элементов)
Во-первых, мы проверяем, больше ли результат вычитания ширины элемента из ширины строки HStack
0.
Если условие выполнено, мы добавляем текущий элемент к singleLineResult
, обновляем доступную ширину строки HStack
и переходим к следующему элементу.
Если результат меньше 0, это означает, что мы не можем поместить следующий элемент в заданную строку, поэтому мы добавляем singleLineResult
к allLinesResult
, устанавливаем singleLineResult
как массив, состоящий только из текущего элемента (тот, который не может поместиться в строку). предыдущая строка) и обновить ширину строки HStack
, вычитая ширину текущего элемента.
После прохождения всех элементов мы должны обработать конкретный угловой случай. Возможно, что singleLineResult
не будет пустым и не будет добавлено к allLinesResult
— так как мы добавляем singleLineResult
только тогда, когда результат вычитания ширины элемента будет меньше 0. В этом случае мы должны проверить, является ли singleLineResult
пустой, если это правда, мы возвращаем allLinesResult
, если нет, мы должны сначала добавить singleLineResult
, а затем вернуть allLinesResult
.
И последнее, но не менее важное: мы должны вычислить высоту VStack
, чтобы облегчить SwiftUI интерпретацию нашего компонента представления. Высота VStack
рассчитывается на основе двух значений:
- высота любого элемента внутри входных данных (вычисляется аналогично ширине с помощью функции уменьшения, чтобы суммировать все высоты, связанные с элементом)
- количество строк, которые будут отображаться в
VStack
Результат умножения этих двух чисел будет нашим ростом VStack
. Просто, да?
Гибкий вид выбора
Наконец, когда вся логика готова, нам нужно реализовать тело представления. Как я упоминал ранее, представление будет создано с использованием ForEach
циклов, один из которых вложен в другой.
Важно иметь в виду, что циклы ForEach
требуют, чтобы каждый элемент повторяемой коллекции соответствовал протоколу Identifiable
или имел уникальный идентификатор.
Вот почему я сопоставил результаты разделенных строк на кортежи, состоящие из каждой строки и идентификатора, который является значением UUID
.
Благодаря этому я могу предоставить аргумент id для цикла ForEach
. Второе, что нужно помнить, это то, что цикл ForEach
ожидает получить какое-то представление в качестве возвращаемого значения.
Если мы вставим туда еще один цикл ForEach
, у нас возникнут проблемы с правильной функциональностью нашего представления, так как ForEach
не является видом представления.
Именно поэтому я завернул весь цикл ForEach
сначала в HStack
, а затем в Group
, чтобы убедиться, что компилятор все правильно интерпретирует.
Почти все готово, осталось добавить функцию, которая будет обрабатывать взаимодействие пользователя с кнопками. Функция просто переключает свойство isSelected
для конкретных данных.
Остальной код прост, в основном это конфигурация всех свойств, таких как шрифт, цвета или граница. Более того, в конце VStack
мы ставим рамку, ширина которой берется из GeometryReader
, а высота — вычисляется ранее созданной функцией.
И вот мы готовы! Наш FlexiblePicker готов и готов к использованию!
Надеюсь, вам понравилось! Как всегда, вся реализация доступна в моем репозитории GitHub. Большое вам спасибо за ваше время.