Add presentationMode option to control paywall modal presentation style#1726
Merged
Merged
Conversation
Adds an opt-in `presentationMode` key to `PaywallProxy.PaywallOptionsKeys` (pageSheet, formSheet, fullScreen, automatic) so hybrid SDKs can control the `UIModalPresentationStyle` used when presenting a paywall. Motivation: on iPad the hardcoded `.pageSheet` presents a large sheet that covers most of the screen (purchases-unity#976). `.formSheet` presents the smaller, centered modal customers expect. This exposes that choice without changing the default: when `presentationMode` is absent it falls back to the existing `useFullScreenPresentation` boolean and defaults to `.pageSheet`, so existing integrations (RN, Flutter, Capacitor, Unity) are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016iVCuCLTGx1MWbF2p24LdT
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016iVCuCLTGx1MWbF2p24LdT
facumenzella
marked this pull request as ready for review
July 8, 2026 10:55
Adds unit coverage for the string->UIModalPresentationStyle resolution introduced by the presentationMode option: token mapping, case insensitivity, the "sheet" alias, presentationMode-over-legacy precedence, unrecognized/non-String fallthrough, and the neither-key backwards-compat default (.pageSheet). To reach the resolver via @testable import, PaywallPresentationParams, Content, and resolveModalPresentationStyle are relaxed from private to internal (nothing becomes public, so no API change). The existing PurchasesHybridCommonTests target now links PurchasesHybridCommonUI (and RevenueCatUI, which it transitively needs), so the tests run in the existing fastlane scan CI job with no new target or CI wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01894Y6cNoGsxxfmRHz8bu6i
vegaro
approved these changes
Jul 21, 2026
Member
Author
|
Unity follow-up (draft) that consumes this: RevenueCat/purchases-unity#995. It adds |
This was referenced Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
presentationModeoption toPaywallProxyso hybrid SDKs can choose theUIModalPresentationStyleused when presenting a paywall (pageSheet,formSheet,fullScreen,automatic).It's tricky because it's a iOS thing only.
on iPad the presentation style is hardcoded to
.pageSheet, which renders a large sheet covering most of the screen (reported in purchases-unity#976). Customers expect the smaller, centered modal (.formSheet), which is what the native SDK's exit offer already shows. This PR exposes that choice without changing the default.Precedence (backwards compatible):
presentationModeif present and recognized.useFullScreenPresentationboolean (true→.fullScreen)..pageSheet(unchanged default).Any caller that does not send
presentationModegets the same behavior as today on every device, including RN, Flutter, Capacitor, and Unity. Nothing changes unless a hybrid explicitly opts in.Why
.formSheetand not just flipping the defaultVerified on an iPad Pro 11" / iOS 26 simulator (see videos), presenting a
PaywallViewControllerwith each style (mirrors this file'srootController.present(controller)path):.pageSheet(current default).formSheet.automatic.pageSheeton earlier iPadOSSo
.formSheetis the version-stable value for the small modal;.automaticexplains why the native exit offer (which sets no explicit style) already renders small on iOS 18+.Videos
Recorded on iPad Pro 11" / iOS 26. Each clip shows the full flow: open paywall → dismiss → exit offer. Drop each
.mp4into the slot directly below its heading (GitHub only accepts media via web upload).Before — bug (
.pageSheet): main paywall opens large, but the exit offer is the correct small modal — the inconsistency reported in #976.bug_exit_flow.mp4
After — fix (
.formSheet): main paywall opens as a small modal, and the exit offer matches — consistent.fix_exit_flow_clip.mp4
Note: the debug copy / "SAVE 85%" in the exit offer is that test paywall's own content, not part of the fix. The fix controls the main paywall's style; the exit offer is presented by the SDK as
.automatic(already small on iOS 18+).Scope / follow-up
This is the purchases-hybrid-common half only. It does not by itself fix #976 for Unity users. The Unity follow-up wires the new option through:
IOSPaywallPresentationStyle.FormSheet(and optionally.Automatic) in Unity'sPaywallPresentationConfiguration.presentationModeviaRevenueCatUI.m/IOSPaywallPresenter.cs.Sheettoken ("sheet") already maps to.pageSheethere, so wiring it is safe.AI session context
Visual companion: https://claude.ai/code/artifact/8feedede-b300-406e-83fb-1d0ffa8f0b57
AI Context
Metadata
facundo/fix-ipad-paywall-presentation-styleGoal
Fix purchases-unity#976: on iPad,
PaywallsPresenter.Present()opens the paywall as a large sheet instead of the small modal customers expect (the native exit offer renders correctly small). Provide a retrocompatible way to control iPad presentation.Initial Prompt
"Check RevenueCat/purchases-unity#976" — investigate the iPad large-paywall report and determine root cause and fix. Later refined to: make presentation configurable per platform without changing the default; reproduce the bug on iOS without Unity; then open a draft PR in purchases-hybrid-common with before/after videos.
Important Follow-up Prompts
Agent Contribution
PaywallProxy.swifthardcodescontroller.modalPresentationStyle = useFullScreenPresentation ? .fullScreen : .pageSheet..pageSheetis large on iPad; the native exit offer (purchases-iosPaywallViewController.swift) sets no style (.automatic)..pageSheetvs.formSheetvs.automaticdistinction in Apple docs; confirmed.automaticresolves to.formSheeton iOS 18+ (SDK header),.pageSheetearlier.PaywallViewControllerwith each style and captured screenshots + videos on iPad Pro 11" / iOS 26 (harness reverted afterward; purchases-ios left clean).presentationModeoption with a backwards-compatible precedence chain; builtPurchasesHybridCommonUIsuccessfully.Human Decisions
.formSheetdefault and a colleague's "iPad default → fullScreen" proposal) because either changes behavior for existing hybrid users; chose opt-in.Key Implementation Decisions
presentationMode; keep.pageSheetdefault..automatic/.formSheet(breaks existing iPad behavior for all hybrids); colleague's iPad-default-.fullScreen(still a breaking iPad change and wrong direction for the report)..formSheetfor the small modal, not.automatic..automaticis small only on iOS 18+ and regresses to.pageSheeton older iPadOS."sheet"as a.pageSheetalias.IOSPaywallPresentationStyle.Sheettoken so the follow-up can't silently no-op.Files / Symbols Touched
ios/PurchasesHybridCommon/PurchasesHybridCommonUI/Paywalls/PaywallProxy.swiftPaywallOptionsKeys.presentationMode,PaywallPresentationParams.modalPresentationStyle,PaywallPresentationParams.resolveModalPresentationStyle(from:),privatePresentPaywall.Dependencies / Config / Migrations
Validation
xcodebuild -workspace PurchasesHybridCommon.xcworkspace -scheme PurchasesHybridCommonUI -configuration Debug -destination "id=<iPad sim>" build: BUILD SUCCEEDED..pageSheet= large (repro),.formSheet= small modal (fix),.automatic= small (identical to formSheet). Videos attached.Validation Gaps
modalPresentationStyle; they were produced via a purchases-ios harness and do not exercise this PR'spresentationMode→resolveModalPresentationStyleparsing path.PurchasesHybridCommonUItest target exists; the pureresolveModalPresentationStylecould be extracted and tested if desired).Review Focus
.pageSheet) correct and truly non-breaking for callers that send neither key, or send only the legacy boolean?Risks / Reviewer Notes
"sheet"alias added specifically to match Unity's existing token.Non-goals / Out of Scope
FormSheetAPI (separate follow-up PR).Omitted Context
Note
Medium Risk
Changes paywall presentation behavior only when new options are passed; default remains
.pageSheet, but incorrect hybrid wiring could surprise iPad layouts.Overview
Adds an opt-in
presentationModepaywall option so hybrid SDKs can setUIModalPresentationStyle(pageSheet,formSheet,fullScreen,automatic, plussheetas apageSheetalias) instead of only toggling full screen viauseFullScreenPresentation.PaywallProxynow resolves style throughresolveModalPresentationStyle: recognizedpresentationModewins, then the legacy boolean, then.pageSheetwhen neither is set—so existing callers keep today’s default unless they opt in.Unit coverage lands in
PaywallPresentationStyleTests; the test target linksPurchasesHybridCommonUIand pullsRevenueCatUIin the Podfile so@testable importworks.Reviewed by Cursor Bugbot for commit 3e500af. Bugbot is set up for automated code reviews on this repo. Configure here.