Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide a view in the top of a ScrollView (SwiftUI)?

I'm trying to replicate a behavior from one of Apple's apps.

enter image description here

I figure this is a combination of scrolling and draging gestures.

By calculating the scroll offset, scroll direction and the position of the hidden view I managed to create a janky version of the behavior.

This is the code:

struct _TestView: View {
    @State var scrollOffset: CGPoint = .zero
    @State var contentOffset: CGPoint = CGPoint(x: .zero, y: -200)
    
    @State var scrollDisabled = true
    
    var drag: some Gesture {
        DragGesture()
            .onChanged { value in
                if !scrollDisabled { return } // if scrolling is enabled then don't change content offset
                
                var newLocation = startLocation ?? contentOffset

                // sets dragging bounds (-200 to 0)
                if contentOffset.y >= 0 && value.translation.height > 0 {
                    newLocation.y = 0;
                } else if contentOffset.y <= -200 && value.translation.height < 0 {
                    newLocation.y = -200;
                } else {
                    newLocation.y += value.translation.height
                }
                
                self.contentOffset = newLocation
            }
            .updating($startLocation) { ... }
        
    }
    
    // this is misc. 
    @GestureState private var fingerLocation: CGPoint? = nil
    @GestureState private var startLocation: CGPoint? = nil
    
    var fingerDrag: some Gesture { ... }
    
    var body: some View {
        OffsetObservingScrollView(offset: $scrollOffset) {
            VStack {
                Rectangle()
                    .fill(.blue)
                    .frame(height: 200)
                Rectangle()
                    .fill(.red)
                    .frame(height: 1000)
                    .overlay { ... }
            }
            .offset(y: contentOffset.y)
            .gesture(drag.simultaneously(with: fingerDrag))
        }
        .scrollDisabled(scrollDisabled)
        .navigationTitle("Hello, world!")
        .border(.black)
    }
}

When scrolling is disabled, drag gestures work.

enter image description here

However when you fling scroll the drag is janky. Would love to know how to improve this :)

The next problem is how to enable scrolling when contentOffset == -200 or 0 I managed to get it to work somewhat by observing changes to scrollOffset, contentOffset and even the drag gesture values, but it's not reliable and barely works. :(

Appreciate all help!

like image 911
A_Jayke Avatar asked Dec 11 '25 11:12

A_Jayke


1 Answers

If you just trying to replicate the behavior where you could hide a view behind like the Books app, it could be achieved by putting two LazyVStack inside a ScrollView where the first LazyVStack will have a pinned view for the behavior.

The code would be something like:

ScrollView(.vertical, showsIndicators: true) {
    LazyVStack(pinnedViews: [.sectionHeaders]) {
        Section {
            Text("10 books 4 pdfs and more!")           
        } header: {
            VStack {
                Divider()
                Text("Collections (Pinned)")
                Divider()
            }
        }
    }
    LazyVStack() {
        // Here goes the book grid
    }
}

But I also noticed that in the Apple Books app, there is more detail to it, for example, the top view is hidden by default, and the navigation title won't collapse until the "hidden" view is fully invisible, plus the scroll snap behavior for the first section.

like image 112
Gerry Gao Avatar answered Dec 13 '25 02:12

Gerry Gao



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!