Skip to content

View.allowsHitTesting(false) incorrectly consumes touches on Android#354

Closed
tifroz wants to merge 4 commits intoskiptools:mainfrom
tifroz:codex/allowsHitTesting-android-bugfix
Closed

View.allowsHitTesting(false) incorrectly consumes touches on Android#354
tifroz wants to merge 4 commits intoskiptools:mainfrom
tifroz:codex/allowsHitTesting-android-bugfix

Conversation

@tifroz
Copy link
Contributor

@tifroz tifroz commented Mar 16, 2026

.clickable(enabled: false, onClick: {}) disables the view handlers for touch events but still consumes touches, breaking the iOS contract (where allowsHitTesting(false) should allow touches to reach the lower layers)

I changed allowsHitTesting() to be a no-op on android, and added a unit-test testAllowsHitTestingFalseOverlayPassesTapThrough that must run on a real device/simulator. A no-op is not perfect, but better than doing the opposite it should be doing.

I verified that the new test

  • Fails with the current implementation
  • Succeeds with the proposed change

I think SkiFuseUI simply proxies the call to SkipUI's allowsHitTesting()? If so this fix should apply to SkipFuseUI as well

Skip Pull Request Checklist:

  • REQUIRED: I have signed the Contributor Agreement
  • REQUIRED: I have tested my change locally with swift test
  • OPTIONAL: I have tested my change on an iOS simulator or device
  • OPTIONAL: I have tested my change on an Android emulator or device
  • REQUIRED: I have checked whether this change requires a corresponding update in the Skip Fuse UI repository (link related PR if applicable)
  • OPTIONAL: I have added an example of any UI changes in the Showcase sample app

  • AI was used to generate or assist with generating this PR. Please specify below how you used AI to help you, and what steps you have taken to manually verify the changes.

@cla-bot cla-bot bot added the cla-signed label Mar 16, 2026
@marcprux
Copy link
Member

Just so I understand completely, the correct behavior on iOS for, say, button1 directly on top of button2 would be that if you set button1.allowsHitTesting(false) and tap on it, then the tap would be passed through to button2. The current behavior for Android is that nothing happens at all (neither button is tapped), and the new behavior would be that button1 is tapped (since allowsHitTesting(false) would be ignored). Neither is the correct behavior, and I'm not sure which is less correct except that it doesn't seem right to just enable hit testing when the code explicitly tries to disable it.

Could we get the correct behavior for Android by using something like .pointerInput(Unit, enabled = false) { } (Modifier.pointerInput)?

@tifroz
Copy link
Contributor Author

tifroz commented Mar 17, 2026

The fix I am proposing with this patch is a very limited one - it is nowhere near providing a reasonable Android implementation of allowHitTest(), it only addresses an issue with the current implementation, when a non-interactive View in (e.g. Text View with no touch handler) overlaps an interactive view (e.g. Button View).

For context, Text Views (and other non-button views) are interactive on iOS by default, non-interactive on Android

  • On iOS any view (e.g Text View) covering a button will intercept and consume the touch unless allowHitTest() is applied to the Text View.
  • On Android, a Text View will pass-through touch events unless a touch handler is configured on the view

Current implementation (Android behavior):

  • button1 over button2 => button1 handle the tap
  • button1.allowsHitTest(false) over button2 => button1 disabled + swallows the tap (no-one handles it). THIS IS WRONG BUT NOT FIXED BY THIS PR
  • text1 over button2 => button2 handle the tap
  • text1.allowsHitTest(false) over button2 => text1 swallows the tap (no-one handles). THIS IS WRONG, button2 should handle (THIS IS THE BUG FIXED BY THIS PR)

My understanding its that .pointerInput modifiers can be used to cancel the gesture handler; but would not pass-through to lower z-order siblings.

I have been working on a competing PR that provides a much more thorough implementation of allowHitTest() on Android. It's also a lot more complex. (I should be submitting this next PR soon, it adds more complexity so ...tradeoffs)

@marcprux
Copy link
Member

Superseded by #359

@marcprux marcprux closed this Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants