Highlight
Xcode 12 随 Swift tools version 5.3 为 Swift Package Manager 加入资源与本地化支持,让 package target 可以携带图片、storyboard、Core Data model、字符串文件等资源,并通过
Bundle.module在运行时访问它们。
核心内容
Swift package 很适合把一段业务逻辑拆出去复用。问题出现在这段逻辑开始需要界面和资源的时候:一个骰子视图可以放进 package,按钮也能刷新点数,但图片资源不在 module 里,预览里只剩一个黑色方块。开发者要么把资源留在客户端 App,要么自己约定文件路径,复用边界很快变得脆弱。
Xcode 12 把这件事补上了。Package target 现在可以包含图片、storyboard、Core Data model 和其他运行时文件。文件类型清楚时,Xcode 会按后缀自动处理;文件用途含糊时,开发者在 Package.swift 里声明它应该被排除、处理,还是原样复制。
资源被构建成属于每个 code module 的 resource bundle。代码在运行时通过 Foundation 的 Bundle API 访问它,Swift 代码里最直接的入口是 Bundle.module。这个设计延续了系统已有 API,所以 package 增加资源不会提高 package 支持的最低 OS 版本。
本地化也进入同一套模型。Package manifest 声明 defaultLocalization,资源目录里放 en.lproj、fr.lproj 这样的 localization directory,SwiftUI 和 UIKit 继续通过 bundle 参数寻找字符串和图片。这样,一个 package 提供的功能可以带着自己的默认语言、翻译文件和本地化资源一起分发。
详细内容
Package target 可以直接携带明确类型的资源
(01:10)演示从一个 SwiftUI 骰子视图开始。Xcode 12 可以在没有客户端 App 的 workspace 里预览 package 内的 SwiftUI view。视图逻辑能运行,按钮能改变点数,缺的是显示点数的图片。
(01:42)演讲者在 DiceUI target 旁边创建 asset catalog,并放入不同分辨率的骰子图片。因为 .xcassets 后缀有明确含义,Xcode 知道它应该在构建时被处理并打包进客户端产品。
关键 API 是给资源加载入口传入 module bundle:
Image("dice-one", bundle: .module)
关键点:
Image仍然使用 SwiftUI 的图片加载入口。"dice-one"是 asset catalog 中的资源名,session 中的图片名称按点数匹配。bundle: .module指向当前 package module 的 resource bundle。- transcript 提到完整写法可以指定 module resource bundle,随后可借助 Swift 类型推断简写为
.module。
manifest 负责说明含糊文件的用途
(03:13)Xcode 会根据文件后缀判断一部分资源。Asset catalog、storyboard、Core Data model 这类文件有清楚用途,直接加入 target 即可。纯文本、普通图片、shell script 或任意目录的用途更含糊,它们可能是运行时资源,也可能只是开发资料。
(04:09)官方代码片段展示了在 package manifest 里声明 target 文件意图的方式:
// swift-tools-version:5.3
import PackageDescription
let package = Package(name: "MyGame",
products: [
.library(name: "GameLogic",
targets: ["GameLogic"])
],
targets: [
.target(name: "GameLogic",
excludes: [
"Internal Notes.txt",
"Artwork Creation"],
resources: [
.process("Logo.png"),
.copy("Game Data")]
)
]
)
关键点:
// swift-tools-version:5.3是资源支持的工具链边界。SwiftPM 的资源能力来自 Swift 5.3。excludes会让构建系统跳过内部笔记和设计素材目录。.process("Logo.png")表示这个文件需要作为资源处理。多数资源应使用process,让构建系统按目标平台应用内置规则。.copy("Game Data")表示目录要原样复制,保留层级结构,适合运行时需要按目录访问的数据。
process 和 copy 的差别影响构建产物
(06:56)process 会对目录递归应用处理规则。Asset catalog 或 storyboard 会被转换成适合运行时使用的形式,图片也可能按平台规则被压缩。文件类型未知或不需要特殊处理时,最终也会被复制进产品。
(07:35)copy 的语义更直接:按字节复制,不做转换。session 给出的典型场景是保留整个目录结构,或者把本来可能被编译的源文件作为运行时模板使用。
这两个选择的关键差异可以简化成 manifest 决策:
.process("Logo.png")
.copy("Game Data")
关键点:
process是默认优先考虑的资源动作,因为它允许 Xcode 使用平台内置规则。copy适合资源目录结构本身就是运行时协议的一部分。- 两者都属于 target 的
resources数组,作用范围限定在声明它们的 package target。
Bundle.module 是 module 内部的资源入口
(07:59)构建时,target 的源文件会编译成 code module,target 的资源会处理成该 module 对应的 resource bundle。Apple 平台上的 App 和 extension 本身是 bundle,package 的 resource bundle 会嵌入到 App bundle 中。命令行工具这类 unbundled product 需要把 resource bundle 安装在工具旁边。
(09:38)运行时访问资源使用 Foundation 的 Bundle API。Xcode 为包含资源的 package target 合成 bundle accessor,Swift 和 Objective-C module 都能拿到对应 bundle。Swift 中的常用入口是:
let logoURL = Bundle.module.url(forResource: "Logo", withExtension: "png")
关键点:
Bundle.module返回当前 code module 对应的 resource bundle。url(forResource:withExtension:)是现有 Foundation API,package 资源没有引入新的资源查找模型。- 这个 accessor 只对同一 module 内的代码可见。
- 如果其他 module 需要资源,session 建议提供类型明确的 accessor,例如返回某张图片或某个数据文件,避免把整个 bundle 暴露成公共依赖。
本地化资源使用 defaultLocalization 和 .lproj
(11:27)Xcode 12 也支持 package 内的 localized resources。第一步是在 manifest 里声明默认开发语言。这个语言会作为运行时没有更合适语言时的 fallback。
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "DiceUI",
defaultLocalization: "en",
targets: [
.target(name: "DiceUI")
]
)
关键点:
defaultLocalization: "en"声明 package 的默认本地化语言。- session 明确说明,包含资源的 package 需要声明默认 localization。
- 语言 ID 使用 Apple 平台本地化中相同的语言标识规则。
(12:02)接着创建 en.lproj 和 fr.lproj,每个目录里放 Localizable.strings。.lproj 后缀让 Xcode 明白这是 localization directory,所以通常不用在 manifest 的 resources 里单独列出。
Sources/DiceUI/en.lproj/Localizable.strings
Sources/DiceUI/fr.lproj/Localizable.strings
关键点:
Localizable.strings保存 key 到目标语言文本的映射。.stringsdict也可以放在 localization directory 中,用于复数等规则。- localization directory 还可以包含某个语言专用的资源版本。
- Asset catalog 自身也可以本地化,在不同语言中提供不同图片。
(13:01)SwiftUI 文本同样通过 module bundle 取本地化字符串:
Text("roll", bundle: .module)
关键点:
"roll"是本地化 key,session 中英语映射为roll,法语映射为对应翻译。bundle: .module让 SwiftUI 到 package 的 resource bundle 中查找字符串。- SwiftUI preview 可以通过 environment 覆盖语言,session 用法语预览验证了 package 内字符串会生效。
核心启发
-
做什么:把带 UI 的共享组件真正封装成 Swift package。为什么值得做:Xcode 12 可以让 package target 携带 asset catalog,SwiftUI preview 也能在没有客户端 App 的 workspace 里显示资源。怎么开始:把组件自己的图片放入 target 旁边的 asset catalog,加载图片时传入
bundle: .module。 -
做什么:把示例数据、模板文件或游戏关卡放进 package。为什么值得做:manifest 可以区分
.process和.copy,运行时数据不用再依赖客户端 App 的目录约定。怎么开始:对需要平台处理的资源使用.process(...),对必须保留目录层级的资源使用.copy(...)。 -
做什么:为 package 提供窄口径资源访问 API。为什么值得做:
Bundle.module只在同一 module 内可见,session 也建议不要把整个 bundle 暴露给其他 module。怎么开始:在 package 内部写ImageProvider、TemplateLoader或类似类型,只返回调用方真正需要的图片、URL 或数据。 -
做什么:让可复用组件自带本地化字符串。为什么值得做:package 可以声明
defaultLocalization,并携带.lproj下的Localizable.strings和.stringsdict。怎么开始:先为默认语言创建en.lproj/Localizable.strings,再添加目标语言目录,SwiftUI 文本使用bundle: .module。 -
做什么:在 package 里预览不同语言的 UI。为什么值得做:session 演示了通过 SwiftUI preview 的 environment 覆盖语言,直接验证法语字符串来自 generated resource bundle。怎么开始:为关键 view 增加本地化 preview,覆盖 locale 后检查按钮文案、图片和布局是否仍然正确。
关联 Session
- Distribute binary frameworks as Swift packages — 继续看 Swift Package Manager 在 Xcode 12 中如何分发预编译 XCFramework。
- Explore Packages and Projects with Xcode Playgrounds — 了解 Xcode 12 的现代构建系统怎样处理 playground、package、framework 和 resources。
- Formatters: Make data human-friendly — 学习把日期、单位、姓名、列表、数字和 stringsdict 交给系统本地化。
- Build localization-friendly layouts using Xcode — 继续处理翻译长度、从右到左语言和 Xcode 本地化布局预览。
- What’s new in Swift — 回到 Swift 5.3、package 生态和开发者体验更新的整体背景。
评论
GitHub Issues · utterances