From 082d787a1effbcfd351f5624ab559356323f94d0 Mon Sep 17 00:00:00 2001 From: Tyler Williams Date: Tue, 1 Sep 2026 12:37:45 -0700 Subject: [PATCH] fix: stale VoiceOver value from async UI updates to Text (#58169) Summary: This is a targeted fix for https://github.com/react/react-native/issues/58145, where I observed iOS VoiceOver reading stale values over `Text` elements that were updated asynchronously (from a `setTimeout` call). This patch definitely fixes the issue, but I'm happy to iterate on the design if there are any considerations I haven't taken into account: https://github.com/coolsoftwaretyler/focusissues/pull/1 Maybe related to https://github.com/react/react-native/issues/49462? Although what I found is that when VoiceOver encountered a failure, it bounced back to the status bar, so it's possible 49462 is some *other* issue with modals that presents the same failure mode. ## Changelog: Pick one each for the category and type tags: [IOS] [FIXED] -VoiceOver text readout when changing elements asynchronously Pull Request resolved: https://github.com/react/react-native/pull/58169 Test Plan: I built a reproducer and tested this patch with it. To test: 1. Build reproducer (or any app that changes text asynchronously) 2. Install app to physical device (I found the accessibility inspector tool with simulators would paper over the issue, so you need to actually use VoiceOver on a device) 3. Turn on voiceover 4. Navigate through the interface, moving focus through your elements to ensure they work as expected 5. Then use VoiceOver to trigger an async update to some text 6. Move on to any value that was changed by the async update 7. Text will probably be stale, and moving focus no longer works ### Before https://github.com/user-attachments/assets/05b8d308-e743-4cff-917c-c5410307937d ### After https://github.com/user-attachments/assets/d7d8d3fc-5bd8-45b9-bab0-de9bbc9cbcda Reviewed By: cortinico Differential Revision: D117875865 Pulled By: christophpurrer fbshipit-source-id: 218ca244ea99e460d687ec527d6fbcaa9e499943 --- .../ComponentViews/Text/RCTParagraphComponentView.mm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 500c0815f58..228ec0d2a28 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -138,6 +138,17 @@ using namespace facebook::react; _textView.state = std::static_pointer_cast(state); [_textView setNeedsDisplay]; [self setNeedsLayout]; + + // If the attributed string has changed, we need to notify the accessibility system that something changed, + // otherwise it may hold on to stale values (this happens most often when an element is updated async) + // https://github.com/react/react-native/issues/58145 + if (state && oldState) { + const auto &newData = std::static_pointer_cast(state)->getData(); + const auto &oldData = std::static_pointer_cast(oldState)->getData(); + if (!newData.attributedString.isContentEqual(oldData.attributedString)) { + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + } + } } - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics