Using resources in Swift Packages

Aron Budinszky
hoursofoperation
Published in
3 min readDec 29, 2023

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…

Well, wind is a resource — and this photo is nicer than a computer-related stock photo
Well, wind is a resource — and this photo is nicer than a computer-related stock photo. Photo by Jason Blackeye on Unsplash

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!

--

--