[iOS][Fabric] Changing Dynamic Type while the app is running does not re-lay out text (root not dirtied on a layout-context-only change)

Aperta
#59,927 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cpp, ios, react-native
Ambito
frontend, mobile

Direzione di ricerca

Start with RootShadowNode.cpp and inspect RootShadowNode::clone, then follow layoutContext into YogaLayoutableShadowNode::configureYogaTree. The issue identifies the three layout-context inputs that need to trigger layout dirtiness and explains why comparing the full context would be too broad. Reproduce the Dynamic Type change on iOS with the New Architecture, then verify existing layout tests cover the context changes and that text relays out after the change.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Needs: Author Feedback Needs: Repro
Description

On iOS with the New Architecture, changing the system text size (Dynamic Type) while the app is running has no visible effect on text already on screen. React Native receives the new fontSizeMultiplier, but no layout pass runs, so text keeps its launch-time size until the app is restarted. Text mounted after the change can pick up the new size, which produces mixed layouts.

Steps to reproduce
  1. Build a Release app with React Native 0.88.1 (New Architecture) and any <Text> that has allowFontScaling left at its default.
  2. Launch it on an iOS simulator or device.
  3. Without closing the app, change the text size (Settings › Display & Brightness › Text Size, or on a simulator xcrun simctl ui <udid> content_size accessibility-extra-large).
  4. Return to the app.

Expected: text re-lays out at the new size.
Actual: nothing changes, in either direction. Relaunching renders the new size correctly.

Root cause
  • RCTFabricSurface handles UIContentSizeCategoryDidChangeNotification by calling _updateLayoutContext with the new fontSizeMultiplier, which reaches SurfaceHandler::constraintLayout. That commits RootShadowNode::clone(propsParserContext, layoutConstraints, layoutContext).

  • RootShadowNode::clone only calls dirtyLayout() when the layout constraints change:

    if (layoutConstraints != getConcreteProps().layoutConstraints) {
      newRootShadowNode->dirtyLayout();
    }
    
  • For a layout-context-only change, the root stays clean. RootShadowNode::layoutIfNeeded then returns early (getIsLayoutClean()), so layoutTree → configureYogaTree never runs.

  • That means the existing font-scale handling in YogaLayoutableShadowNode::configureYogaTree never gets a chance to run, even though enableFontScaleChangesUpdatingLayout defaults to true. That code marks measurable nodes dirty when their fontSizeMultiplier differs from the new one.

  • pointScaleFactor and swapLeftAndRightInRTL are consumed by configureYogaTree in the same way, so a change to either alone has the same problem.

The same code is on main as of 2026-11-10 (RootShadowNode.cpp last changed in 178c30d7ac). #59285 makes ParagraphShadowNode drop content built for another fontSizeMultiplier, but that only helps once a layout pass actually runs.

Proposed fix

Dirty the root when any layout-context input that configureYogaTree consumes changes:

-  if (layoutConstraints != getConcreteProps().layoutConstraints) {
+  const auto& previousContext = getConcreteProps().layoutContext;
+  if (layoutConstraints != getConcreteProps().layoutConstraints ||
+      layoutContext.fontSizeMultiplier != previousContext.fontSizeMultiplier ||
+      layoutContext.pointScaleFactor != previousContext.pointScaleFactor ||
+      layoutContext.swapLeftAndRightInRTL !=
+          previousContext.swapLeftAndRightInRTL) {
     newRootShadowNode->dirtyLayout();
   }

The full LayoutContext == is deliberately not used: it also compares affectedNodes (a per-pass pointer) and the viewport fields, which would dirty the root on unrelated commits.

I traced this in source and reproduced the symptom in a Release build. I have not run the patched build myself, because our app uses the prebuilt React Native core.

Versions

React Native 0.88.1 (also present on 0.88-stable and main), Expo SDK 58, iOS 26.5 simulator, New Architecture, Hermes.

Lingua principale
C++
Stelle
127k
Fork
25.5k
Merge medio
3g 18h
PR unite (30g)
188

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di react/react-native

Tutte le issue di react/react-native

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.