Skip to content

fix: let host-app touches through when the survey has no overlay [ENG-1818] - #57

Open
pandeymangg wants to merge 1 commit into
mainfrom
anshuman/eng-1818-ios-sdk-allow-host-app-interaction-when-survey-overlay-is
Open

pandeymangg wants to merge 1 commit into
mainfrom
anshuman/eng-1818-ios-sdk-allow-host-app-interaction-when-survey-overlay-is

Conversation

@pandeymangg

Copy link
Copy Markdown
Contributor

Ref ENG-1818

Depends on formbricks/formbricks#9242. The rect arrives from the surveys renderer, which this SDK fetches from the server at render time — so that PR has to merge and deploy before this does anything. Until then this SDK falls back to its current behaviour of taking every touch, which is why the two can ship independently.

What & why

Was: a survey with overlay: none painted nothing over the host app but still swallowed every touch, so the app looked frozen until the survey closed. overlay is the default, so this hit every workspace that never configured one.

Now: only the survey card takes touches; everything else reaches the host app.

The renderer already sets pointer-events: none outside the card, but that is a web hit test. WKWebView hit-tests its whole rectangle and UIKit never sees it — and the survey sat in a full-screen presented view controller on top.

Where to look

  • WebView/SurveyTouchRegion.swift — the three states, and PassthroughWindow.hitTest
  • Manager/PresentSurveyManager.swift — why overlay: none gets a window instead of present()
  • WebView/FormbricksViewModel.swift — the onCardRectChange bridge function
Three design points worth checking

A window, not a presented controller. Returning nil from a window's hitTest makes UIKit continue to the next window down — the host app's. A presented view controller cannot do that: UIKit's transition container answers the hit test for the whole screen even when the content declines the touch.

Three states, not two. "No rect has ever arrived" and "the card is not on screen" need opposite answers. An older self-hosted server serves a renderer that never calls onCardRectChange, and the SDK must keep taking every touch as it does today. Treating that as "claim nothing" is exactly what made the Flutter SDK's card untappable when its DOM probe stopped matching.

Shown, not made key. makeKeyAndVisible() would pull the caret out of whatever the host app has focused — the complaint this fixes. UIKit promotes the window once the user taps into the survey, so text input still works.

No native overlay is painted; that stayed the renderer's job in #39. This changes hit testing only.

Coverage

Behaviour Level
overlay: none — host app usable outside the card, survey usable inside manual — simulator, local server on the #9242 branch
overlay: dark — backdrop visible, host app blocked manual — unchanged modal path
Closing leaves no intercepting view manual + unit — window torn down; null rect maps to claiming nothing
Hit testing in all three overlay modes unit — SurveyTouchRegionTests
No rect ever reported falls back to blocking, as before unit — testTheDefaultBeforeAnyRectArrivesBlocksLikeBefore
Bridge payload decodes, rect: null included unit — same file

Rerun: xcodebuild test -scheme FormbricksSDK -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest'

Full suite: 122 tests, 0 failures.

Open gaps

  • Simulator only, no physical device. Worth a device pass for the keyboard case in particular, since that depends on UIKit promoting a non-key window.
  • iPad untested. Placement classes (sm:) only apply at ≥640pt, so the card is positioned differently there and the rect path is the same code but has not been run.
  • Not tested against a server serving an older renderer. The fallback is unit-tested, not exercised end to end.

Breaking changes

  • This is a breaking change

No public API change. overlay: none surveys stop blocking the host app, which is the fix — a host relying on that block would be relying on the bug.


Note

AI model usedclaude-opus-5, reasoning effort unknown.

…-1818]

A survey configured with `overlay: none` painted nothing over the host app but
still took every touch, so the app appeared frozen until the survey closed.
The renderer already sets `pointer-events: none` outside the card, but that is
a web hit test: `WKWebView` hit-tests its whole rectangle and UIKit never sees
it, and the survey was presented as a full-screen view controller on top.

`overlay: none` now gets its own `PassthroughWindow`, masked to the card's rect
as the renderer reports it over `onCardRectChange`. Declining a touch there
lets UIKit carry on to the host app's window underneath, which a presented view
controller cannot do — UIKit's transition container answers for the whole
screen even when the content declines. `light` and `dark` keep the existing
modal presentation: a visible backdrop is meant to block the host app.

No native overlay is painted. That stayed the renderer's job in #39 and still
is; this changes hit testing only.

Three states, not two, because "no rect has ever arrived" and "the card is not
on screen" need different answers. An older self-hosted server serves a
renderer that never calls `onCardRectChange`, and the SDK has to keep taking
every touch exactly as it used to. Treating that as "claim nothing" is what
made the Flutter SDK's survey card untappable when its DOM probe stopped
matching.

The window is shown rather than made key: taking key status would pull the
caret out of whatever the host app has focused, which is the complaint this
fixes. UIKit promotes it once the user taps into the survey, so text input
still works.
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: a946f3a2-46b6-4899-9a57-542a5e0d53e5


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
50.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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.

1 participant