如何在 Swift 包管理器库中包含资产/资源?
How to include assets / resources in a Swift Package Manager library?
我想使用 Apple 的 Swift 包管理器发送我的库。但是,我的库包含一个 .bundle 文件,其中包含用不同语言翻译的多个字符串。 使用 cocoapods,我可以使用 spec.resource 包含它。但是在 SwiftPM,我做不到。有什么解决办法吗?
包管理器还没有任何关于如何将资源与目标捆绑在一起的定义。我们知道这样做的必要性,但还没有具体的建议。我提交了 https://bugs.swift.org/browse/SR-2866 以确保我们有一个错误跟踪这个。
由于 目前 尚不支持框架捆绑包,为捆绑包资产提供 SPM 目标的唯一方法是通过捆绑包。如果您在框架中实现代码以在主项目(支持资产包)中搜索特定包,则可以从所述包中加载资源。
示例:
访问捆绑资源:
extension Bundle {
static func myResourceBundle() throws -> Bundle {
let bundles = Bundle.allBundles
let bundlePaths = bundles.compactMap { [=10=].resourceURL?.appendingPathComponent("MyAssetBundle", isDirectory: false).appendingPathExtension("bundle") }
guard let bundle = bundlePaths.compactMap({ Bundle(url: [=10=]) }).first else {
throw NSError(domain: "com.myframework", code: 404, userInfo: [NSLocalizedDescriptionKey: "Missing resource bundle"])
}
return bundle
}
}
利用捆绑资源:
let bundle = try! Bundle.myResourceBundle()
return UIColor(named: "myColor", in: bundle, compatibleWith: nil)!
您可以对所有资源文件应用相同的逻辑,包括但不限于故事板、xib、图像、颜色、数据块和各种扩展名的文件(json、txt 等)。
注意:有时这是有道理的,有时则不然。确定使用以拥有项目的自由裁量权。需要非常具体的场景才能证明将 Storyboards/Xibs 分成捆绑资产是合理的。
我为此使用的解决方案是将我需要的数据构建到一个 Swift 对象中。为此,我有一个 shell 脚本,它将读取一个输入文件,对其进行 base64 编码,然后编写一个 Swift 文件,将其呈现为 InputStream。然后,当我想向我的 Swift 包添加数据项时,我 运行 脚本读取文件并写入输出文件。当然,需要签入输出文件,以便使用该项目的人即使没有脚本也可以使用该资源。 (通常我将输入文件放在 Resources
目录中,并将输出写入 Sources
目录,但脚本本身并不依赖于此。)
我认为这是一个不太理想的解决方案,并期待包管理器何时内置此功能。但与此同时,这是一个可行的解决方案。
下面的例子展示了它的使用方法:
首先,这是脚本本身:
#!/usr/bin/env bash
# Read an input file, base64 encode it, then write an output swift file that will
# present it as an input stream.
#
# Usage: generate_resource_file.sh <inputfile> <outputfile> <streamName>
#
# The <streamName> is the name presented for the resulting InputStream. So, for example,
# generate_resource_file.sh Resources/logo.png Sources/Logo.swift logoInputStream
# will generate a file Sources/Logo.swift that will contain a computed variable
# that will look like the following:
# var logoInputStream: InputStream { ...blah...
#
set -e
if [ $# -ne 3 ]; then
echo "Usage: generate_resource_file.sh <inputfile> <outputfile> <streamName>"
exit -1
fi
inFile=
outFile=
streamName=
echo "Generating $outFile from $inFile"
echo "Stream name will be $streamName"
if [ ! -f "$inFile" ]; then
echo "Could not read $inFile"
exit -1
fi
echo "// This file is automatically generated by generate_resource_file.sh. DO NOT EDIT!" > "$outFile"
echo "" >> "$outFile"
echo "import Foundation" >> "$outFile"
echo "" >> "$outFile"
echo "fileprivate let encodedString = \"\"\"" >> "$outFile"
base64 -i "$inFile" >> "$outFile"
echo "\"\"\"" >> "$outFile"
echo "" >> "$outFile"
echo "var $streamName: InputStream {" >> "$outFile"
echo " get {" >> "$outFile"
echo " let decodedData = Data(base64Encoded: encodedString)!" >> "$outFile"
echo " return InputStream(data: decodedData)" >> "$outFile"
echo " }" >> "$outFile"
echo "}" >> "$outFile"
echo "Rebuilt $outFile"
然后,给定输入文件 t.dat
显示如下:
Hello World!
运行 命令 generate_resource_file.sh t.dat HelloWorld.swift helloWorldInputStream
生成以下 HelloWorld.swift
文件:
// This file is automatically generated by generate_resource_file.sh. DO NOT EDIT!
import Foundation
fileprivate let encodedString = """
SGVsbG8gV29ybGQhCgo=
"""
var helloWorldInputStream: InputStream {
get {
let decodedData = Data(base64Encoded: encodedString)!
return InputStream(data: decodedData)
}
}
使用 Swift 5.3 终于可以添加本地化资源了
包初始值设定项现在有一个 defaultLocalization
参数,可用于本地化资源。
public init(
name: String,
defaultLocalization: LocalizationTag = nil, // New defaultLocalization parameter.
pkgConfig: String? = nil,
providers: [SystemPackageProvider]? = nil,
products: [Product] = [],
dependencies: [Dependency] = [],
targets: [Target] = [],
swiftLanguageVersions: [Int]? = nil,
cLanguageStandard: CLanguageStandard? = nil,
cxxLanguageStandard: CXXLanguageStandard? = nil
)
假设您有一个 Icon.png
,您希望针对说英语和德语的人进行本地化。
图像应包含在 Resources/en.lproj/Icon.png
和 Resources/de.lproj/Icon.png
中。
在你可以像这样在你的包中引用它们之后:
let package = Package(
name: "BestPackage",
defaultLocalization: "en",
targets: [
.target(name: "BestTarget", resources: [
.process("Resources/Icon.png"),
])
]
)
请注意 LocalizationTag
是 IETF Language Tag 的包装。
来自following proposals overview的致谢和输入,请查看更多详细信息。
从 Swift 5.3 开始,感谢 SE-0271,您可以通过在 .target
声明中添加 resources
来在 swift 包管理器上添加包资源.
示例:
.target(
name: "HelloWorldProgram",
dependencies: [],
resources: [.process(Images), .process("README.md")]
)
如果你想了解更多,我在medium上写了一篇文章,讨论这个话题
重要提示:
生成的 Xcode 项目中似乎没有包含资源swift package generate-xcodeproj
但当您打开 Xcode (xed .
) 上的包文件夹,然后双击该包以解决依赖关系时,它们就会出现。
我也提供了一个很好的教程:https://medium.com/better-programming/how-to-add-resources-in-swift-package-manager-c437d44ec593