如何为相邻单词应用.accessibilitySpeechAdjustedPitch属性并让VoiceOver正确朗读?
我之前也碰到过VoiceOver这个坑,确实给相邻的带语音属性的单词加accessibilitySpeechAdjustedPitch时,会触发这种奇怪的“读空格”问题,折腾了好一阵才找到靠谱的通用解决办法,跟你分享下:
核心问题原因
本质上是VoiceOver对带自定义语音属性的文本片段的优先级处理逻辑导致的:当你给两个独立的相邻文本片段(比如“Spain”和“falls”)分别加上语音属性时,中间的空格会被识别成一个“无属性的独立文本块”,反而触发了异常播报。你尝试给空格单独加accessibilitySpeechSpellsOutCharacters = false没用,是因为VoiceOver会优先处理带自定义语音属性的片段,空格的低优先级属性会被直接忽略。
通用解决方案:合并相邻的属性化片段
因为你要做的是通用函数(处理数组内所有目标单词),核心思路是在构建AttributedString时,把连续的、需要加语音属性的片段(包括中间的空格)合并成一个完整的文本块,统一加属性,而不是给单个单词分别标记。具体步骤如下:
1. 拆分原始文本为片段列表
先把原始字符串拆分成「需要加属性的目标单词」和「普通文本」的片段列表,每个片段标记是否属于目标(需要加adjustedPitch)。这里要注意全词匹配,避免误标记子串(比如不要把“Spainish”里的“Spain”当成目标,可用正则的\\b边界匹配实现)。
2. 合并连续的目标片段
遍历片段列表,把连续的、属于目标的片段(包括中间的空格)合并成一个大的子串,这样就不会让空格成为独立的无属性块。
3. 为合并后的片段统一加属性
给合并后的完整子串统一添加accessibilitySpeechAdjustedPitch属性,普通片段保持原样,最后拼接成最终的AttributedString。
伪代码示例(核心逻辑)
// 伪代码:通用语音属性处理函数 func applySpeechPitchToTargetWords(_ targetWords: [String], in sourceString: String) -> AttributedString { // 辅助结构体:标记文本片段是否为目标 struct TextSegment { let text: String let isTarget: Bool } // 1. 拆分原始文本为片段列表(这里需要自己实现正则匹配全词的逻辑) let rawSegments = splitSourceIntoSegments(sourceString, targetWords: targetWords) // 2. 合并相邻的目标片段 var mergedSegments: [TextSegment] = [] var currentMergedTarget: TextSegment? for segment in rawSegments { if segment.isTarget { if var current = currentMergedTarget { // 合并当前目标片段(包含中间空格) current.text += segment.text currentMergedTarget = current } else { currentMergedTarget = segment } } else { // 把之前合并的目标片段加入列表 if let merged = currentMergedTarget { mergedSegments.append(merged) currentMergedTarget = nil } mergedSegments.append(segment) } } // 加入最后一个未处理的合并片段 if let merged = currentMergedTarget { mergedSegments.append(merged) } // 3. 为片段添加属性并拼接 var finalAttributed = AttributedString("") for segment in mergedSegments { var subAttributed = AttributedString(segment.text) if segment.isTarget { subAttributed.accessibilitySpeechAdjustedPitch = 0.1 } finalAttributed += subAttributed } return finalAttributed }
为什么这个方法有效
当你把“Spain”、“空格”、“falls”合并成一个连续子串,再统一加adjustedPitch属性时,VoiceOver会把它当成一个完整的语音单元处理,中间的空格会被识别为正常的文本间隔,不会被单独播报。这个逻辑同样适用于.accessibilitySpeechSpellsOutCharacters属性的问题(比如电话号码的数字块处理)。
额外小提示
实现片段拆分时,用NSRegularExpression的全词匹配(\\b\\(word)\\b)可以避免误匹配,同时要考虑大小写敏感/不敏感的需求,根据业务场景调整匹配规则。
我用这个思路做的通用工具函数,处理过各种相邻目标单词的场景,VoiceOver都能正常朗读,再也没出现过读“space”的问题,你可以试试这个方向~
内容来源于stack exchange

