Highlight
Apple 在 iOS 18 推出了多项多语言增强,包括全新的 Multilingual Keyboard(多语言键盘,无需手动切换即可输入多种语言)、Live Text 对阿拉伯语的支持、以及 10 种新的印度语系数字支持。但这场 Session 的核心不在这些系统功能,而在于教开发者如何避免多语言场景下的常见 bug。
核心内容
你的 App 支持中文输入吗?如果用户正在用拼音或手写键盘输入中文字符,你的自动补全逻辑可能会打断输入——因为 CJK 输入法会先显示「marked text」(带下划线的临时文本),用户选择候选词后 marked text 才消失。如果你的代码在每次按键时都修改文本,就会破坏这个输入流程。
同样的问题也出现在搜索场景。印地语用户搜索时,因为拼写风格不同,可能搜不到联系人;搜索高亮用粗体显示匹配文字,会导致印地语的字母和附加符号断裂。而乌尔都语的 Nastaliq 脚本在部分 App 中直接渲染崩坏,文字完全无法辨认。
Session 用三个来自不同地区的朋友——新加坡的 April(中英双语)、印度的 Raj(印地语+英语)、美国的 Ismat(英语+乌尔都语)——逐个拆解这些真实场景,每个问题都给出了具体的一行或几行代码修复方案。
详细内容
输入:keyboard 记忆与 marked text
April 在不同对话中分别用中文和英文聊天,键盘能自动记住每个对话使用的语言。实现方式是重写 textInputContextIdentifier(03:18):
override var textInputContextIdentifier: String? {
uniqueID
}
textInputContextIdentifier是UIResponder上的方法,返回一个唯一字符串- 系统根据这个标识符为每个输入上下文独立记忆语言偏好
- 同一个 App 内不同对话可以用不同的 identifier,键盘自动切换
当键盘高度变化(比如手写键盘拉高)时,需要调整 UI 布局。两种方式:inputAccessoryView 直接停靠在键盘上方(03:41),keyboardLayoutGuide 用于自定义视图与键盘的相对位置(04:00)。
CJK 输入的 marked text 问题是最容易踩的坑。修复方式是在修改文本前检查 marked text(04:42):
if textView.markedTextRange.empty {
// Perform actions involving editing text
}
markedTextRange返回当前 marked text 的范围- 如果为空,说明用户已确认候选词,可以安全修改文本
- 如果不为空,说明用户还在输入中,不应修改内联文本
- 但 marked text 存在时,你仍可以基于它搜索并在其他位置(如 table view)显示建议——Spotlight 就是这么做的
搜索:localizedStandardRange 与高亮颜色
Raj 用印地语搜索时找不到联系人,原因是搜索用了 range(of:) 做精确匹配,而印地语有多种拼写风格。改用 localizedStandardRange(05:58):
let range = text.localizedStandardRange(of: search)
localizedStandardRange是遵守 locale 规则的标准搜索 API- iOS 18 对此 API 做了重大升级,支持更多脚本的跨拼写风格匹配和跨数字系统匹配
- 一行代码替换即可修复搜索问题
搜索高亮用粗体会导致印地语文本断裂,因为粗体和常规体是不同字体,字母和附加符号无法跨字体连接。改用颜色高亮(07:24):
attributedString[range].foregroundColor = highlightColor
- 用
foregroundColor代替字体粗细变化来标记匹配文本 - 颜色属性不会改变字体,不会破坏字母与附加符号的连接
另外,斜体(italicization)在大多数语言中没有对应概念——10 种语言的「你好」中只有 3 种应用斜体后会产生视觉变化(07:32)。不要依赖斜体来传递信息差异。
显示:TextKit 2、Text Styles、人名格式化
Ismat 遇到乌尔都语渲染崩坏的问题,解决方式是使用 TextKit 2(09:39)。好消息是 SwiftUI、UIKit、AppKit 的 label 和 text view 已经默认使用 TextKit 2。
Text Styles 保证行间距适配所有用户语言(09:39):
// Text Styles
// SwiftUI
Text("Hello, world!") // uses .body Text Style by default
Text("Hello, world!").font(.title)
// UIKit
let label = UILabel()
label.text = "Hello, world!"
label.font = UIFont.preferredFont(forTextStyle: .body)
// AppKit
let textField = NSTextField(labelWithString: "Hello, world!")
textField.font = NSFont.preferredFont(forTextStyle: .body)
// Keep clipsToBounds off
clipsToBounds = false
- Text Styles 会考虑 App 语言和用户所有偏好语言,自动调整行间距
clipsToBounds必须保持false,因为很多语言的文字需要渲染到视图边界之外- 不需要为任何特定语言写专门代码
如果内容有明确的语言,可以用 typesettingLanguage 指定(10:03):
// SwiftUI
Text(verbatim: "Hello, world!").typesettingLanguage(.init(languageCode: .english))
// UIKit
let label = UILabel()
label.text = "Hello, world!"
label.traitOverrides.typesettingLanguage = Locale.Language(languageCode: .english)
- 默认行为考虑用户所有偏好语言(行间距更大以适配如乌尔都语)
- 指定语言后,排版使用该语言的规则(如英语行间距更紧凑)
人名显示用 formatted API(10:29):
// Formatting names
let nameComponents = PersonNameComponents
(givenName: "瑗珺", familyName: "汪", nickname: "珺珺")
// Short Name (respects settings like "Prefer Nicknames")
let shortName =
nameComponents.formatted(.name(style: .short)) // 珺珺
// Abbreviated Name (can be used for monograms)
let monogram =
nameComponents.formatted(.name(style: .abbreviated)) // 汪
- 中文和日文中只显示 given name 不合适,short style 会在合适时使用 nickname
- abbreviated style 用于 monogram 等场景,中文中会返回 family name
- 这个 API 会尊重用户在「语言与地区」中的「偏好昵称」设置
本地化:语法引擎与数字格式化
iOS 18 将自动语法一致(automatic grammatical agreement)引擎扩展到了印地语和韩语(12:20)。支持的 8 种语言:德语、英语、西班牙语、法语、意大利语、葡萄牙语、印地语、韩语。
数字格式化有两种方式。在代码中用字符串插值(13:43):
Text("\(numberOfDays)-day forecast")
numberOfDays在Text中会自动按当前 locale 格式化- 适合运行时确定数字且所有语言都用数字的场景
今年新增的方式是直接在本地化字符串中格式化数字(14:21):
AttributedString(localized: "10-day forecast")
AttributedString(localized: "0.5× zoom")
- 零代码即可在本地化字符串中格式化数字
- 翻译时可以用
formatNumber确保正确数字系统,如印地语翻译中使用印度数字 - 还能处理小数分隔符等格式差异
核心启发
-
做什么:检查搜索实现是否用了
localizedStandardRange。为什么值得做:印地语、阿拉伯语等语言有大量拼写变体,range(of:)精确匹配会让用户搜不到内容,iOS 18 的localizedStandardRange已支持跨拼写风格和跨数字系统匹配。怎么开始:全局搜索range(of:)和rangeOfString,替换为localizedStandardRange(of:),一行代码即可。 -
做什么:检查自动补全逻辑是否处理了 marked text。为什么值得做:CJK 用户输入时如果被自动补全打断,体验极差——输入法流程被破坏,用户必须重输。怎么开始:在所有修改文本的逻辑前加
if textView.markedTextRange.empty判断,marked text 存在时只更新外部建议列表,不修改内联文本。 -
做什么:搜索高亮用颜色代替粗体,不在多语言场景中依赖斜体。为什么值得做:粗体会导致印地语等语言的字母与附加符号断裂(不同字体无法连接),斜体在大多数语言中没有视觉效果。怎么开始:将搜索高亮的
font: .bold改为foregroundColor,审查所有用斜体传递信息差异的地方,改用颜色或字号区分。 -
做什么:确认
clipsToBounds在所有 label 和 text view 上保持false。为什么值得做:很多语言的文字(如乌尔都语 Nastaliq 脚本、印地语的附加符号)需要渲染到视图边界之外,裁剪会导致文字截断或不可读。怎么开始:搜索所有设置clipsToBounds = true的 label/text view,确认是否有必要,大部分情况下保持默认false即可。
关联 Session
- What’s new in UIKit — UIKit 更新,包含 TextKit 2 改进和新 API
- Discover SwiftUI enhancements — SwiftUI 改进,包括文本渲染和布局增强
- What’s new in SF Symbols 6 — SF Symbols 支持多种语言和脚本的本地化图标
- What’s new in SwiftUI — SwiftUI 文本自定义和布局改进
评论
GitHub Issues · utterances