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.
