The clipped()
modifier in SwiftUI clips a view to its bounds, hiding any out-of-bounds content material. However be aware that clipping doesn�t have an effect on hit testing; the clipped view can nonetheless obtain faucets/clicks outdoors the seen space.
I examined this on iOS 16.1 and macOS 13.0.
Instance
Right here�s a 300�300 sq., which we then constrain to a 100�100 body. I additionally added a border across the outer body to visualise the views:
Rectangle()
.fill(.orange.gradient)
.body(width: 300, top: 300)
// Set view to 100�100 ? renders out of bounds
.body(width: 100, top: 100)
.border(.blue)
SwiftUI views don�t clip their content material by default, therefore the total 300�300 sq. stays seen. Discover the blue border that signifies the 100�100 outer body:
Now let�s add .clipped()
to clip the big sq. to the 100�100 body. I additionally made the sq. tappable and added a button:
VStack {
Button("You'll be able to't faucet me!") {
buttonTapCount += 1
}
.buttonStyle(.borderedProminent)
Rectangle()
.fill(.orange.gradient)
.body(width: 300, top: 300)
.body(width: 100, top: 100)
.clipped()
.onTapGesture {
rectTapCount += 1
}
}
Once you run this code, you�ll uncover that the button isn�t tappable in any respect. It’s because the (unclipped) sq., regardless of not being totally seen, obscures the button and �steals� all faucets.

The repair: .contentShape()
The contentShape(_:)
modifier defines the hit testing space for a view. By including .contentShape(Rectangle())
to the 100�100 body, we restrict hit testing to that space, making the button tappable once more:
Rectangle()
.fill(.orange.gradient)
.body(width: 300, top: 300)
.body(width: 100, top: 100)
.contentShape(Rectangle())
.clipped()
Observe that the order of .contentShape(Rectangle())
and .clipped()
may very well be swapped. The vital factor is that contentShape
is an (oblique) mum or dad of the 100�100 body modifier that defines the dimensions of the hit testing space.
Video demo
I made a brief video that demonstrates the impact:
- Initially, faucets on the button, and even on the encompassing whitespace, register as faucets on the sq..
- The highest change toggles show of the sq. earlier than clipping. This illustrates its hit testing space.
- The second change provides
.contentShape(Rectangle())
to restrict hit testing to the seen space. Now tapping the button increments the button�s faucet rely.
The total code for this demo is obtainable on GitHub.
Abstract
The clipped()
modifier doesn�t have an effect on the clipped view�s hit testing area. The identical is true for clipShape(_:)
. It�s typically a good suggestion to mix these modifiers with .contentShape(Rectangle())
to deliver the hit testing logic in sync with the UI.