Long title for something that should be simple, but actually kind of hard to search online.
Scenario/prerequisites:
- A Swift Package (library/command line) for iOS or macOS
- An image file (png or jpeg)
- SwiftUI :)
Step 1: Add resources to the Swift package
If your package doesn’t have any resources yet, follow these steps:
- Create a
Resources
folder inside your library code (eg./Sources/MyLibrary/Resources
) - Drop your image(s) inside that folder
- In
Package.swift
, add the resources to your target:.target(name: "MyLibrary", dependencies: [], resources: [.process("Resources")]),
Step 2: Use the image in SwiftUI
It seems that, unfortunately, as of June 1 2021, with Xcode 12.5, SwiftUI still does not support loading image resources from a Swift Package bundle. This seems to work only with an Assets catalog in a normal Xcode project. Thus, the following examples won’t work:
var body: some View {
Image("image").resizable() // <-- this won't work ❌
Image("image.png").resizable() // <-- this won't work ❌
Image("Resources/image.png").resizable() // <-- this won't work ❌
Image("image", bundle: Bundle.module).resizable() // <-- this won't work ❌
Image("image.png", bundle: Bundle.module).resizable() // <-- this won't work ❌
}
Instead, we have to rely on UIImage
for iOS and NSImage
for macOS. Using Bundle.module
we can retrieve the path of the image asset, and then load it with UIImage
or NSImage
as needed.
// UIKit
if let path = Bundle.module.path(forResource: name, ofType: type),
let image = UIImage(contentsOfFile: path) {
...
}
// AppKit
if let path = Bundle.module.path(forResource: name, ofType: type),
let image = NSImage(contentsOfFile: path) {
...
}
Step 3: Add an extension for Image
This extension for Image
handles both AppKit
and UIKit
frameworks well, and also works well with SwiftUI previews 🎉:
extension Image {
init(packageResource name: String, ofType type: String) {
#if canImport(UIKit)
guard let path = Bundle.module.path(forResource: name, ofType: type),
let image = UIImage(contentsOfFile: path) else {
self.init(name)
return
}
self.init(uiImage: image)
#elseif canImport(AppKit)
guard let path = Bundle.module.path(forResource: name, ofType: type),
let image = NSImage(contentsOfFile: path) else {
self.init(name)
return
}
self.init(nsImage: image)
#else
self.init(name)
#endif
}
}
It will return an empty image if the resource cannot be found or if neither UIKit
or AppKit
can be imported.
Step 4: Use the extension
We can then proceed to use this extension as follows:
var body: some View {
Image(packageResource: "image", ofType: "png").resizable() // Works well ✅
}
Hope this helps!
This article was written as an issue on my Blog repository on GitHub (see Issue #28)
First draft: 2021-06-02
Published on: 2021-06-02
Last update: 2021-06-21