Skip to main content
Question

V2 paywall keeps downloading custom fonts that are already bundled via UIAppFonts, and registration then fails on iOS

  • August 11, 2026
  • 3 replies
  • 17 views

Forum|alt.badge.img

On iOS, my Paywalls V2 paywall downloads its custom fonts from fonts.pawwalls.com on every launch, even though the exact same font files are bundled in the app and registered through UIAppFonts. The subsequent registration then fails, and the download stalls the paywall's first render by roughly a second.

This looks closely related to this thread: https://community.revenuecat.com/sdks-51/font-are-downloaded-although-they-are-available-7359 — where staff identified "a bug on our end with the font name formatting" and the resolution was to re-upload the font file after the fix shipped. I'm on a much newer SDK than that reporter, and I have re-uploaded the fonts, but the behavior is unchanged.

My main question is at the bottom: what is the supported way to have a V2 paywall use an app-bundled font without an uploaded file, so no download is attempted at all?

**Environment**

- react-native-purchases 10.4.2, react-native-purchases-ui 10.4.2
- PurchasesHybridCommon / PurchasesHybridCommonUI 18.19.0
- RevenueCat / RevenueCatUI pods 5.80.2
- React Native 0.85.3, Expo SDK 56
- iOS 26.5
- Paywalls V2, rendered with the view-based <RevenueCatUI.Paywall>, not presentPaywall
- Font: Baloo 2 (Google Fonts) — Regular 400, Medium 500, SemiBold 600, Bold 700

**Setup**

1. The four Baloo 2 .ttf files are bundled natively: UIAppFonts in the app target's Info.plist lists Baloo2-Regular.ttf, Baloo2-Medium.ttf, Baloo2-SemiBold.ttf and Baloo2-Bold.ttf, and I confirmed all four files are inside the built .app bundle.
2. The same four files are uploaded to the dashboard as a custom font family, and the paywall's text components reference them.
3. The iOS font names in the paywall editor are the filenames without extension, per the docs — and the SDK logs confirm they arrive correctly (see "Font 'Baloo2-Medium'" below).

**Logs (every launch, at cache-warming time)**

```
DEBUG [RevenueCat] Font 'Baloo2-Medium' download already in progress with url: https://fonts.pawwalls.com/1614916_f9dd4df5_f8ce6d5a93c3a668285e.ttf
DEBUG [RevenueCat] Font 'Baloo2-Regular' download already in progress with url: https://fonts.pawwalls.com/1614916_c5458193_deee2bec901d646e5319.ttf
DEBUG [RevenueCat] Font 'Baloo2-SemiBold' download already in progress with url: https://fonts.pawwalls.com/1614916_e968e133_a8314b0416ac62267c03.ttf

ERROR [RevenueCat] Error installing font with url: 'https://fonts.pawwalls.com/1614916_f9dd4df5_f8ce6d5a93c3a668285e.ttf': Font registration error: Font registration was unsuccessful.
ERROR [RevenueCat] Error installing font with url: 'https://fonts.pawwalls.com/1614916_e968e133_a8314b0416ac62267c03.ttf': Font registration error: Font registration was unsuccessful.
ERROR [RevenueCat] Error installing font with url: 'https://fonts.pawwalls.com/1614916_c5458193_deee2bec901d646e5319.ttf': Font registration error: Font registration was unsuccessful.
```

**What I verified myself**

I read the pod sources in ios/Pods/RevenueCat to narrow this down.

1. The downloaded files are byte-identical to the bundled ones. I compared MD5 hashes of the files cached under Library/Caches/RevenueCatFonts/ against my own assets, and three of four matched exactly. So the SDK is downloading fonts that are already installed in the process.

2. "Already registered" is not the error. SystemFontRegistry.registerFont explicitly treats CTFontManagerError.alreadyRegistered (105) as success, so this is a different code — the message corresponds to the generic registration failure. I also confirmed locally that a corrupt file yields code 103 with a different message, so the downloaded file itself is fine.

3. The skip-check depends on a server-provided family name. PaywallCacheWarming.installFont calls fontIsAlreadyInstalled(fontName: font.name, fontFamily: font.fontFamily), which resolves to UIFont.fontNames(forFamilyName: fontFamily).contains(fontName), where fontFamily comes from webFontInfo.family.

4. UIFont.fontNames(forFamilyName:) reads the legacy name ID 1, which differs per weight. I dumped the name tables of my four files:

- Baloo2-Regular — family (ID 1) "Baloo 2", subfamily "Regular", PostScript "Baloo2-Regular"
- Baloo2-Bold — family (ID 1) "Baloo 2", subfamily "Bold", PostScript "Baloo2-Bold"
- Baloo2-Medium — family (ID 1) "Baloo 2 Medium", subfamily "Regular", typographic family (ID 16) "Baloo 2", PostScript "Baloo2-Medium"
- Baloo2-SemiBold — family (ID 1) "Baloo 2 SemiBold", subfamily "Regular", typographic family (ID 16) "Baloo 2", PostScript "Baloo2-SemiBold"

This is the standard RIBBI split: weights outside Regular/Bold/Italic/BoldItalic can't be expressed in the legacy subfamily, so foundries move them into their own pseudo-family and keep the real grouping in the typographic names (ID 16/17). UIFont.fontNames(forFamilyName: "Baloo 2") therefore returns only the Regular and Bold PostScript names — Medium and SemiBold are invisible under that family. No single webFontInfo.family value can match all four weights.

5. But that doesn't explain Regular. Baloo2-Regular genuinely lives under family "Baloo 2", so the check should succeed for it, and it doesn't. That suggests the webFontInfo.family value being sent doesn't match any iOS family name at all — but I can't see that value from the client logs.

**Questions**

1. Is there a supported way to reference an app-bundled font in a V2 paywall without an uploaded file? UIConfig.FontsConfig.downloadableFont returns nil when ios.webFontInfo is absent, which would remove the download path entirely. Can the paywall editor keep the iOS font name (ios.value) while dropping the uploaded file, or does referencing a custom font always require an upload?

2. What is webFontInfo.family populated from, and what value should it hold for a multi-weight family like this so that UIFont.fontNames(forFamilyName:) resolves? Given the RIBBI split above, a single family string seems unable to cover Regular + Medium + SemiBold. Should the check use the typographic family (ID 16), or fall back to the fontFamily == nil path that scans all registered font names?

3. Was the fix from the linked thread shipped in 5.80.2 / hybrid-common 18.19.0? I re-uploaded the font files and the behavior is unchanged — the URLs change on each re-upload, but the downloads and registration failures persist.

4. Is the paywall's first render blocked on this font work? The ~1s stall before the paywall appears suggests it is. If so, a failed registration costing a full network round trip on every cold launch is a meaningful UX regression, and it would help to know whether there's a way to make it non-blocking.

Happy to share the offering's UIConfig JSON or a minimal repro project if that helps.

 

3 replies

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

Hey ​@Almopt,

Matt from RevenueCat here!

Thank you for the detailed write-up and for taking the time to dig into the SDK source! The information you provided was really helpful here.

I’ve taken a closer look into this, and this does indeed look like a bug on our end. I’ve raised this internally with our Paywalls engineering team.

The issue appears to be caused by a mismatch between the font family name we use for uploaded custom fonts and the family name UIKit uses. Because of this, the SDK can fail to recognize that a bundled font is already available and attempt to download and register it again.

This matches the behavior you’re seeing with the font download, registration error, and delay on a cold launch.

To answer your other questions:

  1. Bundled fonts: There currently isn’t a supported V2 option to reference an app-bundled font by name without uploading the font through the Paywall Editor.

  2. webFontInfo.family: This is based on the font’s PostScript-derived family name rather than UIKit’s family name, which is where the mismatch is coming from.

  3. Re-uploading the font: The previous font name formatting issue was separate, so re-uploading the font won’t resolve this behavior.

  4. First render delay: The unnecessary font download can delay the initial paywall presentation, which lines up with the delay you measured.

Thank you again for the excellent report and investigation here! I’ll stay close to this issue internally and update this thread with any progress.

If you have any additional questions in the meantime, please let me know and I’ll be happy to help!

Thanks,


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

Hey ​@Almopt,

I wanted to provide a quick update on the above.

The team has identified the root cause within the SDK and has a potential fix in place. This is currently in code review before some final checks. The fix will ensure local fonts are used when they’re available, resolving the unnecessary download issue.

I’ll keep you in the loop with the progress and let you know when this will be released as part of an SDK update.

Alternatively, as the SDKs are open source, you can view the fix and follow the progress here PR #7394 and PR #7396 

If there is anything else I can do to help, please let me know!

Thanks,


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

Thanks ​@matt-heaney , that's great news — and thanks for turning it around so quickly.

I've read through both PRs. #7394's name-first lookup should cover my case entirely, since the family mismatch stops mattering once the font name is matched directly. And #7396 confirms the registration error I was seeing was duplicatedName (305) rather than a generic failure, which explains why it only appeared once the fonts were bundled.

I'll keep the fonts bundled via UIAppFonts on my side, since that's what the fix needs in order to find them locally, and bump the SDK once it ships.

One follow-up: does the Android SDK have the same issue? I'm shipping on both platforms, and I've set my fonts up as res/font resources there. If the Android font resolution has an equivalent mismatch, it'd be useful to know before I test that side.

Thanks again for the thorough follow-through.