Search code examples
swiftswiftuiscrollviewhstackswiftui-ontapgesture

SwiftUI Horizontal ScrollView onTapGesture is Off


I am experiencing some very odd behavior from a Horizontal ScrollView, it seems as if the clickable frame of the cells are offset. For example, if you click on left half of the first cell it will register the click for the first cell, but if you click the right half of the first cell it will register a click on the second cell. I'm not sure why this is happening.

I've included the code below, you can copy and paste this code into a playground to reproduce the issue. Any ideas here?

struct TestView: View {
    
    var image: String
    
    var body: some View {
            ZStack {
                Image(systemName: image)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 104, height: 168)
                    .cornerRadius(8)
                    .clipped()
                
                VStack {
                    Spacer()
                    Text("Testing")
                        .foregroundColor(Color.white)
                        .frame(alignment: .leading)
                        .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
                        .shadow(radius: 5)

                    Text("1:40")
                        .foregroundColor(Color.white)
                        .frame(alignment: .leading)
                        .padding(EdgeInsets(top: 3, leading: 10, bottom: 10, trailing: 10))
                        .shadow(radius: 5)
                }
                .frame(width: 104, height: 168)
            }
        }
}

struct TestViewCollection: View {
    var collection = ["tv", "faxmachine", "printer"]
    
    var body: some View {
        ScrollView {
            HStack {
                ForEach(collection, id: \.self) { image in
                    TestView(image: image)
                        .onTapGesture {
                            print(image)
                        }
                }
            }
        }
    }
}

PlaygroundPage.current.setLiveView(TestViewCollection())

Solution

  • The issue is that your images are not the same aspect ratio as the frame you have put them in, so they are overflowing the frame. The solution is pretty simple. Instead of using .clipped() which just clips what you can see, use .contentShape() which will will provide an area that the .tapGesture() will use as its tappable area. Like this:

    Image(systemName: image)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(width: 104, height: 168)
        .cornerRadius(8)
        .contentShape(Rectangle())