Context
Consider a simple Swift Package with a MyLibrary
and MyExecutable
target.
The aim is for MyExecutable
to use MyLibrary
(which has an image).
The MyLibrary
target has 2 files, a Resources/Assets.xcassets
catalog (with 1 image named “Test”) and an Images.swift
file like so:
private extension NSImage {
static let test = Bundle.module.image(forResource: "Test")
}
public struct Images {
public static func printTestExists() {
print("Exists: (NSImage.test != nil)")
}
}
Please note in particular the use of Bundle.module
here. The MyExecutable
consists of merely a main.swift
file that imports MyLibrary
and runs:
Images.printTestExists()
Issue
When running the MyExecutable
scheme in Xcode, it works as expected printing:
Exists: true
However, when running the executable from the command line:
swift run -c release MyExecutable
it crashes with the error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSBundle imageForResource:]: unrecognized selector sent to instance
Question
Why do assets seem not to work from the command-line? I’m only executing the swift run
command so the executable file should have access to the library bundle, right?
Package.swift
The assets are processed as a resource as you would expect
let package = Package(
name: "MyLibrary",
platforms: [.macOS(.v13)],
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"]),
.executable(name: "MyExecutable", targets: ["MyExecutable"])
],
targets: [
.target(name: "MyLibrary", resources: [.process("Resources")] ),
.executableTarget(name: "MyExecutable", dependencies: ["MyLibrary"])
]
)
Aside
Using the following SwiftUI code from the command-line would similarly not show an image but works when run from Xcode:
Image("Test", bundle: .module)
Attachments
References
I found a similar issue on Swift forums
To only half answer my own question, I believe the issue is just that swift run
does not support assets. Hopefully, a future answer shows us how to make it work.
I managed to workaround the issue using the xcodebuild
command instead
# Set this to what you want (this is inside the SwiftPM `.build` directory)
derivedDataPath=".build/xcode-derived-data"
xcodebuild build
-configuration release
-scheme MyExecutable
-sdk macosx
-destination 'platform=macOS'
-derivedDataPath "$derivedDataPath"
$derivedDataPath/Build/Products/release/MyExecutable
To see other destinations run
xcodebuild -showdestinations -scheme MyExecutable
It does not require making an .xcodeproj
file which is nice, so we can stay in the realm of SwiftPM. Still, swift run
would be preferable, but I hope this helps in the interim.