Pull to refresh в SwiftUI

На момент публикации — 10 мая 2022, SwiftUI имеет всего лишь refreshable(action:) модификатор для List компонента, чтобы пользователь имел возможность обновить контент на экране (так называемый pull to refresh). Очевидно, что если разработчику потребуется отобразить список в виде отличном, от того, который предоставляет List (например, в несколько колон), то, к сожалению, придется смириться с тем, что контент на экране обновить с помощью pull to refresh будет нельзя. Или же все-таки можно?…

Задача

Требуется некоторый компонент, который бы отображал в scroll view контент и выполнял некоторую функцию при оттягивании контента вниз, аналогично pull to refresh технологии. При этом предпочтительно использовать новую систему concurrency, которая дебютировала в iOS 15. Компонент должен быть реализован в виде отдельного модуля, формирующего некоторую абстракцию для того, чтобы его легче было поддерживать. Другими словами, компонент должен распространяться в некоторой библиотеке и иметь возможность быть добавленным в проект с помощью менеджера зависимостей.

RefreshableScrollView

Не стоит тянуть время, сразу к source коду:

По порядку. При инициализации компонента передается closure, которое будет вызвано в момент срабатывания pull to refresh, а также контент в виде @ViewBuilder, который встраивается в ScrollView. При этом в ScrollView добавляется PullToRefresh view, при необходимости которому задается offset, равный offset контента минус собственная высота. Таким образом, при отображении экрана PullToRefresh view как бы находится за границей ScrollView, а при скроле появляется.

Стоит разъяснить наличие canChangeState свойства. Дело в том, что в случае, если onRefresh функция заканчивает свое выполнение до того, как пользователь отпустил палец, то без проверки canChangeState свойства, было бы вызвано повторное срабатывание onRefresh функции. Имея такое свойство, до тех пор, пока PullToRefresh view не вернется на исходную позицию, невозможно будет повторить onRefresh.

В остальном предельно ясно что и для чего используется. Метод pullProgress(offset:threshold:) рассчитывает прогресс срабатывания (pullProgress) от 0 до 1, а метод refresh(), собственно, запускает анимацию обновления, вызывает haptic engine и выполняет onRefresh функцию.

Наверняка, внимательного читателя может заинтересовать метод bounded(bottom:top:), который используется при расчете pullProgress свойства. Код представлен ниже.

В настоящее время все еще нет у SwiftUI собственного решения для определения contentOffset в ScrollView, поэтому для этих целей используется OffsetPreferenceKey, представленный ниже.

В отдельный компонент выделен PullToRefresh view, как раз таки для того, чтобы его легко можно было заменить/поддерживать.

Все достаточно тривиально. Некоторое изображение или иконка и текст под ним, которые меняются в зависимости от скрола (progress свойство) и выполнения onRefresh функции (refreshing свойство).

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

Стоит обратить внимание, что анимация реализована с помощью withAnimation(_:_:) функции. Поскольку offset данного компонента меняется и во время onRefresh функции, то использовать модификатор animation(_:) не представляется возможным, так как в этом случае анимируется еще и offset компонента.

Заключение

На носу WWDC 2022. Вполне возможно, что в новом обновлении SwiftUI будет представлено решение от Apple. Тем не менее, на сегодняшний день существует не так много библиотек, предоставляющих схожий механизм обновления.

  1. https://github.com/globulus/swiftui-pull-to-refresh
  2. https://github.com/AppPear/SwiftUI-PullToRefresh
  3. https://github.com/wxxsw/Refresh
  4. https://blckbirds.com/post/mastering-pull-to-refresh-in-swiftui/
  5. https://developer.apple.com/wwdc22/

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ace Rodstin

Ace Rodstin

More from Medium

Every iOS developer should know these Xcode tricks

Introduction to Combine framework in Swift

apple/swift-evolution 上關於 guard 的有趣討論

Everyone can code Puzzles — Hypocycloid