web-dev-qa-db-ja.com

Swift Package Managerライブラリにアセット/リソースを含める方法は?

AppleのSwift Package Managerを使用してライブラリを出荷したいのですが、私のライブラリには、複数の文字列が異なる言語に翻訳された.bundleファイルが含まれています。cocoapodsを使用すると、spec.resourceを使用してそれを含めることができます。しかし、SwiftPMではできません。

39
danielemm

パッケージマネージャーには、リソースをターゲットにバンドルする方法の定義がまだありません。これの必要性は認識していますが、具体的な提案はまだありません。私は https://bugs.Swift.org/browse/SR-2866 を提出して、これを追跡するバグがあることを確認しました。

41
Daniel Dunbar

フレームワークバンドルがサポートされていないためまだであり、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)!

ストーリーボード、xibs、画像、色、データblob、さまざまな拡張子のファイル(json、txtなど)を含むがこれらに限定されないすべてのリソースファイルに同じロジックを適用できます。

注:これが意味のある場合もあれば、意味のない場合もあります。プロジェクトの裁量権を使用するかどうかを決定します。 Storyboards/Xibsをバンドルされたアセットに分離することを正当化するには、非常に具体的なシナリオが必要です。

3
TheCodingArt

これに使用する解決策は、必要なデータをSwiftオブジェクトに構築することです。このために、入力ファイルを読み取り、base64でエンコードしてから=を書き込むシェルスクリプトを用意しています。 SwiftファイルをInputStreamとして表示するファイルです。次に、データアイテムをmy 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)
    }
}
2