How to detect scroll up, scroll down, top and bottom in SwiftUI List

Viewed 3763

I have a List and I would like to hide a toolbar and tab bar when the List scrolls up.

I've tried to detect movements using the following:

                    .gesture(
                        DragGesture()
                        .onChanged { value in
                            if value.translation.height > 0 {
                                print("Scroll down")
                            } else {
                                print("Scroll up")
                            }
                        }
                        .onEnded{ value in
                            print("ended")
                        }
                    )

This works intermittently, as in only around 80% of the time, it only seems to detect scrolls when I do it slowly or when I place my finger down straight onto the screen and then scroll. Otherwise nothing. Also, onEnded never fires here.

I also tried this:

                    .gesture(
                        DragGesture(minimumDistance: 0, coordinateSpace: .local)
                        .onChanged { value in
                        
                            print(value.translation.height)

                            if value.translation.height > 0 {
                                print("Scroll down")
                            } else {
                                print("Scroll up")
                            }
                        }
                        .onEnded{ value in
                            print("ended")
                            
                            print(value.location.y)
                        }
                    )

This catches all my scrolling, which is great. onEnded also fires, but the problem is my List doesn't actually scroll.

I'm at a loss here, I want to know when the user scrolls up, scrolls down, by how much, and if possible I would like to know when the user gets to the top of the list.

1 Answers

If you want to do this SwiftUI only you can make a GeometryReader inside the ScrollView to detect offset change like shown here: https://fivestars.blog/swiftui/scrollview-offset.html

If you don't mind using Introspect you can do it like described in this answer: https://stackoverflow.com/a/65467199/3393964


You can use Introspect to get the UIScrollView, then from that get the publisher for UIScrollView.contentOffset and UIScrollView.isDragging to get updates on those values which you can use to manipulate your SwiftUI views.


struct Example: View {
    @State var isDraggingPublisher = Just(false).eraseToAnyPublisher()
    @State var offsetPublisher = Just(.zero).eraseToAnyPublisher()

    var body: some View {
        ...
        .introspectScrollView {
            self.isDraggingPublisher = $0.publisher(for: \.isDragging).eraseToAnyPublisher()
            self.offsetPublisher = $0.publisher(for: \.contentOffset).eraseToAnyPublisher()
        }
        .onReceive(isDraggingPublisher) { 
            // do something with isDragging change
        }
        .onReceive(offsetPublisher) { 
            // do something with offset change
        }
        ...        
}

If you want to look at an example; I use this method to get the offset publisher in my package ScrollViewProxy.

Related