跪拜 Guibai
← Back to the summary

iOS RTL Language Support: Resources, Traits, and the Read-Only App Direction

Deep understanding over rote memorization.

Supporting RTL (right-to-left) languages such as Arabic and Hebrew requires handling the resource language, layout environment, and content semantics at the same time.

This article first explains the relevant properties and their history, then discusses how to implement in-app instant switching and where its boundaries are.

For a self-built instant switcher, a clean division of responsibilities is: switch resources + override the environment direction + preserve control semantics. However, overriding the direction trait does not synchronously rewrite the app-level direction, and it cannot replace a full system language-environment switch.

Prefer the system-provided per-app language setting

Starting in iOS 13, users can choose a language for a single app without changing the device's overall language.

Apple's WWDC19 advice: if an app needs a language entry point, guide users to that app's settings in the system Settings app, let the system relaunch the app in the target language, and use state restoration to improve the return experience. WWDC24 revisited this per-app language setting.

If the product can tolerate jumping to Settings and relaunching, prefer this system route. If the product explicitly requires the language to take effect immediately inside the app, you need to manage resources and direction yourself and verify the behavior of system components and third-party components. The latter is an engineering solution that satisfies product constraints; it is not equivalent to Apple's recommended full language-switching flow.

References: WWDC19: Creating Great Localized Experiences with Xcode 11, around 2:32–3:30; WWDC24: Build multilingual-ready apps.

Related properties and history

A trait represents one feature of the interface environment, such as horizontal/vertical size classes, display scale factor, and layout direction.

Each of these is a trait; UITraitCollection is the collection of these traits.

semanticContentAttribute

semanticContentAttribute is a read-write UIView property introduced in iOS 9. It represents the semantic characteristics the content wants to express.

English value Apple's English description Plain Chinese label Concrete meaning
.unspecified The default value for views. Default semantics, follow the environment No special direction requirement; resolve according to the view's environment. For example, bold/italic button groups usually fall into this case.
.playback A view representing the playback controls, such as Play, Rewind, or Fast Forward buttons or playhead scrubbers. Media playback semantics, keep direction Used for play, rewind, fast forward, playback progress, and similar controls; not mirrored when switching LTR/RTL.
.spatial A view representing a directional control, such as a segment control for text alignment, or a D-pad control for a game. Absolute spatial semantics, keep direction Used for left/right alignment, game D-pads, and similar controls; physical left and right do not change with reading direction.
.forceLeftToRight A view that's always displayed using a left-to-right layout. Force left-to-right No matter the environment direction, content always uses LTR layout.
.forceRightToLeft A view that's always displayed using a right-to-left layout. Force right-to-left No matter the environment direction, content always uses RTL layout.

UITraitCollection.layoutDirection

layoutDirection is a read-only trait property of UITraitCollection introduced in iOS 10. It represents the layout direction associated with the current environment.

enum UITraitEnvironmentLayoutDirection : Int {
    case unspecified = -1
    case leftToRight = 0
    case rightToLeft = 1
}

Difference from UIApplication.userInterfaceLayoutDirection

typedef NS_ENUM(NSInteger, UIUserInterfaceLayoutDirection) {
    UIUserInterfaceLayoutDirectionLeftToRight,
    UIUserInterfaceLayoutDirectionRightToLeft,
} API_AVAILABLE(ios(5.0)) API_UNAVAILABLE(watchos);

Modifying layoutDirection

The UITraitCollection.layoutDirection you read is a read-only value. When changing the environment direction, use the override API to provide a new trait value, rather than directly modifying the read collection or overriding its getter.

On iOS 17+, traitOverrides is available on UIWindowScene, UIView (including UIWindow), UIViewController, and UIPresentationController. The override applies to that environment and everything below it; an explicit override further down still affects the final result. The older setOverrideTraitCollection(_:forChild:) only overrides the specified child controller and its inherited scope, so it cannot be used to change the language environment of the scene or the entire process.

semanticContentAttribute vs layoutDirection

I originally had a question: why have two properties for layout?

Because the same environment can impose different layout requirements on different content.

The official example above: the left side is an English interface, the right side is Arabic. The bold/italic buttons at the top are the style group; the text-alignment buttons at the bottom are the alignment group.

Group Language semanticContentAttribute layoutDirection effectiveUserInterfaceLayoutDirection
Style group English .unspecified LTR LTR
Style group Arabic .unspecified RTL RTL
Alignment group English .spatial LTR LTR
Alignment group Arabic .spatial RTL LTR

UIView.effectiveUserInterfaceLayoutDirection

effectiveUserInterfaceLayoutDirection is a read-only UIView property introduced in iOS 10. It represents the effective layout direction.

Environment direction and content semantics together affect view arrangement. When customizing layout or drawing a view's direct content, query that view's effectiveUserInterfaceLayoutDirection. Do not assume the whole subtree shares the same effective direction. For example, a spatial directional control in an RTL environment can still use LTR. Apple API documentation.

Source and boundaries of UIApplication.userInterfaceLayoutDirection

The latter resolves using the passed-in content semantics and the app-level direction, and could be used for custom layout decisions at that time.

In the normal localization flow, the system combines the user's language preference and the app's supported localizations to choose the app's runtime language; when there is no match, it falls back to the development language.

The device's preferred language is not necessarily the language the app actually uses, and region format preferences do not equal interface direction. Apple QA1828: How iOS Determines the Language For Your App.

According to public documentation, the normal behavior can be understood as: user language preference + app-supported localizations → app runtime language → app-level interface direction → resolve local direction combined with control semantics. Apple has not published the specific implementation of the getters for each iOS 5–9 version, so a language query expression should not be treated as its internal source code.

In testing, the difference can be reproduced: after the system is changed to Arabic, the app-level direction (userInterfaceLayoutDirection) becomes RTL; when the system stays in Chinese and only the in-app business language is switched, it remains LTR. Switching resources, overriding the direction trait on a scene/window/controller, or setting content semantics will not synchronously rewrite the read-only userInterfaceLayoutDirection through the public API.

In-app language switching implementation strategy

In one sentence: switch resources, change the environment direction, preserve control semantics; after refresh, lay out according to each view's effective direction.

Processing item Specific approach Notes
Switch resources Uniformly choose text, images, and server-side content; update established formatting strategies. Saved app language + unified resource entry.
Override direction Decide LTR/RTL from the app language and set it at a suitable common ancestor. Use the layoutDirection override APIs; see Modifying layoutDirection above.
Preserve semantics Normal: .unspecified; playback: .playback; physical direction: .spatial. Usually no need to change semanticContentAttribute.

Refresh and acceptance: Preserve navigation and editing state, and read the view's effectiveUserInterfaceLayoutDirection at the appropriate layout phase. Check new scenes, modal interfaces, and attached windows separately. WWDC23: trait updates and inheritance.

Boundaries: UIApplication.userInterfaceLayoutDirection will not update synchronously; system components and the safe area still need verification.

Known issues

Do not use semanticContentAttribute to implement language switching

References