iOS开发-阿拉伯适配RTL

什么是RTL布局

RTL布局是针对中东地区的书写习惯适配的一套从右向左的布局方式,字面意思就是Right-to-Left.
针对iOS9之后的RTL适配,苹果有一个官方文档教我们怎么做适配。

举个例子对比:

### 适配RTL之前的一个小技巧

在Xcode的项目图标点击Edit Scheme ->Run->Options,找到Application Language,可以指定app运行时的语言环境, 就不需要手动进入设置修改系统语言了.
如下图:

开始适配

切换RTL布局

一般我们做了阿拉伯语言的适配之后, 用户只要系统语言是阿拉伯语言环境, 系统就会自动帮我们的app设置为RTL布局. 但是有一种情况是比如产品需要我们添加一个app内切换语言的功能, 其中就包括阿拉伯语言. 这时App语言不一定跟系统语言保持一致, 也许系统是英文,App内部设置成了阿拉伯语. 我们依然需要变成RTL布局,系统是不会帮我们完成这项任务的,我们只有自己来设置RTL.

由于目前我们的项目iOS支持版本是从iOS9开始, 正好iOS9之后系统提供了相应的API, 可以来强行将视图的布局切换为RTL布局.

1
2
3
4
5
6
7
8
9
typedef NS_ENUM(NSInteger, UISemanticContentAttribute) {
UISemanticContentAttributeUnspecified = 0,
UISemanticContentAttributePlayback, // for playback controls such as Play/RW/FF buttons and playhead scrubbers
UISemanticContentAttributeSpatial, // for controls that result in some sort of directional change in the UI, e.g. a segmented control for text alignment or a D-pad in a game
UISemanticContentAttributeForceLeftToRight,
UISemanticContentAttributeForceRightToLeft
} NS_ENUM_AVAILABLE_IOS(9_0);

@property (nonatomic) UISemanticContentAttribute semanticContentAttribute NS_AVAILABLE_IOS(9_0);

UView有一个semanticContentAttribute的属性,当我们将其设置成UISemanticContentAttributeForceRightToLeft之后,UIView将强制变为RTL布局。当然在非RTL语言下,我们需要设置它为UISemanticContentAttributeForceLeftToRight,来适配系统是阿拉伯语,App是其他语言不需要RTL布局的情况。

一般情况下, 我们设置[UIView appearance]semanticContentAttribute属性就能达到绝大部分视图的RTL布局效果, 因为基本所有视图都继承于UIView.

1
[UIView appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;

使用[UIView appearance]设置后,大部分的View看上去正常了。除了搜索栏。使用[UIView appearance]设置后,搜索栏是不生效的。不过不用担心,我们只需要设置一下[UISearchBar appearance]即可.

1
[UISearchBar appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;

布局适配

Autolayout 约束布局

设置完viewsemanticContentAttribute后,如果使用的是Autolayout布局,并且Autolayout下,使用的是leadingtrailing,系统会自动帮助我们调整布局,将其适配RTL。但是如果使用的是leftright,系统是不会这么做的。

所以为了适配布局,我们需要将所有的left,right替换成leadingtrailing

如果使用了可视化编程, xib或者storyboard上面添加的约束本身就是使用的leadingtrailing, 所以项目中如果使用了可视化的视图, 是会自动适配RTL布局的.

注意:同一个view不要同时使用left, rightleading, trailing属性, 会crash崩溃.

Frame布局

目前我们项目中主要是Masonry和Frame两种布局混用的方式. Masonry就是上面约束的修改方式就可以完成适配.
对于Frame布局, 其实就是重新计算frame.origin.x, 因为RTL模式下, 改变只是x轴的起点, y和视图的size是不变的. 而且对于一个view, 如果知道它的父视图的width, 就可以计算出view在RTL模式下的frame, 所以我们可以给UIView封装一个分类category:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "UIView+RTL.h"

@implementation UIView (RTL)

- (void)setRTLFrame:(CGRect)frame width:(CGFloat)width {
if (kBuny_isRTL) {
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];
}

@end

这样我们只需要在使用了Frame布局的地方, 添加一句类似如下的代码:

1
2
3
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 300)];
[self.view addSubview:view];
[view resetFrameToFitRTL];

就可以完成Frame布局的适配.

从适配的工作量来讲, Frame要比Autolayout繁琐一些, 所以建议以后的新代码, 建议能用Masonry的就用Masonry, 而且要用leadingtrailing属性.

图片镜像

由于一些图片素材, 在RTL下是需要镜像反转的, 比如个人中心cell上的箭头, 我们如果没使用系统自带的, 而是用的图片素材, 那么正常模式下, 箭头是向右的, RTL模式下就需要将它镜像朝右.
系统针对这种情况提供了一个镜像的方法:

1
2
// Creates a version of this image that, when assigned to a UIImageView’s image property, draws its underlying image contents horizontally mirrored when running under a right-to-left language. Affects the flipsForRightToLeftLayoutDirection property; does not affect the imageOrientation property.
- (UIImage *)imageFlippedForRightToLeftLayoutDirection NS_AVAILABLE_IOS(9_0);

但是这个方法并不好用。通过切换系统语言,来适配RTL应该是没问题的。但是在App内部切换语言,手动修改RTL布局,系统的这个方法就经常出现错误镜像的情况。
所以我们可以再给UIImage写一个镜像的分类来达到相同的效果:

1
2
3
4
5
6
7
8
- (UIImage *)rtl_orientationUpMirrored {
if (kBuny_isRTL) {
return [UIImage imageWithCGImage:self.CGImage
scale:self.scale
orientation:UIImageOrientationUpMirrored];
}
return self;
}

Label的textAlignment

在iOS9之后UILabeltextAlignmentNSTextAlignmentNatural, 这个属性在正常语言环境下是居左的, 在RTL下则是居右的, 会自动适配RTL.

1
2
Use the default alignment associated with the current localization of the app. The default alignment for left-to-right scripts is NSTextAlignmentLeft, and the default alignment for right-to-left scripts is NSTextAlignmentRight.
@property(nonatomic) NSTextAlignment textAlignment; // default is NSTextAlignmentNatural (before iOS 9, the default was NSTextAlignmentLeft)

但是有时当我们在系统内切换语言的时候,系统经常会错误的设置textAlignment。所以我们需要自己去适配textAlignment.
UILabel为例,我们hook它的初始化方法,根据当前是否是RTL,给它一个正确的默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "UILabel+RTL.h"

@implementation UILabel (RTL)

+ (void)load {
BNYMethodSwizzling([self class], @selector(init), @selector(rtl_init));
BNYMethodSwizzling([self class], @selector(initWithFrame:), @selector(rtl_initWithFrame:));
}

- (instancetype)rtl_init {
if ([self rtl_init]) {
self.textAlignment = kBuny_isRTL ? NSTextAlignmentNatural : NSTextAlignmentLeft;
}
return self;
}

- (instancetype)rtl_initWithFrame:(CGRect)frame {
if ([self rtl_initWithFrame:frame]) {
self.textAlignment = kBuny_isRTL ? NSTextAlignmentNatural : NSTextAlignmentLeft;
}
return self;
}

@end

总结

其实根据下面的参考文章, 还有一些点需要适配, 但是根据目前项目中遇到的情况, 以上过程, 基本项目中大部分的页面都可以完成RTL的适配.

参考文章:
RTL适配历程
阿拉伯语言的自动适配

0%