Search code examples
iosswiftimageswiftuilayout

SwiftUI Image with contentMode .fill overlapping views


Consider this simple CardView in SwiftUI:

struct CardView: View {
    let cardNumber: Int
    
    var body: some View {
        VStack {
            Image("bg1")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(height: 100)
                .clipped()

            Spacer()

            Text("Card: \(cardNumber.description)")
                .font(.title.bold())

            Button("Test") { print("Tapped") }
                .buttonStyle(.bordered)
        }
        .background(.green)
        .frame(height: 260)
        .border(.blue)
    }
}

Single CardView

The VStack is set to height 260 and the Image is set to height 100 and with the content model .fill. I understand that we need to set .clipped() to prevent the image from extend beyond the view's bounds.

If I render this view alone, everything works fine i.e. tap the button and print "Tapped".

However, if I'd render multiple CardView, for example put this CardView inside a VStack or ScrollView. The clipped image will start overlapping and the Test button will no-longer work.

From the UI debugger I can see it render as below inside the VStack. CardView inside a VStack

Could someone please explain why this is happening and how to fix it?


Solution

  • I realized that .clipped family will not prevent the Image itself from tap action, you need to add .contentShape, which makes the Image not overwhelm the surrounding context:

    Image("bg1")
        ...
        .contentShape(Rectangle()) //<- here