1

Hello I have a scroll view and I would like to know when the user swipes to the bottom of it so that I can populate more data into it. The code I have below isn't working quite right. When the sim launches "hello" is printed about 5 times, and when I barely move it "hello" is printed like 10 more times, by the time I get to the bottom of the scroll view, "Hello" was printed like 100 times. How can I fix my code so that Hello is only printed when the bottom is reached.

import SwiftUI
import UIKit

struct FeedView: View {    
    @State private var scrollViewHeight = CGFloat.infinity
    @Namespace private var scrollViewNameSpace
    
    var body: some View {
        mainInterFaceView
    }
}

extension FeedView{
    var mainInterFaceView: some View{
            ZStack(){
                ScrollView {
                    LazyVStack {
                        ForEach(viewModel.tweets) { tweet in
                            Button {

                            } label: {
                                someView()
                                    .background(
                                        GeometryReader { proxy in
                                            Color.clear
                                                .onChange(of: proxy.frame(in: .named(scrollViewNameSpace))) { newFrame in
                                                    if newFrame.minY < scrollViewHeight {
                                                        print("called")
                                                    }
                                                }
                                            
                                        }
                                    )
                                
                            }
                        }
                    }
                }
                .background(
                    GeometryReader { proxy in
                        Color.clear
                            .onChange(of: proxy.size, perform: { newSize in
                                let _ = print("ScrollView: ", newSize)
                                scrollViewHeight = newSize.height
                            })
                    }
                )
                .coordinateSpace(name: scrollViewNameSpace)
                
            }
        }
}
burnsi
  • 6,194
  • 13
  • 17
  • 27
  • I found the answer: https://stackoverflow.com/questions/68681075/how-do-i-detect-when-user-has-reached-the-bottom-of-the-scrollview –  Feb 18 '23 at 06:06

1 Answers1

0

There's a much simpler way to do this than to use GeometryReaders, and to try and calculate the offset.

If more pages of data are available, you just need to display a view (ProgressView works nicely) at the bottom of your VStack, and then load more data when it appears (using .task modifier)

struct Tweet: Identifiable {
    let id = UUID()
    let text: String
}

@MainActor
final class TweetModel: ObservableObject {
    
    private let pageSize = 20
    private (set) var tweets: [Tweet] = []
    private (set) var moreTweetsAvailable = false
    private var nextPage = 0
    
    func loadFirstPage() async  {
        nextPage = 0
        await loadPage()
    }
    
    func loadNextPage() async  {
        await loadPage()
    }
    
    private func loadPage() async {
        let tweets = await // load tweets for nextPage
        moreTweetsAvailable = tweets.count == pageSize
        self.tweets.append(contentsOf: tweets)
        nextPage += 1
    }

struct ContentView: View {
    
    @StateObject var tweetModel = TweetModel()
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(tweetModel.tweets) { tweet in
                    Text(tweet.text)
                }
                if tweetModel.moreTweetsAvailable {
                    ProgressView()
                        .task {
                            await tweetModel.loadNextPage()
                        }
                }
            }
        }
        .task {
            await tweetModel.loadFirstPage()
        }
    }
}
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • Thanks for your solution I don't understand what moreTweetsAvailable does, can I just replace that with an onAppear on the progressView, and then put the task inside of the onAppear? –  Feb 18 '23 at 20:31
  • I've updated the answer to give you an idea of what your model should contain. – Ashley Mills Feb 18 '23 at 21:22
  • Thanks for your help, would you please look into the question below, its linked to this same problem, Im having a little trouble loading the first page: https://stackoverflow.com/questions/75503979/saving-array-from-closure-in-swift-async-await –  Feb 19 '23 at 23:23