JFortYork
JFortYork

Reputation: 147

How do you tell when the user has scrolled to the bottom of a LazyVGrid?

I'm fairly new to SwiftUI and I'm trying to figure out a problem that I can't seem to solve. I am building an app based on the GitHub API. In one of the screens, I am showing all the followers that a particular user has.

What I am trying to accomplish is that I'm trying to know when the user has scrolled to the bottom of the list of followers as I currently only grab 100 records from the server.

How can I know when the user has scrolled to the bottom of those 100 followers so that I can initiate another request for 100 more records in the 2nd and subsequent pages?

I have googled this and saw things about the ScrollViewReader, GeometryReader but I am not sure how to fix this. Here is the code in this particular screen:

import SwiftUI

struct FollowersScreen: View {
    
    @EnvironmentObject var savedFavorites: SavedFavorites
    
    @State var userID: String
    @State private var isPresented = false
    @State private var followers: [Follower] = []
    @State private var page = 1
    @State private var user: User? = nil
    
    let columns = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
    
    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns: columns, content: {
                    ForEach(followers, id: \.self) { follower in
                        FollowerCell(follower: follower)
                            .onAppear(perform: {
                                // What do I do here to get 100 more records properly?
                            })
                            .onTapGesture {
                                self.getFollowerDetails(userID: follower.login)
                            }// ON TAP GESTURE
                    }// FOR EACH
                })// LAZYVGRID
                    .padding(.horizontal)
            }// SCROLL VIEW
        }// VSTACK
        .onAppear(perform: {
            self.getFollowers(userID: userID, page: 1)
        })
        .navigationTitle("\(userID) Followers")
        .navigationBarTitleDisplayMode(.inline)
        .navigationBarItems(trailing:
                                Button(action: {
            addFavourite(gitHubUser: userID)
        }) {
            Image(systemName: Images.heartCircle)
                .resizable()
                .scaledToFit()
                .frame(width: 33, height: 33, alignment: .center)
        }
        )// NAV BAR ITEMS
        .sheet(isPresented: $isPresented, onDismiss: nil) {
            FollowersDetailScreen(gitHubUser: $userID, isPresented: $isPresented, followers: $followers, user: user)
        }
        .environmentObject(savedFavorites)
    }// BODY
    
    private func getFollowers(userID: String, page: Int) {
        NetworkManager.shared.getFollowers(for: userID, page: page) { result in
            switch result {
            case .success(let followers):
                self.followers += followers
                print("**** Count of Followers: \(self.followers.count)")
            case .failure(let error):
                print("getFollowers(gitHubUser:, page:) -> Error: \(error)")
            }
        }
    }
    
    private func getFollowerDetails(userID: String) {
        NetworkManager.shared.getUserInfo(for: userID) { result in
            switch result {
            case .success(let user):
                print("Success, found the user info!")
                self.user = user
                guard let tempUser = self.user else { return }
                print(tempUser)
                self.isPresented = true
            case .failure(let error):
                print("getUser(gitHubUser:, page:) -> Error: \(error)")
            }
        }
    }
    
    private func addFavourite(gitHubUser: String) {
        NetworkManager.shared.getUserInfo(for: gitHubUser) { result in
            switch result {
            case .success(let user):
                let favorite = Follower(login: user.login, avatarUrl: user.avatarUrl)
                savedFavorites.favorites.append(favorite)
                PersistenceManager.shared.encodeJSONToDisk(favorites: savedFavorites.favorites)
            case .failure(let error):
                print("getFavourite(gitHubUser:, page:) -> Error: \(error)")
            }
        }
    }
}

struct FollowersScreen_Previews: PreviewProvider {
    static var previews: some View {
        FollowersScreen(userID: "sallen0400")
    }
}

Upvotes: 0

Views: 1113

Answers (1)

Yrb
Yrb

Reputation: 9755

You don't need to know when you are at the bottom of the LazyVGrid, you need to know when you are running out of data. You need the index of where you are in the list, and compare it to the count of your array. Also, I had to use some pseudocode as I didn't have all of your types:

            LazyVGrid(columns: columns, content: {
                // Zip gives you both the index and the item. You could just get the index, but the
                // code reads better this way, IMHO
                ForEach(Array(zip(followers.indices, followers), id: \.1) { index, follower in
                    FollowerCell(follower: follower)
                        .onAppear(perform: {
                            // Now we test our index against the total array count.
                            // You should also build a check that once you get an end of
                            // data response that you don't keep calling for more data.
                            if followers.count()/* - buffer*/ == index { // Place buffer here for earlier update.
                                getFollowers(userID: ...
                            }
                        })
                        .onTapGesture {
                            self.getFollowerDetails(userID: follower.login)
                        }// ON TAP GESTURE
                }// FOR EACH
            })// LAZYVGRID

When I originally answered this I built in a buffer of 20 items before the update was called. However, with further experimentation, I found that this is not necessary as the .onAppear is triggered well before the view actually appears on screen. If you want to add a buffer to give you more time to download the data, all you have to do is subtract a given amount of items from the total count so the update is triggered earlier. I put a comment about it as to where to place it.

Upvotes: 3

Related Questions