如何在Swift Package Manager库中包含资源/资源?

时间:2016-10-02 08:03:15

标签: swift swift-package-manager

我想使用Apple的Swift Package Manager运送我的库。但是,我的lib包含一个.bundle文件,其中包含多个以不同语言翻译的字符串。 使用cocoapods,我可以使用spec.resource包含它。但是在SwiftPM中,我不能这样做。任何解决方案?

7 个答案:

答案 0 :(得分:22)

包管理器尚未定义如何将资源与目标捆绑在一起。我们意识到需要这个,但还没有具体的建议。我提交了https://bugs.swift.org/browse/SR-2866以确保我们有跟踪此错误。

答案 1 :(得分:12)

使用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.pngResources/de.lproj/Icon.png中。

像这样在您的包中引用它们之后:

let package = Package(
    name: "BestPackage",
    defaultLocalization: "en",
    targets: [
        .target(name: "BestTarget", resources: [
            .process("Resources/Icon.png"),
        ])
    ]
)

请注意,LocalizationTagIETF Language Tag的包装。

来自following proposals overview的信用和输入,请检查它以获取更多详细信息。

答案 2 :(得分:4)

从Swift 5.3开始,多亏了SE-0271,您可以通过在resources声明中添加.target在swift软件包管理器上添加捆绑资源。

示例:

.target(
   name: "HelloWorldProgram",
   dependencies: [], 
   resources: [.process(Images), .process("README.md")]
)

如果您想了解更多信息,我在medium上写了一篇文章,讨论了这个主题

答案 3 :(得分:3)

我使用的解决方案是将所需的数据构建到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=$1
outFile=$2
streamName=$3

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)
    }
}

答案 4 :(得分:2)

由于尚不支持框架捆绑包,因此,向捆绑包资产提供SPM目标的唯一方法是通过捆绑包。如果在框架中实现代码以在主项目中搜索特定捆绑包(支持资产捆绑包),则可以从所述捆绑包中加载资源。

示例:

访问捆绑的资源:

extension Bundle {
    static func myResourceBundle() throws -> Bundle {
        let bundles = Bundle.allBundles
        let bundlePaths = bundles.compactMap { $0.resourceURL?.appendingPathComponent("MyAssetBundle", isDirectory: false).appendingPathExtension("bundle") }

        guard let bundle = bundlePaths.compactMap({ Bundle(url: $0) }).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,图像,颜色,数据Blob和各种扩展名(json,txt等)的文件。

注意:有时候这很有意义,有时候却没有。自行决定使用项目。需要采取非常具体的方案来证明将Storyboard / Xib分离为捆绑资产是合理的。

答案 5 :(得分:0)

您还可以动态加载单个资源。

假设您的包测试需要一些模拟JSON数据。您会将模拟JSON文件放置在Tests/JSONMocks目录中。然后在Package.swift中:

targets: [
    .testTarget(
        name: "MyTargetTests",
        dependencies: ["MyTarget"],
        path: "Tests"
    ),
]

并动态加载它们:

static func dataForJSONFileNamed(string: String) -> Data {
    // find mock JSON files if using Swift Package Manager:
    let currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
    let fileURL = currentDirectoryURL
        .appendingPathComponent("Tests", isDirectory: true)
        .appendingPathComponent("JSONMocks", isDirectory: true)
        .appendingPathComponent(string)
        .appendingPathExtension("json")
    let jsonFileData = try! Data(contentsOf: fileURL)
    return jsonFileData
}

答案 6 :(得分:-1)

重要说明:

资源似乎未包含在由...生成的Xcode项目中

swift package generate-xcodeproj

但是当您在Xcode(xed .)上打开Package文件夹,然后双击该软件包以解决依赖关系时。

我还将提供一个不错的教程:https://medium.com/better-programming/how-to-add-resources-in-swift-package-manager-c437d44ec593