iOS RTL Language Support: Resources, Traits, and the Read-Only App Direction
Apps shipping Arabic or Hebrew cannot treat RTL as a single global flip: UIKit's direction model has separate read-only and override paths, and in-app switching leaves UIApplication.userInterfaceLayoutDirection untouched. Teams building an in-app language switcher need to know exactly which parts of the system route are reproducible and which are not.
Supporting Arabic, Hebrew, and other RTL languages on iOS touches three separate layers: which localized resources load, what layout direction the environment reports, and what each control says about its own content semantics. Apple's recommended path has been available since iOS 13: the per-app language setting in system Settings, where the system relaunches the app in the chosen language and state restoration returns the user to where they were. WWDC19 and WWDC24 both push apps toward that route. When a product demands language switching inside the app without a restart, the engineering fallback is to switch resources, override the layout direction trait at a common ancestor, and leave semanticContentAttribute alone. That arrangement respects the difference between a trait environment and app-level direction.
UIKit separates environment direction from content semantics. UITraitCollection.layoutDirection, read-only since iOS 10, describes the trait environment for a view or controller; semanticContentAttribute, writable since iOS 9, lets a control declare that it should follow the environment, keep media playback orientation, or keep absolute spatial meaning. The effective direction a view actually lays out with is UIView.effectiveUserInterfaceLayoutDirection. Both inputs matter: an alignment control in an RTL environment can still resolve to LTR when it uses .spatial. The catch is that none of these overrides writes back to UIApplication.userInterfaceLayoutDirection, the read-only app-level direction property from iOS 5. In-app switching therefore never fully replicates a system language change.
One known trap: setting UIView.appearance().semanticContentAttribute to force RTL can look correct but abuses the property's semantic meaning and has triggered incorrect safe areas on the iPhone Duo simulator. The sane division of labor is switch resources, override direction traits, preserve semantics, then refresh and verify effective direction on new scenes, modal interfaces, and attached windows.
The reason UIKit has both semanticContentAttribute and layoutDirection is that one environment must host controls with opposite directional needs; a global RTL flip that ignores per-control semantics will misplace things like text alignment buttons.
Apple's per-app language route is the only complete way to change direction because UIApplication.userInterfaceLayoutDirection is read-only; an in-app switcher is an engineering workaround that leaves system components and safe areas outside its control.
The iPhone Duo simulator failure is evidence that abusing semanticContentAttribute can corrupt layout beyond visual mirroring, so respecting its semantic contract is practical, not pedantic.