iOS RTL (Right‑to‑Left) Language Adaptation: Challenges and Solutions
The article discusses challenges and solutions for adapting iOS apps to right‑to‑left languages like Arabic, covering UI mirroring, layout adjustments using leading/trailing constraints, image flipping, text alignment and attributed strings, Unicode direction controls, collection view handling, RTL‑aware insets, navigation gestures, number formatting, and in‑app language switching with practical code examples.
Introduction
More than 22 countries and 660 million people use Arabic script, making it the third largest written language after Latin and Chinese. As business expands overseas, adapting iOS apps to Arabic (an RTL language) becomes essential. Unlike LTR languages, Arabic is written and read from right to left, which requires special handling even though iOS provides many RTL APIs.
Key RTL Characteristics
Text – RTL languages are read from right to left.
Icons – Directional icons (e.g., arrows) must be mirrored, while neutral icons stay unchanged.
Numbers – Different regions use Western Arabic digits or Eastern Arabic digits; the correct set must be chosen per market.
Project Status and Characteristics
The codebase is large, so extensive changes must be weighed against risk and cost.
Much of the UI is built with manual frame layout rather than AutoLayout.
The app allows users to switch language inside the app, independent of the system language.
Problems Encountered
In‑app Language Switching
When the system language is set to an RTL language, iOS automatically flips the layout. If the app’s internal language differs from the system language, the default system handling cannot be used. Since iOS 9, UIView exposes a new property:
@property (nonatomic) UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0));By setting semanticContentAttribute on each view we can control whether it should be mirrored. Changing every view manually is impractical, so the global appearance proxy is used when the language is switched:
if isRTLLanguage {
UIView.appearance().semanticContentAttribute = .forceRightToLeft
} else {
UIView.appearance().semanticContentAttribute = .forceLeftToRight
}Views that must never be mirrored can set their own semanticContentAttribute explicitly.
Layout
Two layout approaches are common: AutoLayout and manual frame layout. AutoLayout libraries (Masonry, SnapKit) already support RTL when constraints use leading and trailing instead of left and right . For legacy frame‑based code, a mapping is introduced:
@implementation UIView (RTL)
- (CGFloat)leading {
NSAssert(self.superview != nil, @"View must be added to a superview");
if ([self isRTL]) {
return self.superview.width - self.right;
}
return self.left;
}
- (void)setLeading:(CGFloat)leading {
NSAssert(self.superview != nil, @"View must be added to a superview");
if ([self isRTL]) {
self.right = self.superview.width - leading;
} else {
self.left = leading;
}
}
- (CGFloat)trailing {
NSAssert(self.superview != nil, @"View must be added to a superview");
if ([self isRTL]) {
return self.leading + self.width;
}
return self.right;
}
- (void)setTrailing:(CGFloat)trailing {
NSAssert(self.superview != nil, @"View must be added to a superview");
if ([self isRTL]) {
self.right = self.superview.width - trailing + self.width;
} else {
self.left = trailing - self.width;
}
}
@endAfter adding these helpers, existing left / right assignments can be replaced with leading / trailing , reducing migration cost.
Image
Only images with a clear direction need mirroring. Since iOS 9, UIImage provides:
- (UIImage *)imageFlippedForRightToLeftLayoutDirection API_AVAILABLE(ios(9.0));
@property (nonatomic, readonly) BOOL flipsForRightToLeftLayoutDirection API_AVAILABLE(ios(9.0));For custom flipping, a category is added:
@implementation UIImage (RTL)
- (UIImage *_Nonnull)checkOverturn {
if (isRTL) {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, self.size.width/2, self.size.height/2);
CGContextScaleCTM(bitmap, -1.0, -1.0);
CGContextTranslateCTM(bitmap, -self.size.width/2, -self.size.height/2);
CGContextDrawImage(bitmap, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
return image;
}
return self;
}
@endA view‑level helper mirrors any UIView using a transform:
@implementation UIView (RTL)
- (void)checkOverturn {
if (self.overturned) { return; }
self.transform = CGAffineTransformScale(self.transform, -1, 1);
}
@endText
Three main issues are alignment, attributed strings, and character order.
Alignment
NSTextAlignment values differ between LTR and RTL. Since iOS 9 the default is NSTextAlignmentNatural , which follows the system language direction. When the app’s language differs from the system language, the alignment must be set manually, e.g. for UILabel :
typedef NS_ENUM(NSUInteger, NMLLabelRTLAlignment) {
NMLLabelRTLAlignmentUndefine,
NMLLabelRTLAlignmentLeft,
NMLLabelRTLAlignmentRight,
NMLLabelRTLAlignmentCenter,
};
@implementation UILabel (RTL)
- (void)setRtlAlignment:(RTLAlignment)rtlAlignment {
switch (rtlAlignment) {
case RTLAlignmentLeading:
self.textAlignment = (isRTL ? NSTextAlignmentRight : NSTextAlignmentLeft);
break;
case RTLAlignmentTrailing:
self.textAlignment = (isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
break;
case RTLAlignmentCenter:
self.textAlignment = NSTextAlignmentCenter;
break;
default:
break;
}
}
@endAttributedString handling
Because textAlignment does not affect attributed strings, a similar category sets NSParagraphStyle alignment:
@implementation NSMutableAttributedString (RTL)
- (void)setRtlAlignment:(RTLAlignment)rtlAlignment {
switch (rtlAlignment) {
case RTLAlignmentLeading:
self.yy_alignment = (isRTL ? NSTextAlignmentRight : NSTextAlignmentLeft);
break;
case RTLAlignmentTrailing:
self.yy_alignment = (isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
break;
case RTLAlignmentCenter:
self.yy_alignment = NSTextAlignmentCenter;
break;
default:
break;
}
}
@endCharacter order
The system decides direction based on the first character. Mixed LTR/RTL strings (e.g., "مرحبا@我") require inserting Unicode control characters such as \u200E (LEFT‑TO‑RIGHT MARK) or \u202A (LEFT‑TO‑RIGHT EMBEDDING) and \u202C (POP DIRECTIONAL FORMATTING) to obtain the desired visual order.
Other Considerations
UICollectionView
RTL does not automatically flip collection views. Since iOS 11, UICollectionViewLayout has a read‑only property:
@property (nonatomic, readonly) BOOL flipsHorizontallyInOppositeLayoutDirection;Setting this to true forces horizontal mirroring; a subclass must override the getter.
UIEdgeInsets
Legacy UIEdgeInsets use left / right which are not mirrored. A helper creates RTL‑aware insets:
UIEdgeInsets UIEdgeInsetsMake_RTLFlip(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right) {
if (!isRTL) {
return (UIEdgeInsets){top, left, bottom, right};
}
return (UIEdgeInsets){top, right, bottom, left};
}UINavigationController
The back‑swipe gesture follows the system direction. When the app overrides semanticContentAttribute globally, the navigation controller’s view must also be set:
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
self.view.semanticContentAttribute = [UIView appearance].semanticContentAttribute;
}
return self;
}Gesture
Direction‑sensitive gestures (e.g., UISwipeGestureRecognizer ) are not mirrored by the system; the app must check the RTL flag and adjust the direction logic.
Number
The project uses Western Arabic digits; if Eastern Arabic digits are required, additional formatting logic is needed.
Conclusion
The RTL compatibility work is now complete. Because the app supports in‑app language switching, many issues become more complex, and the chosen solutions aim to balance effort and risk. New projects or apps without independent language settings can adopt simpler strategies.
References
Internationalization and Localization Guide [1]
Design for Arabic [2]
How to use Unicode controls for bidi text [3]
This article is published by NetEase Cloud Music’s technical team. Unauthorized reproduction is prohibited. We are hiring for various technical positions; if you are interested, contact grp.music-fe(at)corp.netease.com.
NetEase Cloud Music Tech Team
Official account of NetEase Cloud Music Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.