Swift Package Resources
Introduction
When building Swift packages, you often need to include more than just code. Images, JSON files, audio clips, and other assets are crucial parts of many libraries and applications. Before Swift 5.3, managing these resources within packages was challenging, requiring workarounds or external dependencies.
Swift Package Resources solves this problem by providing first-class support for bundled resources within packages. This feature allows you to include, access, and manage non-code assets directly in your Swift packages, making them more self-contained and easier to work with.
In this tutorial, you'll learn:
- How to declare resources in your Swift package
- Ways to organize resources in your package structure
- How to access resources at runtime
- Best practices for working with package resources
Understanding Resource Basics
What Are Package Resources?
Package resources are non-code files bundled with your Swift package that can be:
- Images and media files
- Configuration files (JSON, YAML, etc.)
- Data files
- Interface definition files (XIB, Storyboard)
- Localization files
- And more!
Why Use Package Resources?
Including resources in your package offers several benefits:
- Self-contained packages: Everything your package needs is in one place
- Simplified distribution: No need to instruct users to add resources separately
- Version control: Resources are versioned alongside your code
- Better organization: Clearer structure for your package components
Adding Resources to Your Package
Step 1: Define Resources in Package.swift
To include resources in your package, you need to declare them in your package manifest file (Package.swift
). There are two main approaches:
1. Implicit Resource Inclusion
Swift Package Manager automatically includes resources inside a directory named Resources
at the root level of your target:
// Package.swift
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
targets: [
.target(
name: "MyPackage",
dependencies: []),
]
)
With this configuration, all files in Sources/MyPackage/Resources/
are automatically included as resources.
2. Explicit Resource Declaration
You can also explicitly declare which resources to include using the resources
parameter:
// Package.swift
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
targets: [
.target(
name: "MyPackage",
dependencies: [],
resources: [
.process("Assets/Images"),
.copy("Data/config.json"),
.process("Assets/Sounds")
]),
]
)
Step 2: Resource Processing Options
When declaring resources explicitly, you have two processing options:
.process
Directive
The .process
directive tells SwiftPM to process certain file types specially:
- Asset catalogs (
.xcassets
) get compiled - Storyboards and XIB files get compiled
- Strings files get processed for localization
.target(
name: "MyPackage",
dependencies: [],
resources: [
.process("Images.xcassets"),
.process("Views")
]
)
.copy
Directive
The .copy
directive simply copies the files as-is without any processing:
.target(
name: "MyPackage",
dependencies: [],
resources: [
.copy("Data/config.json"),
.copy("README.txt")
]
)
Organizing Resources in Your Package
A well-organized package structure makes resources easier to manage:
MyPackage/
├── Package.swift
├── Sources/
│ └── MyPackage/
│ ├── Code files (.swift)
│ ├── Resources/ // Implicitly included resources
│ │ ├── Images/
│ │ └── Data/
│ └── Assets/ // Must be explicitly declared
│ ├── Images.xcassets/
│ └── Sounds/
└── Tests/
└── MyPackageTests/
Accessing Resources at Runtime
Once your resources are included in your package, you need to access them in your code. Swift provides several methods to do this.
Using Bundle Module
For Swift 5.3 and later, a new module
property on Bundle
makes accessing package resources straightforward:
import Foundation
// Access an image
if let imageURL = Bundle.module.url(forResource: "logo", withExtension: "png") {
// Use the imageURL
print("Image URL: \(imageURL)")
}
// Access a data file
if let dataURL = Bundle.module.url(forResource: "config", withExtension: "json") {
do {
let data = try Data(contentsOf: dataURL)
print("Config data loaded: \(data.count) bytes")
// Process your data
} catch {
print("Error loading data: \(error)")
}
}
UIKit and SwiftUI Integration
UIKit Example
import UIKit
func loadPackageImage() -> UIImage? {
guard let imageURL = Bundle.module.url(forResource: "profilePicture", withExtension: "jpg") else {
print("Could not find image in package resources")
return nil
}
return UIImage(contentsOfFile: imageURL.path)
}
// Usage
let myImage = loadPackageImage()
SwiftUI Example
import SwiftUI
struct ResourceImageView: View {
var body: some View {
// For bundled images
if let uiImage = UIImage(named: "myIcon", in: .module, with: nil) {
Image(uiImage: uiImage)
.resizable()
.frame(width: 100, height: 100)
} else {
Text("Image not found")
}
}
}
Real-World Application: Building a Theme Package
Let's create a practical example - a theming package that includes colors, fonts, and images:
1. Define the Package Structure
ThemeKit/
├── Package.swift
└── Sources/
└── ThemeKit/
├── Theme.swift
├── Resources/
│ ├── Fonts/
│ │ ├── CustomFont-Regular.ttf
│ │ └── CustomFont-Bold.ttf
│ └── Data/
│ └── theme_config.json
└── Assets/
└── Images.xcassets/
├── logo.imageset/
├── background.imageset/
└── icons/
2. Configure Package.swift
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "ThemeKit",
platforms: [.iOS(.v13), .macOS(.v10_15)],
products: [
.library(name: "ThemeKit", targets: ["ThemeKit"]),
],
targets: [
.target(
name: "ThemeKit",
resources: [
.process("Resources/Fonts"),
.process("Assets/Images.xcassets"),
.copy("Resources/Data/theme_config.json")
]
),
.testTarget(
name: "ThemeKitTests",
dependencies: ["ThemeKit"]
),
]
)
3. Create the Theme Implementation
import Foundation
import SwiftUI
public struct ThemeKit {
// MARK: - Theme Configuration
/// Load theme configuration from bundled JSON
public static func loadThemeConfiguration() -> [String: Any]? {
guard let configURL = Bundle.module.url(forResource: "theme_config", withExtension: "json") else {
print("Could not find theme configuration")
return nil
}
do {
let data = try Data(contentsOf: configURL)
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
return json
} catch {
print("Error loading theme configuration: \(error)")
return nil
}
}
// MARK: - Fonts
/// Register custom fonts from the bundle
public static func registerFonts() {
guard let fontURLs = Bundle.module.urls(forResourcesWithExtension: "ttf", subdirectory: "Fonts") else {
print("No fonts found in bundle")
return
}
for url in fontURLs {
if let fontDataProvider = CGDataProvider(url: url as CFURL),
let font = CGFont(fontDataProvider) {
var error: Unmanaged<CFError>?
if !CTFontManagerRegisterGraphicsFont(font, &error) {
print("Error registering font: \(url.lastPathComponent)")
}
}
}
}
// MARK: - Images
/// Get a named image from the package
public static func image(named name: String) -> UIImage? {
return UIImage(named: name, in: .module, compatibleWith: nil)
}
}
4. Using the Theme Package in a Project
import SwiftUI
import ThemeKit
struct ContentView: View {
init() {
// Register custom fonts when the view initializes
ThemeKit.registerFonts()
}
var body: some View {
VStack(spacing: 20) {
// Use an image from the package
if let logoImage = ThemeKit.image(named: "logo") {
Image(uiImage: logoImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
}
Text("Themed Application")
.font(.custom("CustomFont-Bold", size: 24))
Text("This text uses a custom font from our ThemeKit package")
.font(.custom("CustomFont-Regular", size: 16))
.multilineTextAlignment(.center)
.padding()
}
.padding()
.onAppear {
// Load theme configuration on appear
if let config = ThemeKit.loadThemeConfiguration() {
print("Theme config loaded: \(config)")
}
}
}
}
Common Issues and Troubleshooting
Resources Not Found at Runtime
If your resources aren't found at runtime, check:
- The resource names and locations match exactly what you're trying to access
- Resources are correctly declared in
Package.swift
- You're using the right bundle (
.module
) to access resources
Bundle.module Not Found
If your code can't find Bundle.module
, ensure:
- You're using Swift 5.3 or later
- Your package's deployment target is compatible
- You've properly declared resources in
Package.swift
Summary
Swift Package Resources provide a powerful way to include and manage non-code assets in your Swift packages. By properly declaring and organizing these resources, you can create more self-contained, reusable components that don't rely on external assets.
In this tutorial, you've learned:
- How to declare resources in your Swift package manifest
- Different methods for organizing resources in your package
- Ways to access resources at runtime using
Bundle.module
- A real-world example of creating a theme package with various resource types
With this knowledge, you can create richer Swift packages that include everything needed for their functionality, making them more portable and easier to use in different projects.
Additional Resources
- Swift Package Manager Documentation
- WWDC 2020 Session on Resources in Swift Packages
- Swift.org Forums - Swift Package Manager category
Exercises
- Create a simple Swift package that includes an image resource and write code to display it.
- Build a "configuration" package that includes JSON files with different settings and API to access them.
- Experiment with localized resources by creating a package with strings files in multiple languages.
- Create a package with a custom font and use it in a SwiftUI view.
- Build an animation package that includes both image sequences and code to display them.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)