Using resources in Swift Packages
If you scour the internet for details on how to access resource files in your iOS app, you’ll find tons of references and usages of Bundle.main.path
and Bundle.main.url
. That’s all great — if your resources are stored in your main bundle. But if — for example — you build features within Swift Packages, ideally you’ll want to package all of the resources associated with that feature within the package itself. Here’s how…
Declaring your resource files
First off, the package needs to know which files are resources (as opposed to source files). Whereas your source files will be compiled, resource files are merely copied (optionally with some processing) to your final app package. You’ll need to declare which of your files are resource files in your package’s Package.swift
file:
// Make sure you are using tools 5.3 or higher! - set at the top of the file
let package = Package(
// ...
targets: [
.target(
name: "MyPackage",
dependencies: [
// ...
],
resources: [
// Paths are defined relative to your sources root!
.copy("Resources/example.json"),
.process("Resources/image.jpg"),
// You can also just copy an entire folder if needed
.copy("Resources/json/")
]
),
// ...
]
)
In the example above the target definition contains a resources
array with a list of files (or folders) that should be copied as resources. Of course you can define these both for targets or test targets, as needed. Folders will be copied recursively.
Note that there are two ways of copying resources:
.copy()
— unsurprisingly this will just copy the file.process()
— the compiler may choose to do some pre-processing, such as optimizing images, etc. before the files are copied. Files that do not require processing will just be copied unmodified, so it’s fairly safe to just use.process()
unless explicitly required.
Important! You’ll need to define resource paths relative to your root source path. Typically (unless you configured otherwise) the root path will be something like /Sources/MyPackage/
so if you place a resource file in /Sources/MyPackage/Resources/image.jpg
you’ll need to use the pathResources/image.jpg
within the .copy()
or .process()
functions.
Using your resources
Now that we have our resources nicely embedded in our package we still need to figure out how to actually use them. That’s where Bundle.module
comes in.
When you add some resources to your package the compiler will automatically generate Bundle.module
for you — which can be used to access your resources safely.
But importantly this is kinda flimsy at first (thanks Xcode!). If you get weird errors with Bundle.module
not being found you should try some of these steps:
- Ensure you’ve waited for all your packages to load
- Double check that your resource path is correctly set (including what it is relative to — see above). Xcode won’t warn you for incorrect paths!
- If still not working, clean build your module (or app)
- If still not working, restart Xcode, clear derived data, all the usual shenanigans
If Bundle.module
is ready, then you can access your resource files like so:
guard let resourceUrl= Bundle.module.url(
forResource: "example",
withExtension: "json"
) else {
// Probably log an error or throw here...
return
}
let resourceData = try Data(contentsOf: resourceUrl)
Now just use resourceData
as you wish!