在 SwiftUI Text 中控制孤行:挖掘未公开的 avoidsOrphans

在 SwiftUI Text 中控制孤行:挖掘未公开的 avoidsOrphans

💡 原文中文,约5000字,阅读约需12分钟。
📝

内容提要

SwiftUI 的 Text 默认避免段落末行出现孤词,但可能导致上一行大片空白。UIKit 可关闭此策略,SwiftUI 却无公开接口。文章介绍通过创建 .swiftinterface 文件,利用 -module-abi-name SwiftUI 补全 avoidsOrphans 声明,从而在 iOS 16+ 调用该隐藏 API。此方法技术上可行,但属未公开接口,存在审核风险,不建议用于正式发布。

🔎

延伸解读

技术原理与风险

文章通过创建 .swiftinterface 文件,利用 -module-abi-name SwiftUI 补全 avoidsOrphans 声明,从而调用系统隐藏 API。该方法依赖 Swift 编译器与现有系统 ABI,未修改 SDK 文件,但属于未公开接口,存在审核风险,且无兼容性承诺,可能导致启动崩溃。

与 UIKit 的对比

UIKit 可通过 NSParagraphStyle 的 lineBreakStrategy 控制孤行,而 SwiftUI 无公开接口。文章尝试通过 NSAttributedString 设置段落样式,但 Text 会覆盖外部配置,因此不可行。相比之下,UIKit 提供了更直接的控制,但 SwiftUI 的隐藏 API 提供了一种替代方案。

实际应用建议

文章强调,尽管技术上可行,但 avoidsOrphans 未公开,不适合正式发布。建议开发者接受 SwiftUI 默认行为,或在局部使用 UIKit/TextKit 能力。若需使用,应收敛到兼容层,并注意审核风险。

Q&A

SwiftUI 的 Text 默认如何处理孤行?

SwiftUI 的 Text 默认采用 .standard 换行策略,其中包含 .pushOut,当段落末行可能只剩一个孤词或孤字时,会自动将上一行的完整词组一并推到末行,避免孤行,但可能导致上一行出现大片空白。

为什么在 SwiftUI 中无法通过 NSAttributedString 设置 lineBreakStrategy 来关闭孤行避免?

因为 Text 在渲染时会根据当前的 EnvironmentValues 重新构造并写入段落样式,覆盖掉外部传入的 lineBreakStrategy 配置,所以即使通过 NSAttributedString 设置了 lineBreakStrategy,Text 也不会遵守。

如何在 SwiftUI 中调用未公开的 avoidsOrphans 方法?

需要创建一个 .swiftinterface 文件,声明 avoidsOrphans 方法,并通过 -module-abi-name SwiftUI 让编译器生成与系统 SwiftUI 相同的 ABI 符号。具体步骤包括:创建 Modules 文件夹,放入 SwiftUI_SPI.swiftinterface,在 Build Settings 的 Module Import Paths 中添加该目录,并将 Deployment Target 设为 iOS 16 或更高版本。

使用 avoidsOrphans 方法有哪些风险?

avoidsOrphans 是未公开的 API,不在 Apple 公开 SDK 接口中,因此违反 App Store 审核指南 2.5.1(仅使用公开 API)。虽然它不同于传统私有 API 调用,但审核风险依然存在,且没有兼容性承诺,最坏可能导致启动崩溃。

avoidsOrphans 方法在哪些 iOS 版本上可用?

根据文章,avoidsOrphans 符号自 iOS 16.x 开始出现在系统二进制中,因此至少从 iOS 16 起可用。文章作者在本地验证到 iOS 16.4。

为什么说 avoidsOrphans 是未公开的 API?

因为 Apple 随 SDK 分发的模块接口已经删除了 avoidsOrphans 的声明,开发者无法直接通过 import SwiftUI 调用它。但该符号存在于系统二进制中,说明 SwiftUI 内部已具备此能力,只是未公开。

使用 avoidsOrphans 后,项目需要做其他调整吗?

不需要。.swiftinterface 文件不会生成或嵌入 SwiftUI_SPI.framework,App Bundle 中不会多出动态库。其他源文件仍可正常使用 import SwiftUI,只需在需要隐藏接口的文件中改为 import SwiftUI_SPI。建议将相关调用收敛到一个兼容层文件中。

如何自行探索 SwiftUICore 中的导出符号?

可以使用以下命令:SDK_PATH=$(xcrun --sdk iphoneos --show-sdk-path) 然后使用 rg 和 swift-demangle 工具搜索 SwiftUICore.tbd 中的符号,例如:rg -o '\$s[[:alnum:]_]+' "$SWIFTUI_CORE_TBD" | sort -u | xcrun swift-demangle。

🏷️

标签

➡️

继续阅读