Search code examples
iosswiftswiftuitouchscrollview

SwiftUI - How to stop ScrollView receiving taps behind other view


I have a LazyVStack inside a ScrollView that sits below a smaller view (the yellow view in the image).

enter image description here

The problem is that when the grid is scrolled up, the cells can still receive taps through the yellow view which I really don't want. I want to put a search TextField into that view and the touches get mixed up and the user experience is a mess. In the example code I added a button to swap to a List to check if that had the same scrolling problem. It doesn't.

import SwiftUI

struct ContentView: View {
    
    @State private var useList = false
    @State private var gridLayout: [GridItem] = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
    
    var data = (0...249).map { "\($0)" }
    
    var body: some View {
        NavigationView {
            VStack {
                TopInfoPane()
                if useList {
                    List(data, id: \.self) { item in
                        NavigationLink(destination: Text(item)) {
                            Text(item)
                        }
                    }
                } else {
                    ScrollView {
                        LazyVGrid(columns: gridLayout, alignment: .center, spacing: 8) {
                            ForEach(data, id: \.self) { item in
                                NavigationLink(destination: Text(item)) {
                                    Cell(item: item)
                                }
                            }
                        }
                    }
                }
            }.navigationBarTitle("Scroller")
            .navigationBarItems(trailing: Button(action: { useList.toggle() }) { Text("Swap") })
        }
    }
}

struct Cell: View {
    var item: String
    var body: some View {
        Text(item)
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 75)
            .background(Color.green)
    }
}

struct TopInfoPane: View {
    var body: some View {
        Text("TopInfoPane")
            .frame(height: 48)
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.yellow)
    }
}

I've tried setting animation on the view to nil and setting .allowsHitTesting(false) but they didn't help. I'm at a loss because I really didn't expect the ScrollView to scroll beyond its own frame anyway.


Solution

  • Try to clip ScrollView as follows

    ScrollView {
        LazyVGrid(columns: gridLayout, alignment: .center, spacing: 8) {
            ForEach(data, id: \.self) { item in
                NavigationLink(destination: Text(item)) {
                    Cell(item: item)
                }
            }
        }
    }
    .contentShape(Rectangle())
    .clipped()