Introduction
Swift Package Manager supports resource bundles, but accessing them at runtime requires using Bundle.module (generated by SPM) instead of Bundle.main. When the bundle path is resolved incorrectly, images, localization strings, and data files embedded in a Swift package return nil or fail to load. This is especially confusing because resources work in the package's test target but fail when the package is used as a dependency.
Symptoms
Bundle.module.url(forResource:)returns nil- Images in SPM package not found by
UIImage(named:in:compatibleWith:) - Localized strings from package not loading
- Resources work in package tests but not in consuming app
Bundle.main.path(forResource:)does not find package resources
Debug bundle:
``swift
// In your package code
print("Bundle.module: \(Bundle.module)")
print("Bundle path: \(Bundle.module.bundlePath)")
print("Bundle resources: \(Bundle.module.paths(forResourcesOfType: nil, inDirectory: nil))")
Common Causes
- Using
Bundle.maininstead ofBundle.modulein package code - Resources not declared in Package.swift
resourcesarray - Resource file in wrong directory relative to target
- Bundle module not generated for the target
- Resource copy rule does not match expected file type
Step-by-Step Fix
- 1.Declare resources correctly in Package.swift:
- 2.```swift
- 3.// swift-tools-version: 5.7
- 4.import PackageDescription
let package = Package( name: "MyUIComponents", platforms: [.iOS(.v15)], products: [ .library(name: "MyUIComponents", targets: ["MyUIComponents"]), ], targets: [ .target( name: "MyUIComponents", resources: [ // Copy resources as-is .process("Resources"),
// Or copy specific files .copy("Assets/logo.png"), .copy("Assets/colors.xcassets"),
// Or process for localization .process("Localizations"), ] ), .testTarget( name: "MyUIComponentsTests", resources: [.process("TestResources")] ), ] ) ```
- 1.**Use Bundle.module correctly in package code":
- 2.```swift
- 3.import SwiftUI
// WRONG - Bundle.main does not contain package resources let image = UIImage(named: "logo", in: Bundle.main, compatibleWith: nil)
// CORRECT - Bundle.module is generated by SPM for the target let image = UIImage(named: "logo", in: Bundle.module, compatibleWith: nil)
// Loading data files func loadJSON(filename: String) -> Data? { guard let url = Bundle.module.url(forResource: filename, withExtension: "json") else { return nil } return try? Data(contentsOf: url) }
// Loading localized strings func localizedString(key: String) -> String { NSLocalizedString(key, bundle: Bundle.module, comment: "") } ```
- 1.**Access resources from SwiftUI views in a package":
- 2.```swift
- 3.import SwiftUI
// Image from package resource bundle struct PackageLogo: View { var body: some View { Image("logo", bundle: Bundle.module) .resizable() .frame(width: 100, height: 100) } }
// Accessing color assets from xcassets in package struct ThemedView: View { var body: some View { Rectangle() .fill(Color("PrimaryColor", bundle: Bundle.module)) } }
// Loading a text file resource struct HelpView: View { private let helpText: String
init() { if let url = Bundle.module.url(forResource: "help", withExtension: "md"), let content = try? String(contentsOf: url) { helpText = content } else { helpText = "Help content not available." } }
var body: some View { ScrollView { Text(helpText) } } } ```
- 1.**Debug resource loading issues":
- 2.```swift
- 3.// List all resources in the bundle
- 4.func debugBundleResources() {
- 5.let bundle = Bundle.module
- 6.print("Bundle: \(bundle.bundlePath)")
- 7.print("Bundle identifier: \(bundle.bundleIdentifier ?? "nil")")
// List all resource paths let resourcePaths = bundle.paths(forResourcesOfType: nil, inDirectory: nil) print("Resource paths:") for path in resourcePaths { print(" \(path)") }
// Check for specific resource if let url = bundle.url(forResource: "logo", withExtension: "png") { print("Found logo at: \(url)") } else { print("Logo not found!") print("Available PNG files:") let pngPaths = bundle.paths(forResourcesOfType: "png", inDirectory: nil) print(pngPaths) } } ```
Prevention
- Always use
Bundle.modulein SPM package code, neverBundle.main - Verify resource directory structure matches Package.swift declarations
- Add resource loading tests to the package test target
- Use
.process()for files that need localization processing - Use
.copy()for files that should be copied as-is - Check
Bundle.module.paths(forResourcesOfType:)during development