Skip to content

Fix: floating ball's default position blocks touches even when never shown - #440

Merged
MaatheusGois merged 1 commit into
DebugSwift:mainfrom
Eva0306:fix/floating-ball-touch-passthrough-when-hidden
Aug 28, 2026
Merged

MaatheusGois merged 1 commit into
DebugSwift:mainfrom
Eva0306:fix/floating-ball-touch-passthrough-when-hidden

Conversation

@Eva0306

@Eva0306 Eva0306 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • CustomWindow.point(inside:with:) decides whether to swallow a touch purely by testing it against ballView's frame (DSFloatChat.ballRect, i.e. x: 0, y: screenHeight * 0.3, width: 40, height: 40) - it never checks whether ballView is actually attached to the window.
  • FloatViewManager's setup() sets ballView.frame as soon as FloatViewManager.shared is first accessed (in practice ~1s after DebugSwift.setup() runs, via FeatureHandling's delayed FloatViewManager.setup(...) call), but ballView is only ever added to the window when it's actually toggled visible (FloatBallView.show's didSet calls addSubview only when show == true).
  • Between those two points, ballView has a real frame but is not part of the view hierarchy at all. Any touch landing inside that 40x40pt rect on the screen's left edge is silently swallowed by CustomWindow, even though the float ball has never been shown - a real, invisible dead zone, not just a theoretical edge case.
  • Fix: only honor the ball hit-test when ballView is actually installed in this window (ballView.window === self).

Type of change

  • Fix

Test plan

  • Manual testing completed

Manual test steps

  1. Call DebugSwift.setup() at app launch and never call .show()/.toggle().
  2. Wait ~2s for the delayed FloatViewManager.setup(...) to run (lazily initializes FloatViewManager.shared, which sets ballView.frame).
  3. Tap anywhere inside x: 0...40, y: screenHeight*0.3...screenHeight*0.3+40 (left screen edge, ~30% down).
  4. Before this fix: the tap never reaches the app's own view hierarchy (ballView.point(inside:) returns true purely from frame geometry). After this fix: the tap passes through normally, since ballView.window is nil at that point.

I reproduced this with instrumented logging in a real app build (confirmed rawHit=true, inHierarchy=false at that exact coordinate before the fix, and confirmed touches pass through correctly after applying it), then verified manually in a simulator running the patched build.

Checklist

  • I reviewed my own changes
  • I updated docs when needed
  • I considered backward compatibility

…ven when hidden

CustomWindow.point(inside:with:) decides whether to swallow a touch purely
by testing the point against ballView's frame (DSFloatChat.ballRect), without
checking whether ballView is actually attached to the window.

FloatViewManager.setup() assigns ballView.frame as soon as FloatViewManager.shared
is first accessed (~1s after DebugSwift.setup() runs via FeatureHandling's
delayed FloatViewManager.setup(...) call), but ballView is only added to the
window when show is toggled true (FloatBallView.show's didSet). Until then,
ballView has a real frame but isn't part of the view hierarchy at all.

Any touch inside that 40x40pt rect on the screen's left edge (x: 0,
y: screenHeight * 0.3) is silently swallowed, even though the float ball is
never shown - a real, invisible dead zone whenever DebugSwift.setup() has run
but the ball has never been toggled visible.

Fix: only honor the ball hit-test when ballView is actually installed in this
window (ballView.window === self).
@nippach

nippach commented Aug 27, 2026

Copy link
Copy Markdown

+1 to merging this.

Independent confirmation — this still reproduces on current main (8aece6b, 2026‑08‑15).

We hit this in a production app: a checkbox on our login screen was "impossible to tap unless you found a magic spot". It took a hitTest: sweep under LLDB to find that CustomWindow was eating the touches — nothing is drawn there, so there is no visual clue at all.

Environment: Xcode 26.4.1 (17E202), iPhone 17 Pro simulator, iOS 26.0 (402 × 874 pt).

Reproduced in this repo's own Example app, public API only

debugSwift
    .setup(enableBetaFeatures: [.swiftUIRenderTracking, .networkSessionPersistence, .agentDebugLog])
    .show()

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    self.debugSwift.hide()
}

After the ball visibly disappears:

Tap Result
(25, 355) — left edge of the GraphQL Inspector Test row nothing happens
(200, 355) — same row, further right opens GraphQL Operation Inspector

hitTest: on the CustomWindow instance, sampled every 2 pt (X = window captures, . = passes through):

      x=0                        x=60
y=336 ...............................
y=338 XXXXXXXXXXXXXXXXXXXX...........
y=358 XXXXXXXXXXXXXXXXXXXX...........
y=376 XXXXXXXXXXXXXXXXXXXX...........
y=378 ...............................

Dead rect x 0–40, y 337–377 — exactly ballView.frame, while the window's only subviews are UITransitionView and BottomFloatView. FloatBallView is not in the hierarchy.

Verified this PR's diff

Applied ballView.window === self from this PR to main and re-ran both states:

State Ball rect hit-test Behaviour
Ball shown captures floating button still opens the debugger — unchanged
After hide() passes through (25, 355) now opens GraphQL Operation Inspector

isShowingDebuggerView still short-circuits to true while the debugger UI is open, so nothing regresses there. FloatBallView.isShowing works identically as the guard if you'd prefer reusing the existing helper.

@MaatheusGois
MaatheusGois merged commit 5470362 into DebugSwift:main Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants