iOS RTL 布局适配
前言
阿拉伯地区的语言是从右到左展示的,不止是文本的适配,页面布局也需要做适配。
iOS9之后的RTL适配,官方文档做适配
适配
UIVIew
1 | typedef NS_ENUM(NSInteger, UISemanticContentAttribute) { |
设置 UIView 的属性 semanticContentAttribute 解决,UIView是所有控件的基类,不可能给项目所有的控件都进行修改,想要使用hook方法在初始化方法中解决,但是会有坑,比如 WKWebView
在 AppDelegate 中使用 [UIView appearance] 解决1
2
3
4if (kIsRTL()) {
[UIView appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
[UISearchBar appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
}
布局
Masonry
left、right 替换成 leading、trailing
全局搜索 .left.、 .right.、 mas_left、mas_right
注意:要Masonry库中的内容不能被替换
Frame
使用 frame 的布局需要我们自行处理适配,使用 category处理:
RTL的布局 对于 y、size 是不会影响,唯一要处理的是 frame.origin.x1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21- (void)setRTLFrame:(CGRect)frame width:(CGFloat)width {
if (kIsRTL()) {
if (self.superview == nil) {
NSAssert(0, @"must invoke after have superView");
}
CGFloat x = width - frame.origin.x - frame.size.width;
frame.origin.x = x;
}
self.frame = frame;
}
- (void)setRTLFrame:(CGRect)frame {
[self setRTLFrame:frame width:self.superview.frame.size.width];
}
- (void)resetFrameToFitRTL {
[self setRTLFrame:self.frame];
}
- 对于旧代码中的视图 Frame 且 父视图的frame已知,可以直接使用
resetFrameToFitRTL方法 - 对于新代码可以考虑直接使用 Masonry 布局
- 特殊情况就要特殊处理,比如Masonry布局影响性能,使用Frame布局
contentHorizontalAlignment
1 | typedef NS_ENUM(NSInteger, UIControlContentHorizontalAlignment) { |
UIControlContentHorizontalAlignmentLeft、UIControlContentHorizontalAlignmentRight 换成 UIControlContentHorizontalAlignmentLeading 、UIControlContentHorizontalAlignmentTrailing
TextAlignment
官方中 textAlignment ios9 系统之后,默认是使用 NSTextAlignmentNatural,这个属性也是会自动适配RTL
UITextField
UITextFileld 中的书写方向会根据键盘的语言,当前编辑的内容自动适配
UILabel
虽然系统默认使用 NSTextAlignmentNatural 适配方向,还是需要手动设置的方向适配
使用 交换方法 处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20static char kAssociatedObjectKeyLabelTextAlignment;
- (NSTextAlignment)rtl_textAlignment {
return [objc_getAssociatedObject(self, &kAssociatedObjectKeyLabelTextAlignment) integerValue];
}
- (void)setRtl_textAlignment:(NSTextAlignment)rtl_textAlignment {
objc_setAssociatedObject(self, &kAssociatedObjectKeyLabelTextAlignment, @(rtl_textAlignment), OBJC_ASSOCIATION_ASSIGN);
if (kIsRTL) {
if (rtl_textAlignment == NSTextAlignmentNatural || rtl_textAlignment == NSTextAlignmentLeft) {
rtl_textAlignment = NSTextAlignmentRight;
} else if (rtl_textAlignment == NSTextAlignmentRight) {
rtl_textAlignment = NSTextAlignmentLeft;
}
}
self.textAlignment = rtl_textAlignment;
}
文本拼接混排的情况
阅读习惯的不同要区分文本拼接是左到右,还是右到左

根据首个字符是什么语言,文本的方向就会自动改变
比如:”天气是 المطر”
- “天气是المطر”,”天气” 提到前面就是 LTR 的顺序,APP显示为:”天气是 المطر”
- “المطر天气是”,” المطر” 放到前面就是 RTL 的顺序,APP显示为:”天气是 المطر”
为了适配这种情况,可以在字符串前面加一些不会显示的字符,强制将字符串变为LTR或者RTL。
在字符串前面添加\u202B表示RTL,加\u202A 表示LTR
iOS 将@也当成了阿拉伯语 的一部分,我们需要对@手动添加 LEFT-TO-RIGHT 标志 \u200E,声明为LTR展示 @"\u200E@"
hook UILabel 中设置文本方向处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25BOOL isRTLString(NSString *string) {
if ([string hasPrefix:@"\u202B"] || [string hasPrefix:@"\u202A"]) {
return YES;
}
return NO;
}
NSString *RTLString(NSString *string) {
if (string.length == 0 || isRTLString(string)) {
return string;
}
if (kIsRTL()) {
string = [@"\u202B" stringByAppendingString:string];
} else {
string = [@"\u202A" stringByAppendingString:string];
}
return string;
}
@implementation UILabel (RTL)
- (void)rtl_setText:(NSString *)text {
[self rtl_setText:RTLString(text)];
}
@end
NSMutableParagraphStyle
方法1:对于富文本直接设置 UILabel 的 textAlignment 无效,要使用 NSMutableParagraphStyle 设置 textAlignment,可以 hook NSMutableParagraphStyle 设置正确的 textAlignment
1 | @implementation NSMutableParagraphStyle (RTL) |
UIImage
系统提供了图片镜像处理的方法1
- (UIImage *)imageFlippedForRightToLeftLayoutDirection NS_AVAILABLE_IOS(9_0);
比如导航栏的返回键显示1
2
3
4
5
6
7
8
9
10- (UIImage *)rtl_imageFlippedForRightToLeftLayoutDirection {
if (kIsRTL) {
return [UIImage imageWithCGImage:self.CGImage
scale:self.scale
orientation:UIImageOrientationUpMirrored];
}
return self;
}
定义一个宏方法,如果遇到需要反转的图片就使用这个方法1
也可以直接在 image Assets.xcassets 资源包中设置
UIButton
UIEdgeInsets 也会影响到布局,系统没有处理这个内容,手动处理
hook了UIButton的setContentEdgeInsets,setImageEdgeInsets,setTitleEdgeInsets方法在RTL情况下,手动调换left <-> right
1 | UIEdgeInsets RTLEdgeInsetsWithInsets(UIEdgeInsets insets) { |
UIScrollView
分页控制器处理
将ScrollView旋转180度,上面的子视图再旋转180度1
2
3self.scrollView.transform = CGAffineTransformMakeRotation(M_PI);
subView.transform = CGAffineTransformMakeRotation(M_PI);
UICollectionView 水平方向
继承UICollectionViewFlowLayout 重写两个方法1
2
3
4
5
6
7
8
9
10
11-(UIUserInterfaceLayoutDirection)effectiveUserInterfaceLayoutDirection {
if (isRTL()) {
return UIUserInterfaceLayoutDirectionRightToLeft;
}
return UIUserInterfaceLayoutDirectionLeftToRight;
}
- (BOOL)flipsHorizontallyInOppositeLayoutDirection{
return YES;
}
总结
1.尽量使用AutoLayout处理布局,不要用left和right,改用 leading 和 trailing。
2.多语言遇到拼接字符串的话,要谨慎。其他LTR语言中,如果是拼接字符串,第一个字符是RTL语言,会出现显示问题
3.图片翻转也要谨慎使用
遇到问题
1
1 | A constraint cannot be made between a leading/trailing attribute and a right/left attribute. Use leading/trailing for both or neither. |
解决:
你在制定约束时,约束的对象和参考对象没有使用同一约束的成员属性。前者使用 leading,后者也要使用 leading
