Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by StellarScientist758

How can I fix incorrect tap detection on rounded rectangle images in SwiftUI?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I’m displaying images as rounded rectangles in a horizontal scroll view, and tapping an image should open it in full screen. However, when I tap the red part of Image A (which has two shades), the app mistakenly opens the wider Image B. Tapping the yellow part of Image A correctly opens Image A.

This issue appears to be related to using image.resizable().aspectRatio(contentMode: .fill), which fills the view and may extend the hit-test area beyond the rounded bounds. When I use .aspectRatio(contentMode: .fit), the tap behavior is correct, but I lose the rounded rectangle appearance.

Below is my executable sample code:

SWIFT
import SwiftUI struct Foo { var title: String var url: String var image: Image? init(title: String, url: String, image: Image? = nil) { self.title = title self.url = url self.image = image } } struct ContentViewA: View { @State private var data = [ Foo(title: "Image A", url: "https://www.shutterstock.com/image-illustration/two-shades-color-background-mix-260nw-2340299851.jpg", image: nil), Foo(title: "Image B", url: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Sydney_Harbour_Bridge_night.jpg/800px-Sydney_Harbour_Bridge_night.jpg", image: nil) // Foo(title: "Image B", url: "https://www.shutterstock.com/image-photo/ultra-wide-photo-mountains-river-260nw-1755037052.jpg", image: nil) /// There are more images in the array in real code. ] var body: some View { ZStack { Color.black.opacity(0.7).ignoresSafeArea() VStack { ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .center, spacing: 10) { ForEach(Array(data.enumerated()), id: \ .offset) { index, item in if let urlObject = URL(string: item.url) { AsyncImage(url: urlObject, scale: 1.0, transaction: Transaction(animation: .spring(response: 0.5, dampingFraction: 0.65, blendDuration: 0.025)), content: { renderPhoto(phase: $0, item: item, index: index) }) } else { /// Note: Shows placeholder view EmptyView() } } } .padding(.leading, 0) .padding(.trailing, 16) .frame(maxWidth: .infinity, minHeight: 65, maxHeight: 65, alignment: .topLeading) } } .padding([.top, .bottom], 150.0) .padding([.leading, .trailing], 50.0) } } @ViewBuilder private func renderPhoto(phase: AsyncImagePhase, item: Foo, index: Int) -> some View { switch phase { case .success(let image): thumbnailView(image: image, item: item, index: index) case .failure(let error): thumbnailView(item: item, index: index, isFailure: true) case .empty: thumbnailView(item: item, index: index, isFailure: true) @unknown default: EmptyView() } } private func thumbnailView(image: Image? = nil, item: Foo, index: Int, isFailure: Bool = false) -> some View { VStack { Rectangle() .foregroundColor(.clear) .frame(width: 72, height: 55) .background( VStack { if let image = image { image.resizable() .aspectRatio(contentMode: .fill) // .aspectRatio(contentMode: .fit) /// Setting aspect ratio to fit avoids the problem, but doesn't give rounded rectangle look. .frame(width: 72, height: 55) .disabled(false) .clipped() } else { /// show error image EmptyView() } } ) .cornerRadius(8) .padding([.top, .bottom], 10.0) .onTapGesture { print("%%%%% Tapped image title: \(item.title) and index is: \(index) %%%%%%") } } } }

Screenshots showing current behavior:

Rounded rectangle images with aspectRatio(.fill):
Tapping the red part of Image A opens Image B (because Image B is wider).

enter image description here

Images with aspectRatio(.fit):
Tapping Image A and Image B correctly opens the respective images, but the desired rounded rectangle look is lost.

enter image description here

How can I achieve the rounded rectangle appearance while ensuring that tapping anywhere on Image A only opens Image A?

1

Comments 0

Answers 2

Views 57

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

You’re most likely running into an issue with SwiftUI’s hit-testing. When you use aspectRatio(.fill), the image fills the view and—even though it’s clipped visually—the hit test area may extend to the full area of the underlying view. As a result, the tap gesture “leaks” into the adjacent view when the image overflows its bounds.

A common solution is to explicitly set the hit-test shape using the contentShape modifier. For example, if you want the tap area to be limited to a rounded rectangle matching your visual clipping, you can add a contentShape with the same rounded rectangle:

SWIFT
private func thumbnailView(image: Image? = nil, item: Foo, index: Int, isFailure: Bool = false) -> some View { VStack { Rectangle() .foregroundColor(.clear) .frame(width: 72, height: 55) .background( VStack { if let image = image { image.resizable() .aspectRatio(contentMode: .fill) .frame(width: 72, height: 55) .clipped() } else { // show error image EmptyView() } } ) .cornerRadius(8) // Define the hot area to match the visible rounded rectangle .contentShape(RoundedRectangle(cornerRadius: 8)) .padding([.top, .bottom], 10.0) .onTapGesture { print("%%%%% Tapped image title: \(item.title) and index is: \(index) %%%%%%") } } }

How It Works

  • Clipping & Corner Radius: The image is clipped to a rounded rectangle visually with .cornerRadius(8).
  • contentShape: By adding .contentShape(RoundedRectangle(cornerRadius: 8)), you explicitly restrict the tap detection area to the same rounded rectangle.
  • Result: Even though aspectRatio(.fill) makes the image fill and possibly exceed the frame (if not clipped), the tappable area remains within the bounds of the rounded rectangle. That way, tapping within the bounds of the first image always registers correctly.

This approach lets you maintain the desired rounded rectangle appearance while ensuring that taps are handled on the right image view.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by GalacticCollector579 1 month ago

0

I've run into this behavior before. clipped() may visually clip the view, but it still accepts clicks/taps.

To solve this, you can add .allowsHitTesting(false) to the Image.

Then, you'll need to add .contentShape(.rect) (or a rounded rect if you want) to your Rectangle, since otherwise, the clear color it has means that it won't accept hits.

Here's the modified thumbnail view:

SWIFT
private func thumbnailView(image: Image? = nil, item: Foo, index: Int, isFailure: Bool = false) -> some View { VStack { Rectangle() .foregroundColor(.clear) .frame(width: 72, height: 55) .background( VStack { if let image = image { image.resizable() .aspectRatio(contentMode: .fill) .frame(width: 72, height: 55) .clipped() .allowsHitTesting(false) // <-- Here } else { /// show error image EmptyView() } } ) .cornerRadius(8) .padding([.top, .bottom], 10.0) .contentShape(.rect) // <-- Here .onTapGesture { print("%%%%% Tapped image title: \(item.title) and index is: \(index) %%%%%%") } } }

No comments yet.

Discussion

No comments yet.