Skip to main content
Question

Intro-offer condition unresolved in first paywall render (eligibility cached)

  • August 8, 2026
  • 6 replies
  • 39 views

Forum|alt.badge.img

Components-based paywall: conditional intro-offer row is missing in the first
render, even though eligibility is already cached

## Setup
- purchases-ios 5.70.0, SwiftUI, iOS 18
- Components-based paywall built in the editor, presented with
  RevenueCatUI.PaywallView(offering:displayCloseButton:)
- In the editor, a text row ("7 days free trial") is hidden by default and
  shown only when the customer is eligible for the introductory offer, by paywall logic.
- Product identifiers below are redacted.

## Problem
The paywall opens with that row missing and shows it one render pass later.
There is no transition or animation involved — the row simply is not there in
the first frame and is there in the next.

The plan card grows upward, so nothing below it moves and the purchase button
stays in place. It is purely cosmetic. Still, what appears late is the offer
itself, which is the one line we would want visible from the very first frame.

## What I tried
Resolving eligibility before rendering, so the answer is already in the SDK
cache by the time PaywallView is created:

    let ids = offering.availablePackages.map(\.storeProduct.productIdentifier)
    _ = await Purchases.shared.checkTrialOrIntroDiscountEligibility(
        productIdentifiers: ids
    )
    // only afterwards set the offering that drives PaywallView

The same call also runs at app start, right after offerings().

## What the log shows
Both lookups are served from cache — there is no network round trip:

    Vending Offerings from memory cache
    Found cached trial or intro eligibility for products:
        ["pro_yearly", "pro_monthly"]
    ...
    Found cached trial or intro eligibility for products:
        ["pro_monthly", "pro_yearly"]

The second line is the paywall's own evaluation after presentation. So the
data is available immediately, and the row still only appears in the second
render pass.

## Questions
1. Why does a components-based paywall perform an initial render with the
   intro-offer condition unresolved, even when eligibility is already in the
   SDK cache before PaywallView is created?
2. Is there a way to have the first render include the resolved condition —
   an API to hand the eligibility in, or an option to defer presentation
   until it is known?
3. If this is expected behaviour, is there a recommended pattern for
   conditional content on a paywall so that it does not change after the
   first frame?

6 replies

Forum|alt.badge.img
  • Author
  • New Member
  • August 9, 2026

Update: still reproducible on 5.83.1.

The SDK now warms the eligibility cache itself ("Warming up intro
eligibility cache for 2 products", added in 5.75.0), and the app resolves it
again before creating PaywallView. The log confirms the answer is present:

    Intro eligibility — <yearly>: eligible, <monthly>: noIntroOfferExists

The conditional row appears sooner than on 5.70.0, but still only in the
second render pass — it is not in the first frame.

Possibly related: the SDK logs


    WARN: Received unknown workflow trigger type: on_purchase_press

so the dashboard appears to emit configuration this SDK version does not
implement.


matt-heaney
RevenueCat Staff
Forum|alt.badge.img+6
  • RevenueCat Staff
  • August 10, 2026

Hey there!

Matt from RevenueCat here!

Thank you for the detailed write-up here, and for retesting this on 5.83.1.

Recent versions of the iOS SDK introduced a loading state for paywalls while introductory offer eligibility is being determined. During this, it can be expected for the first frame to briefly show redacted/placeholder text, before updating once the eligibility result is available.

Prefetching the eligibility, or warming the cache beforehand, can help make this faster, but it won’t completely remove this initial loading state.

However, I’d love to take a closer look at exactly what you’re seeing here. This will really help us debug what is happening and offer the best next steps. I’ve been testing your paywall in my sample project, and on my end, the intro text appears after the initial redacted text state. However, it sounds like you may be seeing something slightly different.

Could you please open a support ticket through the RevenueCat dashboard (link) and ask for me (Matt), and attach a short screen recording showing the paywall opening? I’ll be happy to pick this up from there and investigate further.

Once I can see exactly what is happening on that first frame, I’ll be able to confirm whether this is the expected loading behavior or something we still need to investigate on our end.

Thanks!


Forum|alt.badge.img
  • Author
  • New Member
  • August 11, 2026

Hi Matt, thanks for picking this up.

To be clear, I don't think this is a bug, and I'm not asking for the loading state to be removed — for a genuine cold start with no cached eligibility it is exactly right.

My point is about when the condition is resolved in the case where the answer is already known. I call checkTrialOrIntroDiscountEligibility before presenting the paywall, and the logs confirm the value is then served from cache ("Found cached trial or intro eligibility"). Even so, the conditional row is not part of the first frame — it is added one pass later, which makes the plan card grow by a line after the paywall is already on screen.

Worth noting that a placeholder can't fix this, because the condition cuts both ways: reserving the row would simply move the jump to users who are not eligible, whose card would then shrink by a line instead. The only thing that avoids a change after presentation is knowing the answer before the first frame — which, in this case, the SDK does.

From the outside it looks as if the paywall starts evaluating its conditions (paywall logic) only after the view appears, rather than using what is already cached at presentation time. If that resolution happened up front whenever a cached value exists, the paywall could render in its final shape right away, in both directions.

Happy to open a support ticket with a screen recording if that's useful.

Best wishes
Florian


matt-heaney
RevenueCat Staff
Forum|alt.badge.img+6
  • RevenueCat Staff
  • August 12, 2026

Hey ​@flow!

Thanks again for the additional context! I’ve been digging much deeper into this, and I’d love to provide some more context.

Your understanding here is correct. Prefetching the eligibility using checkTrialOrIntroDiscountEligibility warms the Purchases cache, which is what the “Found cached trial or intro eligibility” logs are showing.

However, the paywall still resolves its own eligibility state after the initial render. This means that even when the eligibility is already cached, an intro offer visibility condition can still be applied just after the paywall appears, causing the layout change you’re seeing.

This approach has been discussed internally before. Under the current design, the paywall applies intro eligibility after it appears. The UI initially renders without that state and then resolves it asynchronously, using the cache when available. So, while a warm Purchases cache speeds up that follow-up, it doesn’t change the initial layout.

This is why the row can still be missing on the first frame, even when you’ve already warmed the cache. The paywall doesn’t currently use that cached eligibility when calculating its initial layout.

The best option to avoid this layout change today would be to avoid using an intro offer condition to control the visibility of the main offer line. Instead, keeping the row visible and change the copy, or use offer variables depending on eligibility. This prevents the layout from changing once eligibility is applied.

That means this doesn’t appear to be a cache miss on your side. It’s a limitation of how eligibility conditions are currently resolved within the paywall. I’m sorry, I understand that may be disappointing news.

The team is always looking for ways to improve Paywalls, and I can make sure this case is included in future discussions around this behavior.

I hope this helps clarify what’s happening! If you have any additional questions, please let me know and I’ll be happy to keep digging.

Thanks,


Forum|alt.badge.img
  • Author
  • New Member
  • August 12, 2026

Hey Matt,

thanks for the clear answer — that settles it. Knowing that the initial layout is computed without the cached eligibility was exactly the piece I was missing.

Thanks for taking the time.

Florian


matt-heaney
RevenueCat Staff
Forum|alt.badge.img+6
  • RevenueCat Staff
  • August 12, 2026

Hey ​@flow,

I’m so glad to hear the information helped, and I’m delighted to have answered your questions!

As always, if there is anything else we can do to help, please don’t hesitate to reach out!

Thanks,