I would like to ship my library using Apple's Swift Package Manager. However my lib includes a .bundle file with several strings translated in different languages. Using cocoapods, I can include it using spec.resource. But in SwiftPM, I cannot do it. Any solution?
Using Swift 5.3 it's finally possible to add localized resources π
The Package initializer now has a defaultLocalization
parameter which can be used for localization resources.
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
)
Let's say you have an Icon.png
which you want to be localised for English and German speaking people.
The images should be included in Resources/en.lproj/Icon.png
& Resources/de.lproj/Icon.png
.
After you can reference them in your package like that:
let package = Package(
name: "BestPackage",
defaultLocalization: "en",
targets: [
.target(name: "BestTarget", resources: [
.process("Resources/Icon.png"),
])
]
)
Please note LocalizationTag
is a wrapper of IETF Language Tag.
Credits and input from following proposals overview, please check it for more details.
The package manager does not yet have any definition for how resources will be bundled with targets. We are aware of the need for this, but don't yet have a concrete proposal for it. I filed https://bugs.swift.org/browse/SR-2866 to ensure we have a bug tracking this.
Using Swift 5.3 it's finally possible to add localized resources π
The Package initializer now has a defaultLocalization
parameter which can be used for localization resources.
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
)
Let's say you have an Icon.png
which you want to be localised for English and German speaking people.
The images should be included in Resources/en.lproj/Icon.png
& Resources/de.lproj/Icon.png
.
After you can reference them in your package like that:
let package = Package(
name: "BestPackage",
defaultLocalization: "en",
targets: [
.target(name: "BestTarget", resources: [
.process("Resources/Icon.png"),
])
]
)
Please note LocalizationTag
is a wrapper of IETF Language Tag.
Credits and input from following proposals overview, please check it for more details.
starting on Swift 5.3, thanks to SE-0271, you can add bundle resources on swift package manager by adding resources
on your .target
declaration.
example:
.target(
name: "HelloWorldProgram",
dependencies: [],
resources: [.process(Images), .process("README.md")]
)
if you want to learn more, I have written an article on medium, discussing this topic
Due to framework bundles not being supported yet, the only way to provide bundle assets with an SPM target is through a Bundle. If you implement code in your framework to search for a particular bundle in your main project (supporting asset bundles), you can load resources from said bundle.
Example:
Access the bundled resources:
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
}
}
Utilize the Bundled resources:
let bundle = try! Bundle.myResourceBundle()
return UIColor(named: "myColor", in: bundle, compatibleWith: nil)!
You can apply the same logic for all resource files, including but not limited to storyboards, xibs, images, colors, data blobs, and files of various extensions (json, txt, etc).
Note: Sometimes this makes sense, sometimes it doesn't. Determine use to own project's discretion. It would take very specific scenarios to justify separating Storyboards/Xibs into bundled assets.
The solution I use for this is to build the data I need into a Swift object. To this end I have a shell script that will read an input file, base64 encode it, then write a Swift file that presents it as an InputStream. Then, when I want to add a data item to my Swift package, I run the script to read the file and write the output file. Of course the output file needs to be checked in so that the resource is available to those who use the project even if they do not have the script. (Typically I place my input files in a Resources
directory and write the output to the Sources
directory, but the script itself does not depend on that.)
I consider this a less than ideal solution, and am looking forward to when the package manager has this ability built in. But in the meantime, it is a workable solution.
The following example shows how it is used:
First, here is the script itself:
#!/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"
Then, given an input file t.dat
shown here:
Hello World!
Running the command generate_resource_file.sh t.dat HelloWorld.swift helloWorldInputStream
generates the following HelloWorld.swift
file:
// 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)
}
}
If you declare a Swift tools version of 5.3 or later in your package manifest, you can bundle resources with your source code as Swift packages. For example, Swift packages can contain asset catalogs, storyboards, and so on.
To add resources to a Swift package, do any of the following:
- Drag them into the Project navigator in Xcode.
- From the File menu in Xcode, choose Add Files to [packageName].
- Use Finder or the Terminal app.
When you add a resource to your Swift package, Xcode detects common resource types for Apple platforms and treats them as a resource automatically. For example, you donβt need to make changes to your package manifest for the following resources:
- Interface Builder files; for example, XIB files and storyboards
- Core Data files; for example, xcdatamodeld files
- Asset catalogs
- .lproj folders you use to provide localized resources
Access resource https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Access-a-resource-in-code
For more: https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
Important note:
Resources doesn't seem to be included in the Xcode project generated by
swift package generate-xcodeproj
But they are when you open the Package folder on Xcode (xed .
) and then double click on the package to resolve dependencies.
I'm including a nice tutorial as well: https://medium.com/better-programming/how-to-add-resources-in-swift-package-manager-c437d44ec593
© 2022 - 2024 β McMap. All rights reserved.