SwiftUI Button is not tappable if an Image is followed in a VStack

Here is the code:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Button(action: {
                print("Tapped the button")
            }, label: {
                Text("Button")
                    .foregroundStyle(Color.white)
                    .padding()
                    .background(Color.gray)
            })
            .border(Color.green, width: 2)
            
            Image(systemName: "square.and.arrow.up")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .foregroundStyle(Color.black)
                .frame(height: 240)
                .border(Color.blue, width: 2)
                .clipped()
        }
        .padding()
        .border(Color.black, width: 2)
    }
}

#Preview {
    ContentView()
}

The problem is that the button is not tappable.

Test environment: macOS 15.3.2 (24D81), Xcode Version 16.2 (16C5032a), Simulator: iPhone 16 (18.3.1)

However, if we change the code from ".aspectRatio(contentMode: .fill)" to ".aspectRatio(contentMode: .fit)" then the problem goes away.

Any suggestions to fix the problem?

Set the width and it works:

.frame(width: 240, height: 240)

Or change aspect ratio:

 .aspectRatio(contentMode: .fit)// .fill)

Problem is that image (with fill aspect or without width) overlays the button, as you can see with this:

            Image(systemName: "square.and.arrow.up")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .foregroundStyle(Color.black)
                .frame(height: 240)
                .border(Color.blue, width: 2)
                .clipped()
                .onTapGesture {
                    print("Tapped the image")
                }

You can visualise if you do not clip:

            Image(systemName: "square.and.arrow.up")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .foregroundStyle(Color.black)
                .frame(height: 240)
                .border(Color.blue, width: 2)
//                .clipped()

@Claude31 Thanks for the answer. And I found out there is another workaround for this. We can add ".zIndex(1)" to the button.

Accepted Answer

Yes, there are many workarounds, as soon as it is understood that the cause is that clipping just clips the image, but not the view, which then overlays the button and intercept taps.

The problem is not the VStack by itself. But VStack makes Button on Image sit close together, hence the problem. If VStack spacing is increased, no problem.

You can also disable user interaction by adding this modifier to the image:

                .allowsHitTesting(false)

Don't forget to close the thread on the correct answer.

SwiftUI Button is not tappable if an Image is followed in a VStack
 
 
Q