From ee59fd35254ab6c9f7a7bf07ee59fbc951e74bcb Mon Sep 17 00:00:00 2001 From: tifroz Date: Sun, 15 Mar 2026 17:02:03 -0700 Subject: [PATCH 1/3] Bug fix: `allowsHitTesting(false)` and Android should allow touches to pass through --- .../SkipUI/View/AdditionalViewModifiers.swift | 25 ++++++++---- Tests/SkipUITests/SkipUITests.swift | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift index 2d3cd424..1188c14d 100644 --- a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift +++ b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift @@ -57,16 +57,27 @@ import struct Foundation.URL #endif extension View { + // SKIP @bridge public func allowsHitTesting(_ enabled: Bool) -> any View { #if SKIP - if enabled { - return self - } else { - return ModifiedContent(content: self, modifier: RenderModifier { - return $0.modifier.clickable(enabled: false, onClick: {}) - }) - } + + // `.clickable(enabled: false, onClick: {})` disables the view handlers for touch events, + // but the touches will be consumed by the view. + // This breaks the contract of allowsHitTesting(false) which should allow touches to pass through. + + // Keep this as a no-op until we have a true pass-through equivalent for SwiftUI's + // `allowsHitTesting(false)` semantics on Android. + + //if enabled { + // return self + //} else { + // return ModifiedContent(content: self, modifier: RenderModifier { + // return $0.modifier.clickable(enabled: false, onClick: {}) + // }) + //} + + return self #else return self #endif diff --git a/Tests/SkipUITests/SkipUITests.swift b/Tests/SkipUITests/SkipUITests.swift index 5d907e28..d4b07731 100644 --- a/Tests/SkipUITests/SkipUITests.swift +++ b/Tests/SkipUITests/SkipUITests.swift @@ -247,6 +247,44 @@ final class SkipUITests: SkipUITestCase { } } + func testAllowsHitTestingFalseOverlayPassesTapThrough() throws { + try testUI(view: { + AllowsHitTestingOverlayView().accessibilityIdentifier("test-view") + }, eval: { rule in + try check(rule, id: "tap-count", hasText: "Taps: 0") + #if SKIP + rule.onNodeWithTag("tap-area").performTouchInput { + click(Offset(Float(100.0), Float(100.0))) + } + try check(rule, id: "tap-count", hasText: "Taps: 1") + #endif + }) + } + + struct AllowsHitTestingOverlayView: View { + @State var tapCount = 0 + + var body: some View { + VStack { + ZStack { + Color.yellow + .opacity(0.2) + .onTapGesture { + tapCount += 1 + } + .accessibilityIdentifier("tap-area") + Text("HUD") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .allowsHitTesting(false) + .accessibilityIdentifier("hud-overlay") + } + .frame(width: 200.0, height: 200.0) + Text("Taps: \(tapCount)") + .accessibilityIdentifier("tap-count") + } + } + } + func testSlider() throws { if isAndroid { throw XCTSkip("Test not working on Android emulator") From 816aca1598157c089f93bd0546baef4e2f40359e Mon Sep 17 00:00:00 2001 From: tifroz Date: Mon, 16 Mar 2026 10:11:38 -0700 Subject: [PATCH 2/3] retrigger CI From c52990b3d955049d5fdbd82ebce014c22c166386 Mon Sep 17 00:00:00 2001 From: tifroz Date: Mon, 16 Mar 2026 12:03:00 -0700 Subject: [PATCH 3/3] Revert "Bug fix: `allowsHitTesting(false)` and Android should allow touches to pass through" This reverts commit ee59fd35254ab6c9f7a7bf07ee59fbc951e74bcb. --- .../SkipUI/View/AdditionalViewModifiers.swift | 25 ++++-------- Tests/SkipUITests/SkipUITests.swift | 38 ------------------- 2 files changed, 7 insertions(+), 56 deletions(-) diff --git a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift index 1188c14d..2d3cd424 100644 --- a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift +++ b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift @@ -57,27 +57,16 @@ import struct Foundation.URL #endif extension View { - // SKIP @bridge public func allowsHitTesting(_ enabled: Bool) -> any View { #if SKIP - - // `.clickable(enabled: false, onClick: {})` disables the view handlers for touch events, - // but the touches will be consumed by the view. - // This breaks the contract of allowsHitTesting(false) which should allow touches to pass through. - - // Keep this as a no-op until we have a true pass-through equivalent for SwiftUI's - // `allowsHitTesting(false)` semantics on Android. - - //if enabled { - // return self - //} else { - // return ModifiedContent(content: self, modifier: RenderModifier { - // return $0.modifier.clickable(enabled: false, onClick: {}) - // }) - //} - - return self + if enabled { + return self + } else { + return ModifiedContent(content: self, modifier: RenderModifier { + return $0.modifier.clickable(enabled: false, onClick: {}) + }) + } #else return self #endif diff --git a/Tests/SkipUITests/SkipUITests.swift b/Tests/SkipUITests/SkipUITests.swift index d4b07731..5d907e28 100644 --- a/Tests/SkipUITests/SkipUITests.swift +++ b/Tests/SkipUITests/SkipUITests.swift @@ -247,44 +247,6 @@ final class SkipUITests: SkipUITestCase { } } - func testAllowsHitTestingFalseOverlayPassesTapThrough() throws { - try testUI(view: { - AllowsHitTestingOverlayView().accessibilityIdentifier("test-view") - }, eval: { rule in - try check(rule, id: "tap-count", hasText: "Taps: 0") - #if SKIP - rule.onNodeWithTag("tap-area").performTouchInput { - click(Offset(Float(100.0), Float(100.0))) - } - try check(rule, id: "tap-count", hasText: "Taps: 1") - #endif - }) - } - - struct AllowsHitTestingOverlayView: View { - @State var tapCount = 0 - - var body: some View { - VStack { - ZStack { - Color.yellow - .opacity(0.2) - .onTapGesture { - tapCount += 1 - } - .accessibilityIdentifier("tap-area") - Text("HUD") - .frame(maxWidth: .infinity, maxHeight: .infinity) - .allowsHitTesting(false) - .accessibilityIdentifier("hud-overlay") - } - .frame(width: 200.0, height: 200.0) - Text("Taps: \(tapCount)") - .accessibilityIdentifier("tap-count") - } - } - } - func testSlider() throws { if isAndroid { throw XCTSkip("Test not working on Android emulator")