Objective‑C UIView ↔ SwiftUI UIViewRepresentable: Propagating accessibilityLabel
0 reputation · 19 Oct 2022, 13:16 UTC
0 reputation · 19 Oct 2022, 13:16 UTC
Ensure that an Objective‑C UIView subclass’s accessibilityLabel is announced by VoiceOver when the view is wrapped in a SwiftUI UIViewRepresentable.
init or makeUIView may be ignored unless updated in updateUIView.UIAccessibilityPostNotification to SwiftUI’s hierarchy.updateUIView to guarantee that the accessibilityLabel set in Objective‑C is propagated to VoiceOver?accessibilityTraits from the Objective‑C view to the SwiftUI wrapper without manual SwiftUI modifiers?32275 reputation · 19 Oct 2022, 18:23 UTC
Set the label (and traits) in updateUIView, not only in init or makeUIView. SwiftUI may recreate or reuse the wrapped view and discard accessibility state configured at creation time, so the only reliable point to propagate values is the update pass. Do not add SwiftUI .accessibilityLabel() modifiers to the representable — that creates a second, competing accessibility element.
struct ObjCViewWrapper: UIViewRepresentable {
@Binding var label: String
func makeUIView(context: Context) -> MyObjCView {
let view = MyObjCView()
view.accessibilityDelegate = context.coordinator // if label can change internally
return view
}
func updateUIView(_ uiView: MyObjCView, context: Context) {
uiView.accessibilityLabel = label
// Traits: copy explicitly if the ObjC view manages them
// uiView.accessibilityTraits = uiView.objcManagedTraits
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
}Copying accessibilityLabel and accessibilityTraits in updateUIView is the minimal guarantee that VoiceOver sees current values, because UIKit reads them from the view at focus time.
There is no automatic mirroring of traits into SwiftUI, and you generally should not try to route them through SwiftUI modifiers — .accessibilityAddTraits() on the representable wraps the view in a separate accessibility element, causing duplicate or conflicting announcements. Keep ownership of accessibility on the UIView itself. If the Objective-C view is a UIControl subclass, its built-in traits (e.g. button) are already exposed; copying accessibilityTraits in updateUIView preserves whatever the view sets internally.
If the Objective-C view mutates its own label outside SwiftUI's data flow, updateUIView may not be called — especially on iOS 17+, where update passes can be skipped when inputs appear unchanged. The reliable pattern is:
@Binding/@State.updateUIView pass, which re-assigns the label.As a fallback, calling setNeedsAccessibilityUpdate() on the view nudges the accessibility system to re-query, but routing through state is the deterministic option.
No forwarding layer is needed for announcements. If the Objective-C view posts UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, ...) on itself, the system speaks it because the view is already in the accessibility hierarchy. UIAccessibilityLayoutChangedNotification also works, though focus behavior depends on the view being the intended focus target.
updateUIView call frequency differs.Assumes iOS 16/17+ and a single wrapped view. If your Objective-C view builds a custom UIAccessibilityElement tree internally rather than being directly accessible, the propagation strategy changes — that detail is worth confirming before applying the above.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.