- Published on
> Disable Touches with allowsHitTesting in SwiftUI
- Authors

- Name
- Mick MacCallum
- @0x7fs
Sometimes you need a view to be visible but not interactive. Maybe you have an overlay that shouldn't block touches, a decorative element on top of buttons, or a loading state that should let users interact with content behind it. The allowsHitTesting(_:) modifier controls whether a view participates in hit testing, letting you make views "transparent" to touches.
Basic Usage
When you set allowsHitTesting(false) on a view, taps pass right through it as if it wasn't there:
struct OverlayExample: View {
var body: some View {
ZStack {
Button("Tap Me") {
print("Button tapped!")
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
Rectangle()
.fill(Color.red.opacity(0.3))
.allowsHitTesting(false)
}
}
}
The red overlay covers the button visually, but taps go right through to the button underneath. Without .allowsHitTesting(false), the rectangle would intercept all touches.
Difference from disabled
You might wonder how this differs from the .disabled() modifier. The key difference is what happens to touches:
VStack(spacing: 20) {
// disabled: button looks dimmed, absorbs taps (nothing happens)
Button("Disabled Button") { print("Won't print") }
.disabled(true)
// allowsHitTesting(false): button looks normal, taps pass through
Button("No Hit Testing") { print("Won't print") }
.allowsHitTesting(false)
}
With .disabled(true), the view still receives the touch but ignores it. With .allowsHitTesting(false), the view doesn't receive the touch at all—it continues to whatever is behind. Also, .disabled() changes the view's appearance to indicate it's inactive, while .allowsHitTesting() has no visual effect.
Practical Example: Decorative Overlays
A common use case is adding visual effects that shouldn't interfere with interaction:
struct GlowingButton: View {
@State private var isGlowing = false
var body: some View {
ZStack {
Button("Start") {
print("Started!")
}
.padding(.horizontal, 40)
.padding(.vertical, 15)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
if isGlowing {
RoundedRectangle(cornerRadius: 10)
.stroke(Color.blue, lineWidth: 2)
.frame(width: 130, height: 50)
.scaleEffect(1.2)
.opacity(0.5)
.allowsHitTesting(false)
}
}
.onAppear {
withAnimation(.easeInOut(duration: 1).repeatForever()) {
isGlowing = true
}
}
}
}
The pulsing glow effect sits on top of the button but doesn't prevent taps from reaching it.
Conditional Hit Testing
You can make hit testing conditional based on state:
struct ConditionalInteraction: View {
@State private var isLocked = true
var body: some View {
VStack(spacing: 20) {
Toggle("Unlock Controls", isOn: Binding(
get: { !isLocked },
set: { isLocked = !$0 }
))
.padding()
VStack(spacing: 15) {
Button("Action 1") { print("Action 1") }
Button("Action 2") { print("Action 2") }
Button("Action 3") { print("Action 3") }
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(12)
.allowsHitTesting(!isLocked)
.opacity(isLocked ? 0.5 : 1)
}
}
}
When locked, the entire button group ignores touches. The opacity change provides visual feedback that the controls are inactive.
Loading Overlay Pattern
A semi-transparent loading overlay that still allows some interaction:
struct LoadingOverlay: View {
@State private var isLoading = false
@State private var count = 0
var body: some View {
ZStack {
VStack(spacing: 20) {
Text("Count: \(count)")
.font(.title)
Button("Increment") {
count += 1
}
.buttonStyle(.borderedProminent)
Button("Simulate Load") {
isLoading = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isLoading = false
}
}
}
if isLoading {
Color.black.opacity(0.3)
.ignoresSafeArea()
.allowsHitTesting(true) // blocks all interaction
ProgressView()
.scaleEffect(1.5)
.tint(.white)
}
}
}
}
During loading, the overlay has allowsHitTesting(true) (the default) which blocks all touches. Change it to false if you want users to still interact while loading.
Passthrough Gesture Areas
Create views with specific interactive and non-interactive zones:
struct PartialInteraction: View {
var body: some View {
ZStack {
// Background content that should receive taps
VStack {
ForEach(0..<5) { i in
Button("Item \(i)") {
print("Tapped item \(i)")
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue.opacity(0.1))
}
}
// Floating panel that blocks only its area
VStack {
Spacer()
HStack {
Spacer()
VStack {
Text("Quick Actions")
.font(.headline)
Button("Share") { }
Button("Save") { }
}
.padding()
.background(.regularMaterial)
.cornerRadius(12)
.padding()
}
}
}
}
}
The floating panel naturally blocks touches in its area while the rest of the screen remains interactive.
Hit Testing with Animations
Be careful with animated views—you might want hit testing to follow the animation:
struct AnimatedOverlay: View {
@State private var showOverlay = false
var body: some View {
ZStack {
Button("Main Action") {
print("Main action!")
}
.buttonStyle(.borderedProminent)
Rectangle()
.fill(Color.black.opacity(showOverlay ? 0.5 : 0))
.allowsHitTesting(showOverlay)
.ignoresSafeArea()
Button("Toggle Overlay") {
withAnimation {
showOverlay.toggle()
}
}
.offset(y: 100)
}
}
}
The allowsHitTesting value changes immediately with the state, even though the opacity animates. This is usually what you want—interaction should toggle instantly, not gradually.
Important Notes
The allowsHitTesting modifier affects the view and all its children. If you need different hit testing behavior for child views, apply the modifier at the appropriate level:
VStack {
Button("This is tappable") { }
Button("This is not") { }
.allowsHitTesting(false)
Button("This is also tappable") { }
}
Also remember that allowsHitTesting(false) only affects touches—the view still renders normally and takes up space in the layout. It's purely about whether the view responds to user interaction.
Use allowsHitTesting whenever you need visual overlays that don't block interaction, or when you want to selectively enable and disable touch handling without changing a view's appearance.
// Continue_Learning
Control Tappable Area with contentShape in SwiftUI
Learn how to use the contentShape modifier to expand or customize the tappable area of your SwiftUI views, making buttons and interactive elements more user-friendly.
Detect when a context menu is open in SwiftUI
Learn how to detect when a context menu is open in SwiftUI.
Supporting Dark Mode in a SwiftUI App
Learn how to properly support dark mode in SwiftUI using semantic colors, adaptive color assets, and color scheme detection.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.