This content originally appeared on DEV Community and was authored by Gualtiero Frigerio
Pull down to refresh a list is something quite common for an iOS app. We got used to that gesture over the years and I find it a quick and intuitive way to perform the task.
With the increasing adoption of SwiftUI people are looking at ways to implement the same mechanism, and this post is about my implementation of this very gesture.
The code is available on GitHub as usual, I updated an old repository I mentioned in my previous post about lazy loading.
I’m going to show you two ways of implementing the same thing, the first putting your content inside a particular view and the second is via a ViewModifier.
RefreshableScrollView
Let’s start with the first approach, putting your content inside a view. I called it RefreshableScrollView and you can find the implementation here.
The view has to be configured with an action, a function called when the user pulls down over a certain threshold (in my example I set 50 pixels). The component doesn’t show a progress view, so you can fully customise that part.
RefreshableScrollView(action: refreshList) {
if isLoading {
VStack {
ProgressView()
Text("loading...")
}
}
LazyVStack {
ForEach(posts) { post in
PostView(post: post)
}
}
}
private func refreshList() {
isLoading = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
isLoading = false
}
}
The example is very simple, I mimic the reloading of a set of data by calling asyncAfter to wait for a second. You’d likely have to interact with a view model to ask to fetch data again, and if the user pulls while you’re loading you may want to avoid fetching again, but you get the point.
Let’s see how RefreshableScrollView actually works
struct RefreshableScrollView: View {
init(action: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) {
self.content = content
self.refreshAction = action
}
var body: some View {
GeometryReader { geometry in
ScrollView {
content()
.anchorPreference(key: OffsetPreferenceKey.self, value: .top) {
geometry[$0].y
}
}
.onPreferenceChange(OffsetPreferenceKey.self) { offset in
if offset > threshold {
refreshAction()
}
}
}
}
// MARK: - Private
private var content: () -> Content
private var refreshAction: () -> Void
private let threshold:CGFloat = 50.0
}
fileprivate struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
As you can see I’m wrapping the content inside a ScrollView with a GeometryView.
The ScrollView is necessary to be able to pull down the content.
In my tests I found out that having a List inside a ScrollView doesn’t work, so use ForEach.
GeometryReader is necessary to compute the offset.
In order to get the offset, we need to use a PreferenceKey. You can see I implemented a struct OffsetPreferenceKey for that purpose, we need to provide a defaultValue and reduce, what reduce does in our example is basically keeping the value updated.
In order to use our PreferenceKey and get the offset we have to set .anchorPreference on our content. Unfortunately there isn’t documentation about this method (the notorious no overview available). You don’t need to know the details though, all you have to know is to set this preference and to implement .onPreferenceChange, where you can get the value from GeometryProxy. We asked for the .top value, and we get the y coordinate via subscript. We get a CGPoint, so we have x and y.
If the offset is bigger than the defined threshold, we can call the action to refresh the content.
RefreshableScrollViewModifier
The second approach is a ViewModifier. Internally this view modifier uses RefreshableScrollView
struct RefreshableScrollViewModifier: ViewModifier {
var action: () -> Void
func body(content: Content) -> some View {
RefreshableScrollView(action: action) {
content
}
}
}
and this is how to use it in your view
var body: some View {
LazyVStack {
if isLoading {
ProgressView()
}
ForEach(posts) { post in
PostView(post: post)
}
}
.modifier(RefreshableScrollViewModifier(action: refreshAction))
}
I think I like using the RefreshableScrollView directly more than implementing the modifier, but it is up to you.
Hope you’ll have fun implementing pull down to refresh in your SwiftUI apps, happy coding ?
This content originally appeared on DEV Community and was authored by Gualtiero Frigerio
Gualtiero Frigerio | Sciencx (2021-05-14T12:06:03+00:00) Pull down to refresh in SwiftUI. Retrieved from https://www.scien.cx/2021/05/14/pull-down-to-refresh-in-swiftui/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.