Skip to content

fix(perry-ui-ios): #1107 partial-alpha text invisible on iOS 26#1109

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-agent-aa6bacf9d42508b33
May 19, 2026
Merged

fix(perry-ui-ios): #1107 partial-alpha text invisible on iOS 26#1109
proggeramlug merged 1 commit into
mainfrom
worktree-agent-aa6bacf9d42508b33

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Closes #1107.

Root cause

On iOS 26 device, UILabel.setTextColor: and UIButton.setTitleColor:forState: with a UIColor whose alpha < 1.0 render zero glyphs. Reporter confirmed:

  • alpha == 1.0 paints fine (same code path)
  • iOS 17 simulator is unaffected (only iOS 26 device fails)
  • AttributedText widget's path — UIColor under NSColor / NSForegroundColorAttributeName inside an NSAttributedString applied via setAttributedText: — renders correctly with sub-1.0 alpha

Looks like an iOS 26 / Liquid Glass text renderer regression: the plain textColor / titleColor paths are silently dropped (or rasterised invisibly) when the color isn't fully opaque, while the attributed-text path goes through a different renderer that still honors partial alpha.

Fix

In crates/perry-ui-ios/src/widgets/text.rs::set_color (UILabel) and crates/perry-ui-ios/src/widgets/button.rs::set_text_color (UIButton): for alpha < 1.0, mirror AttributedText::append's known-working code path on the plain widget by also setting attributedText / attributedTitle:forState::

  1. Read the current text / titleForState:UIControlStateNormal
  2. Read the current font (from the label or button.titleLabel.font)
  3. Build an NSAttributedString with NSFont + NSColor attrs (matching attributed_text.rs line-for-line on the keys)
  4. Apply via setAttributedText: / setAttributedTitle:forState:UIControlStateNormal

setTextColor: / setTitleColor: is still issued first, so any later setText: / setTitle: clobber (which discards the attributed buffer) retains a reasonable solid-color fallback. For alpha == 1.0 we explicitly clear any prior attributedText/attributedTitle so the plain path takes over, avoiding disturbing intrinsic-content sizing on the unaffected iOS 17 path.

Critically, the NSFont attr is always emitted — the reporter's "Routing set_color through setAttributedText: with NSColor in attributes dict … Zero-glyph" bisection-step likely didn't include NSFont, and on iOS 26 it appears NSFont is required for sub-1.0 alpha to render.

Files touched

  • crates/perry-ui-ios/src/widgets/text.rs
  • crates/perry-ui-ios/src/widgets/button.rs

No TS / HIR / codegen / shim changes.

Test plan

The agent doesn't have iOS 26.5 device access. Reporter should validate:

  • On iOS 26.5 device, run the issue reproducer:
    import { Text, VStack, textSetFontSize, textSetColor } from "perry/ui";
    const TEXT_PRIMARY = { r: 0, g: 0, b: 0, a: 0.87 };
    const slogan = Text("Erlebe eine Welt, in der Teilen Freude schenkt");
    textSetFontSize(slogan, 24);
    textSetColor(slogan, TEXT_PRIMARY.r, TEXT_PRIMARY.g, TEXT_PRIMARY.b, TEXT_PRIMARY.a);
    expect: 87%-black glyphs visible.
  • Same on a Button(...) title with textSetColor(btn, 0, 0, 0, 0.87) — expect 87%-black glyphs.
  • iOS 17 simulator regression check: Text + textSetColor with both alpha == 1.0 and alpha == 0.87 still render.
  • Existing AttributedText widget still works (untouched code path; smoke test only).

If the alpha < 1.0 cases still come out blank on device, the next hypothesis is that NSColor (the iOS legacy key) needs to be NSForegroundColorAttributeName literal (@"NSColor" resolves to the same symbol but if Apple changed it on iOS 26, we'd want to look up kCTForegroundColorAttributeName from CoreText instead). Easy follow-up if validation fails.

No version bump or changelog change — maintainer will fold those in at merge.

UILabel.setTextColor: and UIButton.setTitleColor:forState: with a
UIColor whose alpha < 1.0 render zero glyphs on iOS 26 device (alpha
== 1.0 paints fine, iOS 17 simulator is unaffected, and the
AttributedText widget's NSAttributedString-with-NSColor path renders
correctly). Likely an iOS 26 / Liquid Glass text renderer regression.

Workaround: for alpha < 1.0, mirror AttributedText's working code
path on the plain Text/Button receivers — build an NSAttributedString
with NSFont + NSColor attrs (pulled from the label's current font /
the button's titleLabel.font) and apply it via setAttributedText: or
setAttributedTitle:forState:UIControlStateNormal. The plain
setTextColor:/setTitleColor: call is still issued so any setText: /
setTitle: clobber later retains a reasonable fallback color. For
alpha == 1.0 we leave the simple path alone (and explicitly clear
any prior attributedText/attributedTitle) to avoid disturbing
intrinsic-content sizing on the non-buggy iOS 17 path.

Files:
- crates/perry-ui-ios/src/widgets/text.rs
- crates/perry-ui-ios/src/widgets/button.rs

No version bump or changelog change.
@proggeramlug proggeramlug merged commit ddcf13a into main May 19, 2026
9 checks passed
@proggeramlug proggeramlug deleted the worktree-agent-aa6bacf9d42508b33 branch May 19, 2026 11:16
proggeramlug added a commit that referenced this pull request May 19, 2026
…+ nested stack bg (#1127)

* fix(perry-ui-ios): finish #1107 — iOS 26 device rendering of Buttons + nested stack bg

PR #1109 shipped a partial fix for #1107 (partial-alpha NSColor renders
zero glyphs on iOS 26 device) but three regressions / blind spots
remained, all reproduced on physical iPhone running iOS 26.5:

1. Text widgets with `textSetColor(α < 1.0)` (e.g. Material's 87% black)
   still rendered nothing. #1109's `apply_label_color_via_attributed`
   reused the label's borrowed `view.font` pointer and the un-retained
   `initWithString:attributes:` result; the working AttributedText path
   builds a fresh `[UIFont systemFontOfSize:]` and retains the result.
   This commit mirrors that exact pattern.

2. Bare `Button("Mitmachen", cb)` + `widgetSetBackgroundColor(BRAND_RED)`
   rendered invisible. `buttonWithType:UIButtonTypeSystem` on iOS 26
   routes through the Liquid Glass renderer, which overrides explicit
   `setTitleColor:` / `setBackgroundColor:` with the system tint.
   Switch to `UIButtonTypeCustom` (and seed `[UIColor labelColor]` as
   the default title color so a bare button with no explicit color is
   still visible). Also drop the `setAttributedTitle:nil` cleanup branch
   from #1109 — on iOS 26 it nukes the button's plain-title rendering
   along with the attributed state, leaving even α==1.0 buttons blank.

3. `widgetSetBackgroundColor` on a NESTED `UIStackView` painted
   transparent. `UIStackView.backgroundColor` is documented iOS 14+ but
   the property doesn't drive painting on inner stacks under iOS 26's
   compositor. The CALayer's own `backgroundColor` (CGColor) does paint
   reliably, so set both — view-level for non-buggy paths (root stack,
   iOS 17 sim) and layer-level as the belt-and-suspenders fix.

Verified on iPhone running iOS 26.5: bisect of the Wishare Onboarding
screen (TEXT_PRIMARY α=0.87 labels, BRAND_RED-bg `Mitmachen` button,
white-bg nested card VStack) now renders all three widget classes.

Follow-up issue #1122 tracks the broader audit of iOS 26 regressions.

* style(perry-ui-ios): cargo fmt — drop stray blank line in widgets/mod.rs

Lint job on PR #1127 failed on `cargo fmt --check`. Single extraneous
blank line between `set_background_color` and `gradient_bg_class`.
proggeramlug added a commit that referenced this pull request May 21, 2026
…utton (#1284)

PR #1127 added a CALayer.backgroundColor fallback for UIStackView to
work around nested-stack transparency on iOS 26 device. UIButton has
the same class of bug — Liquid Glass renderer can override
setBackgroundColor: on certain button-type/configuration combinations
even after #1127 forced UIButtonTypeCustom. Apply the same
belt-and-suspenders fallback to UIButton so the CGColor lands on the
layer directly regardless of UIKit-side interception. No behavior
change for ordinary UIViews (setBackgroundColor: already updates
layer.backgroundColor on those).

Also rewrites the function's doc comment — it previously described an
"inserting a UIView pinned to stack bounds" fallback that the actual
implementation never did (we always set layer.backgroundColor with
CGColor). Documents both currently-known iOS 26 cases (UIStackView,
UIButton) and the rationale.

Closes #1122 pending on-device retest. Native side has now layered
#1109 (attributed-text for partial-alpha labels), #1127 (UIButtonTypeCustom +
UIStackView layer fallback), and this PR (UIButton layer fallback).
Underlying TS-compiler bug #1129 — which silently produced NaN/
undefined when the consumer app imported color object literals from
another module, masking all native-side fixes — was closed by #1281
(baaeb44).
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.

iOS 26: partial-alpha NSColor in setAttributedText renders zero glyphs on plain UILabel/UIButton

1 participant