用一套 View,同时支持「全局 RTL、全局 LTR、局部 RTL、局部 LTR、混合排版」核心就是:
- 用 semanticContentAttribute 控制 "流向"
- 布局只写 leading/trailing,永远不写 left/right
- 文本用 natural 对齐
- 图标用 directional asset / 自动翻转
- 混合文字用 Unicode 控制符 修正
下面从原理 → 架构 → 代码 → 坑点,一次说清,项目可以使用。
一、核心原理:每个 View 都有自己的 "流向"
iOS 9+ 每个 UIView 都有:
swift
var semanticContentAttribute: UISemanticContentAttribute
.unspecified:继承父视图.forceLeftToRight:强制 LTR.forceRightToLeft:强制 RTL
**结论:**你可以让 页面整体 RTL,但某个子 View(如手机号、URL、时间轴)强制 LTR ;也可以反过来。完全做到同一屏 RTL/LTR 共存。
二、整体架构:三层流向控制(一套 View 走天下)
1. 全局层:App 整体方向
swift
// 语言切换时调用
func setAppLayoutDirection(isRTL: Bool) {
let attr: UISemanticContentAttribute =
isRTL ? .forceRightToLeft : .forceLeftToRight
// 全局默认
UIView.appearance().semanticContentAttribute = attr
UINavigationBar.appearance().semanticContentAttribute = attr
UITabBar.appearance().semanticContentAttribute = attr
}
此时所有控件 默认跟随全局 RTL/LTR。
2. 页面层:VC 统一流向(可选)
scss
override func viewDidLoad() {
super.viewDidLoad()
view.semanticContentAttribute = isRTL ? .forceRightToLeft : .forceLeftToRight
}
页面内所有子 View 默认继承页面流向。
3. 局部层:单个 View 强制方向(关键!共存核心)
ini
// 手机号、验证码、URL、时间轴、播放器控制 → 强制 LTR
phoneLabel.semanticContentAttribute = .forceLeftToRight
urlLabel.semanticContentAttribute = .forceLeftToRight
// 纯阿拉伯语文案 → 跟随全局(或强制 RTL)
arabicLabel.semanticContentAttribute = .unspecified
这就是 "一套 View 共存" 的核心:局部覆盖全局。
三、布局:永远只用 leading/trailing(AutoLayout 自动镜像)
❌ 错误(写死方向,不能共存)
ini
label.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
✅ 正确(自动适配 RTL/LTR)
ini
label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
- LTR:leading = 左,trailing = 右
- RTL:leading = 右,trailing = 左系统自动切换,一套约束走天下。 Apple Developer
间距 / 内边距适配
less
// 统一写法,自动交换 left/right
let inset: UIEdgeInsets = isRTL
? UIEdgeInsets(top: 8, left: 16, right: 8, bottom: 8)
: UIEdgeInsets(top: 8, left: 8, right: 16, bottom: 8)
或用扩展:
swift
extension UIEdgeInsets {
func rtlFlipped() -> UIEdgeInsets {
UIEdgeInsets(top: self.top, left: self.right, right: self.left, bottom: self.bottom)
}
}
四、文本:natural 对齐 + 混合文字修正
1. 对齐方式
ini
label.textAlignment = .natural
- LTR:左对齐
- RTL:右对齐自动适配,不用改代码。
2. 混合文字(阿语 + 英文 / 数字)
系统默认会自动分段,但遇到 @、#、链接、手机号时会错乱。用 Unicode 控制符 强制 LTR:
ini
// \u200E = LEFT-TO-RIGHT MARK(强制 LTR)
let text = "مرحبا \u{200E}@username \u{200E}13800138000"
label.text = text
这样 @username 和手机号永远 LTR,阿语部分 RTL,完美共存。
五、图标:Directional Asset(自动镜像)
1. 箭头类图标(返回、前进、左右箭头)
在 Asset Catalog 中勾选 Directional:
- LTR:显示原图
- RTL:自动水平翻转不用代码,一套图片走天下。 Apple Developer
2. 非方向图标(相机、搜索、设置)
不勾选 Directional,永远不翻转。
六、自定义控件适配(一套代码,双向渲染)
示例:自定义按钮(带图标 + 文字)
swift
class RTLButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
// 系统已根据 semanticContentAttribute 自动翻转 leading/trailing
// 只需确保图标和文字用 natural 对齐 + directional 图标
}
override var semanticContentAttribute: UISemanticContentAttribute {
didSet {
// 流向变化时刷新布局
setNeedsLayout()
}
}
}
关键点:自定义控件不要硬编码 frame 的 x 坐标,全部用 AutoLayout + leading/trailing。
七、手势与动画:自动反转 + 局部覆盖
1. 手势方向
ini
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeHandler))
swipe.direction = isRTL ? .left : .right
- LTR:右滑 → 前进
- RTL:左滑 → 前进
2. 页面切换动画
系统导航栈 自动反转 push/pop 方向:
- LTR:push 从右进,pop 从左出
- RTL:push 从左进,pop 从右出无需手动写动画代码。
八、常见坑
- 用了 left/right 约束 → RTL 不翻转,布局错乱
- 自定义控件硬编码 frame → 镜像后位置错误
- 图标没设为 Directional → 箭头方向反,用户困惑
- 混合文字没加 \u200E → 英文 / 数字倒序
- 第三方库用了 left/right → 全局错乱(如旧版 Masonry)
九、一句话总结
通过 semanticContentAttribute 实现全局与局部流向控制,布局全程使用 leading/trailing,文本设为 natural 对齐,箭头类图标采用 Directional Asset,混合文字用 Unicode 控制符修正,手势动画按 RTL 动态反转,从而用一套 View、一套代码完美支持 RTL/LTR 全局与局部共存。