How to include assets / resources in a Swift Package Manager library?
Using Swift 5.3 it's finally possible to add localized resources /p>
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.
Use resources in unit tests with Swift Package Manager
Swift 5.3
See Apple Documentation: "Bundling Resources with a Swift Package"
Swift 5.3 includes Package Manager Resources SE-0271 evolution proposal with "Status: Implemented (Swift 5.3)".
Resources aren't always intended for use by clients of the package; one use of resources might include test fixtures that are only needed by unit tests. Such resources would not be incorporated into clients of the package along with the library code, but would only be used while running the package's tests.
- Add a new
resources
parameter intarget
andtestTarget
APIs to allow declaring resource files explicitly.SwiftPM uses file system conventions for determining the set of source files that belongs to each target in a package: specifically, a target's source files are those that are located underneath the designated "target directory" for the target. By default this is a directory that has the same name as the target and is located in "Sources" (for a regular target) or "Tests" (for a test target), but this location can be customized in the package manifest.
// Get path to DefaultSettings.plist file.
let path = Bundle.module.path(forResource: "DefaultSettings", ofType: "plist")
// Load an image that can be in an asset archive in a bundle.
let image = UIImage(named: "MyIcon", in: Bundle.module, compatibleWith: UITraitCollection(userInterfaceStyle: .dark))
// Find a vertex function in a compiled Metal shader library.
let shader = try mtlDevice.makeDefaultLibrary(bundle: Bundle.module).makeFunction(name: "vertexShader")
// Load a texture.
let texture = MTKTextureLoader(device: mtlDevice).newTexture(name: "Grass", scaleFactor: 1.0, bundle: Bundle.module, options: options)
Example
// swift-tools-version:5.3
import PackageDescription
targets: [
.target(
name: "Example",
dependencies: [],
resources: [
// Apply platform-specific rules.
// For example, images might be optimized per specific platform rule.
// If path is a directory, the rule is applied recursively.
// By default, a file will be copied if no rule applies.
// Process file in Sources/Example/Resources/*
.process("Resources"),
]),
.testTarget(
name: "ExampleTests",
dependencies: [Example],
resources: [
// Copy Tests/ExampleTests/Resources directories as-is.
// Use to retain directory structure.
// Will be at top level in bundle.
.copy("Resources"),
]),
Reported Issues & Possible Workarounds
- Swift 5.3 SPM Resources in tests uses wrong bundle path?
- Swift Package Manager - Resources in test targets
Xcode
Bundle.module
is generated by SwiftPM (see Build/BuildPlan.swift SwiftTargetBuildDescription generateResourceAccessor()) and thus not present in Foundation.Bundle when built by Xcode.
A comparable approach in Xcode would be to manually add a Resources
reference folder to the Xcode project, add an Xcode build phase copy
to put the Resource
into some *.bundle
directory, and add a some custom #ifdef XCODE_BUILD
compiler directive for the Xcode build to work with the resources.
#if XCODE_BUILD
extension Foundation.Bundle {
/// Returns resource bundle as a `Bundle`.
/// Requires Xcode copy phase to locate files into `ExecutableName.bundle`;
/// or `ExecutableNameTests.bundle` for test resources
static var module: Bundle = {
var thisModuleName = "CLIQuickstartLib"
var url = Bundle.main.bundleURL
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
url = bundle.bundleURL.deletingLastPathComponent()
thisModuleName = thisModuleName.appending("Tests")
}
url = url.appendingPathComponent("\(thisModuleName).bundle")
guard let bundle = Bundle(url: url) else {
fatalError("Foundation.Bundle.module could not load resource bundle: \(url.path)")
}
return bundle
}()
/// Directory containing resource bundle
static var moduleDir: URL = {
var url = Bundle.main.bundleURL
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
// remove 'ExecutableNameTests.xctest' path component
url = bundle.bundleURL.deletingLastPathComponent()
}
return url
}()
}
#endif
Load fonts from a Swift Package
✅ It is supported now
From Swift 5.3, you can add any resources
to the target, including images, assets, fonts, zip, etc. Using the directory name will include all subfiles of the directory:
.target(
name: "ABUIKit",
dependencies: [],
resources: [.process("Resources") // <- this will add Resource directory to the target
]
),
Note that you should put the Resources
folder under sources/packageName
to make it recognizable.
⚠️ Also, Don't forget to register them!
You need to register fonts to make it work. So you can use frameworks like FontBlaster.
NOTE: You can always write your own code, but I prefer to use an existing framework and contribute my extra needs to them
So you call this somewhere early in the module's code (like some init method):
FontBlaster.blast(bundle: .module)
Then you can use the font inside or even outside of the module!
There is no need to name the font manually!
It will register all included fonts automatically. You can double-check the loaded fonts right after calling the blast method:
FontBlaster.loadedFonts.forEach { print("quot;, $0) }
Related Topics
Constant Speed Orbit Around Point with Sknode
Swift, Auto Resize Custom Table View Cells
Transform a Swift Sequence in to Adjacent Pairs
Expandable Sections Uitableview Indexpath Swift
Arkit - Viewport Size VS Real Screen Resolution
Swift Error Handling for Methods That Do Not Throw
Mutable Binding in Swiftui Live Preview
Change Uibarbuttonitem from Uisearchbar
Implementing a Function with a Default Parameter Defined in a Protocol
Sprite Kit Physicsbody.Resting Behavior
Nsurl Found Nil While Unwraping an Optional Value
How to Cast Any to an Optional
What's the Equivalent to String.Localizedstringwithformat(_:_:) for Swiftui's Localizedstringkey