Skip to content

fix(ios): don't apply .weight() to a resolved custom font - #105

Open
adamjmarshall wants to merge 1 commit into
NativePHP:mainfrom
adamjmarshall:fix/ios-navbar-titleview-bold-font
Open

adamjmarshall wants to merge 1 commit into
NativePHP:mainfrom
adamjmarshall:fix/ios-navbar-titleview-bold-font

Conversation

@adamjmarshall

Copy link
Copy Markdown

Summary

On iOS, a custom named font resolved by NativeUIFontResolver (e.g. Text::make(...)->font('Montserrat-Bold')) can silently render as the plain system font instead — colors and everything else stay correct, only the font is wrong. Most visible on NavBar::titleView(), since that's the one place a caller in my app requested a specific bold static font without also requesting a matching ->weight().

Root cause

NUIScaledFontModifier.body (resources/ios/NativeUITheme.swift) chains .weight(weight) onto every custom Font NativeUIFontResolver returns — weight defaults to .regular when a caller doesn't request one explicitly.

Every font token this resolver resolves names one specific static bundled face — one .ttf/.otf per weight, registered via CTFontManagerRegisterFontsForURL and referenced by its own PostScript name (per NativeUIFontResolver's own doc comment: "Fonts are bundled into the app's Resources by this plugin's copy_assets hook... filename... is the token"). It's never a variable-font family with sibling weights for SwiftUI/CoreText to select between under that name.

Chaining .weight() onto a Font.custom(name:) built from that exact PostScript name asks CoreText to synthesize a different weight it has nothing else to draw from — and on iOS that silently drops the custom font entirely in favor of the system font, rather than erroring or ignoring the modifier. .foregroundColor (a separate modifier) is unaffected, which is why only the font — not the color — looks wrong.

Fix

Drop the .weight(weight) call for a resolved custom font (both the italic-with-synthesized-oblique branch and the plain branch). The system-font fallback branch is untouched — weight is still meaningful there, since there's no named face to lose.

Verification

Reproduced and fixed in a consuming app (NavBar::titleView() with two Text::make(...)->font('Montserrat-Bold') elements) via the iOS Simulator: toggling this exact .weight() call off/on reproduces and fixes the issue deterministically, independent of build configuration (confirmed against both a Debug and a Release-configuration local build). I wasn't able to find a Swift-level test harness in this repo (tests/ only covers the PHP wire-props/serialization layer, not actual SwiftUI rendering), so I don't have an automated regression test to add here — happy to add one if you can point me at a pattern for testing actual font resolution/rendering.

🤖 Generated with Claude Code

NUIScaledFontModifier.body chained .weight(weight) onto every custom Font
NativeUIFontResolver returned, defaulting to .regular whenever a caller
didn't request a specific weight. But every font token this resolver
resolves names one specific bundled static face — one .ttf/.otf per weight,
registered and referenced by its own PostScript name (see
NativeUIFontResolver's own doc comment) — never a variable-font family with
sibling weights for SwiftUI to select between.

Asking CoreText to synthesize a different weight for a name that already
identifies one exact static face has nothing to draw from, and on iOS it
silently drops the custom font in favor of the system font. Colors and
everything else stay correct (separate modifiers), so the only visible
symptom is the requested custom font quietly not applying — e.g. a
NavBar::titleView() with Text::make(...)->font('Montserrat-Bold') renders
in the plain system font instead of Montserrat Bold.

Confirmed in a consuming app via the iOS Simulator: toggling this exact
`.weight()` call off/on reproduces and fixes the issue, deterministically,
across Debug and Release configurations. This repo has no Swift-level test
harness to pin the regression (tests/ only covers the PHP wire-props layer),
so I'm not able to add automated coverage for it here.

The system-font fallback branch is untouched — `weight` is still meaningful
there, since there's no named face to lose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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