Connect with us

Разработка

Осваиваем скроллинг в SwiftUI: реализация кастомной прокрутки

Настроив ScrollTargetBehavior, мы не только добились горизонтального пейджинга, но и можем расширить его для поддержки вертикальной прокрутки или более сложной логики прокрутки.

Опубликовано

/

     
     

Начиная с iOS 17 в SwiftUI появилась функция scrollTargetBehavior, позволяющая разработчикам управлять поведением прокрутки с большей точностью. Будь то выравнивание представлений или реализация пользовательских эффектов пагинации, ScrollTargetBehavior обеспечивает надежную поддержку. Что еще более важно, разработчики могут создавать собственные реализации ScrollTargetBehavior для удовлетворения конкретных потребностей. В этой статье на реальном примере шаг за шагом будет показано, как использовать scrollTargetBehavior и в конечном итоге реализовать пользовательскую логику управления прокруткой.

Проблема: ограничения пейджинга по умолчанию

Несколько дней назад один из разработчиков поднял проблему с scrollTargetBehavior: при использовании стандартного поведения пагинации прокрутка в ландшафтном режиме (Landscape) приводила к смещению, не позволяя привязаться к нужной странице.

Эта проблема удивила меня, поскольку логика работы с пейджингом должна быть относительно простой. Чтобы быстро решить проблему, я сначала попробовал использовать библиотеку Introspect, чтобы напрямую включить свойство isPagingEnabled базового UIScrollView.

Однако результат был идентичен использованию .scrollTargetBehavior(.paging), с той же проблемой смещения в ландшафтном режиме. Это навело меня на мысль, что стандартное поведение пейджинга, возможно, на самом деле не полагается на ScrollTargetBehavior для своей реализации.

Я сообщил об этой проблеме в Apple (FB16486510).

Альтернативное решение: может ли viewAligned устранить проблему?

Учитывая, что ширина каждого представления в коде разработчика точно соответствовала ширине контейнера прокрутки, я предложил попробовать viewAligned — режим, который обеспечивает выравнивание краев представления с краями контейнера в конце прокрутки.

viewAligned предлагает несколько уровней точности управления:

  • alwaysByOne: Прокрутка одного View за раз
  • alwaysByFew: Прокрутка небольшого количества представлений
  • never: Нет ограничений на количество прокручиваемых представлений

Однако во время тестирования я обнаружил, что alwaysByOne не гарантирует прокрутку только одного представления за раз. В текущем коде, поскольку ширина каждого дочернего представления совпадает с шириной контейнера прокрутки, случайно достигается эффект, похожий на прокрутку страниц. Но если дочерние представления более узкие, расстояние прокрутки становится непредсказуемым.

Кроме того, viewAligned требует, чтобы содержимое контейнера прокрутки состояло из нескольких дочерних представлений, что делает его непригодным для таких сценариев, как Swift Charts. На этом этапе реализация кастомного поведения пейджинга казалась единственным жизнеспособным решением.

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

ScrollTargetBehavior позволяет разработчикам настраивать поведение прокрутки. Его объявление выглядит следующим образом:

SwiftUI вызывает updateTarget в конце жеста прокрутки, позволяя разработчикам регулировать положение target.

ScrollTargetBehaviorContext предоставляет следующую ключевую информацию:

  • originalTarget: положение цели в начале жеста
  • velocity: вектор скорости
  • contentSize: размер прокручиваемого содержимого
  • containerSize: размер контейнера прокрутки
  • axis: ось прокрутки

ScrollTarget не только задает позицию цели, но и предоставляет цель прокрутки, рассчитанную ScrollView на основе жеста.

Далее мы итеративно доработаем пользовательскую реализацию пейджинга в нескольких версиях.

Версия 1: Простая реализация на основе скорости

В первой версии мы определяем направление прокрутки на основе вектора скорости и настраиваем целевую позицию путем добавления или вычитания ширины контейнера прокрутки.

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

Версия 2: улучшенная реализация на основе расстояния прокрутки

Во второй версии мы определяем направление прокрутки, вычисляя разницу между целевой и начальной позициями. Затем мы решаем, нужно ли переходить на страницу, основываясь на заданном условии (например, расстояние прокрутки превышает 1/3 ширины контейнера).

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

Версия 3: надежное управление пейджингом

В третьей версии мы не только решаем предыдущие проблемы, но и справляемся со следующими крайними случаями:

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

Эта версия легко справляется со случаями, когда размер содержимого не является точным кратным размеру контейнера.

Я поделился реализацией горизонтальной и вертикальной пагинации в этом Gist. Не стесняйтесь посмотреть и проверить его.

За пределами пейджинга

Настроив ScrollTargetBehavior, мы не только добились горизонтального пейджинга, но и можем расширить его для поддержки вертикальной прокрутки или более сложной логики прокрутки. Например, комбинируя скорость прокрутки (velocity), можно реализовать многостраничную прокрутку при быстром пролистывании и одностраничную при легком пролистывании.

Кроме того, scrollTargetBehavior может служить инструментом для динамической загрузки данных. По сравнению с использованием onAppear в ленивых представлениях, оно позволяет нам запускать загрузку данных раньше во время прокрутки, тем самым улучшая проблему скачка прокрутки, вызванную динамической загрузкой данных в ленивых контейнерах SwiftUI.

Хотя onScrollGeometryChange позволяет добиться аналогичной функциональности, он доступен только в iOS 18 и более поздних версиях, в то время как ScrollTargetBehavior поддерживается с iOS 17, что делает его более широко применимым.

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: