Pull to refresh

Реализация Grid раскладки на SwiftUI

Reading time5 min
Views3.8K
Original author: Денис Обухов

Что такое Grid Layout?


Разрабатывая интерфейс приложения, мы чаще всего имеем дело именно с двумерным представлением визуальных элементов. Существует множество контролов, призванных стандартизировать и упростить верстку. Если рассматривать концепции веб-разработки, то в качестве наилучшего способа создания двумерных шаблонов интерфейса CSS Working Group в 2017 году предложила Grid Layout.
image
В iOS 13 SDK в наличии у нас имеется возможность располагать элементы линейно VStack, HStack, ZStack, в виде списка: List. Но нет возможности определить семантику расположения элементов в двумерной системе координат. Поэтому мы решили создать его сами.
В iOS 14 Apple представила свою реализацию grid, о которой мы расскажем ниже в разделе Apple LazyVGrid/LazyHGrid.


Примеры интерфейсов с Grid Layout


Как самый очевидный пример использования grid на ум приходит отображение в виде ячеек коллекции. Будь это горизонтальная или вертикальная ось, объединенные ячейки или же одинарные. Grid, в отличие от UICollectionView, работает с семантикой на уровне строк и столбцов.
vertical_scroll
horizontal_scroll
Вот несколько приложений, интерфейс которых отлично приспособлен для отображения с помощью Grid Layout


Timetable


image
Пример отображения расписания дня в виде ячеек, представляющих собой события, размеры которых пропорциональны их длительности. Здесь grid представляет собой сетку, градация которой соответствует точности отображения временным интервалов. Spans у grid item соответствует длительности события.
В альтернативном виде мы можем представить, что изменилась длительность и начальная дата некоторых событий.


Flipboard


image
Новостное приложение, где каждая новость представлена одной ячейкой с картинкой, заголовком и коротким описанием. Размер ячейки может показывать приоритет новости, а её положение — привязку ко времени. Grid Layout подходит для перестроения порядка новостей на лету, к примеру на картинке выше мы меняем grid flow с .rows на .columns.


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


Вот самое примитивное представление grid:
image
Здесь все grid элементы одного размера, но они могли бы быть и различными, если бы мы захотели. Некоторые могли бы охватывать несколько столбцов и рядов, другие могли бы оставаться размером в одну ячейку.


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


Ключевой особенностью Grid, является возможность объединять столбцы или строки вместе. Объединяться могут треки даже с разной семантикой определения размеров.


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


Также мы должны уметь задавать направление, вдоль которого элементы будут располагаться: по строчкам или по столбцам.


Может возникнуть ситуация, когда очередной элемент не может поместиться в текущую свободную позицию и он переносится на новый трек. Таким образом возникает пустота, даже если в дальнейшем есть элементы, которым это место пришлось бы в самый раз. Для этого в grid должен присутствовать режим плотного (dense) расположения элементов.


Grid может содержать элементы, которые либо должны быть вписаны в размеры родительского контейнера, либо должны прокручиваться внутри него в соответствии с их внутренними размерами. В этом бы нам помогли режимы scroll и fill.


Отступы между элементами также должны настраиваться.


И, разумеется, все изменения должны происходить с анимацией.


Наша реализация


Все описанные выше требования мы реализовали в ExyteGrid — нашей реализации Grid, написанной полностью на SwiftUI. Вот примеры нескольких приложений, переписанных с использованием ExyteGrid:


Фитнес-трекер Strava


Интерфейс этого экрана представляет из себя ни что иное, как grid. Цветными прямоугольниками обозначены элементы внутри Grid. Так же мы реализовали альтернативный вид в альбомной ориентации, изменив количество столбцов, стартовые позиции и spans некоторых элементов.
image


Стандартный калькулятор iOS


Помните стандартный калькулятор в iOS? Как вы уже догадались, это тоже grid, где каждая кнопка является grid item. С использованием ExyteGrid мы не только повторили его layout, но и сделали альтернативный вид аналогичным способом. Благодаря силе SwiftUI мы можем анимировать изменения состояния grid.
image


Apple LazyVGrid/LazyHGrid


Если говорить о недавно анонсированных Apple LazyVGrid и LazyVGrid в iOS 14, то отличий между ними и ExyteGrid несколько:


  • В Apple реализации отсутствует возможность указать span у ячеек. То есть их нельзя объединить по вертикали и/или по горизонтали с несколькими соседними ячейками.
  • Так же нельзя отдельным элементам указать приоритетную позицию в grid: фиксированный столбец и/или фиксированную ячейку.
  • Наличие этих двух возможностей приводит к существованию в ExyteGrid двух режимов упаковки элементов (grid packing): обычного sparse, и режима dense, когда порядок элементов может меняться, чтобы заполнить пространство, максимально уменьшая количество пустот.
  • Изменение состояния с анимацией у Apple Grid чаще всего приводит к обычному fade эффекту, если элементы не находятся в едином ForEach. ExyteGrid же сам производит анимацию, ассоциируя ID элемента с его позицией внутри grid. При этом отдельным элементам можно присвоить конкретный ID с использованием GridGroup или ForEach, что дает возможность SwiftUI рассчитывать анимацию перехода (transition) для каждого элемента.
  • Отличается способ задания размеров гибких треков. Flexible размер у Apple Grid требует явного указания ограничивающих размеров в точках, в то время, как .fr размер у ExyteGrid задает соотношение между другими треками в долях.
  • Большая разница между .adaptive размером у Apple Grid и .fit размером в нашей реализации. Adaptive размер в действительности не задает конкретное число треков. Он их создает столько, сколько получится в соответствии с ограничениями. Fit размер у ExyteGrid создает один трек, размер которого соответствует размеру максимального элемента, попавшего в него.
  • В ExyteGrid можно указать gridCellOverlay и gridCellBackground, что дает возможность разместить custom view в свободном пространстве вокруг элементов, когда такое образуется. Это происходит при разнице размеров элементов в одной строке или столбце.
  • Apple Grid лениво загружает элементы, в том время, как ExyteGrid инициализирует их все сразу.
  • В ExyteGrid есть режим content mode: .fill, когда grid располагает элементы так, чтобы они заполнили container view, без необходимости вручную расчитывать размеры каждого элемента.
    Концепции этих двух реализаций в чем-то схожи, но во многом и отличаются. На стороне у Apple Grid — нативная реализация, ориентированность на большое количество элементов, а на стороне ExyteGrid — больший функционал и открытый исходный код, в развитии которого может принять участие каждый.

Рад видеть Вас как в виде пользователей, так и в виде контрибьюторов в репозитории библиотеки ExyteGrid.


Разумеется, остается список фич, которые еще только предстоит сделать в разделе roadmap.

Tags:
Hubs:
Total votes 4: ↑4 and ↓0+4
Comments1

Articles