In a .xcodeproj I am able to enrich SwiftUI Previews with resources that are stripped during an archive of an app when placed in a “Preview Assets” asset catalog. See Development Assets in Xcode to enrich SwiftUI Previews for more info.
In a Swift Package, I am not aware of an option that resources, specified for a target in a package manifest, can be made conditional based on configuration.
Adding resources to Swift Package to enrich SwiftUI Previews have a negative impact on an app as it bloats its IPA size unnecessarily.
Do I overlook something or are there plans for Swift Packages to support development assets?
@NeoNacho If Package Manager Conditional Target Dependencies would support configuration conditionals then it would be an option to create a dedicated helper target containing SwiftUI previews and resources for DEBUG configuration. But I guess there are no plans to introduce support for configuration conditionals?
I'm doing exactly the same thing - environment variable triggers that are used to manipulate whats in, and out, of Package.swift. Not for this specific use case (preview assets), but the same pattern anyway.
A question to you, @Joseph_Heck, out of curiosity: you are doing this for an internal Swift package (to modularize an iOS app) or for an open-source reusable Swift package? Using environment variable seems fine for an internal Swift package but not for an SDK-style Swift package
I'm using it within an open-source package, specifically to expose a CLI exec that I use when developing the package - generating images for docs in one case, or enabling a benchmarking executable in another. I mostly came at it from the area of not wanting these additional targets "clogging up" a developer's experience when they go to "just use" the package.
In the SE-0273 Acceptance thread, @NeoNacho stated that…
@NeoNacho: Was this effort tracked in a new proposal or other visible location? If not, can you give us an update regarding the current direction? Apologies if there's a discussion that I missed... I've spent a fair bit of time searching to no avail, and it's been quite some time since that quote. Thanks!
Sure, but unless there's something I'm missing, you can only create a TargetDependencyCondition for a collection of platforms, not build configurations (the scope of this thread).
@doozMen How does adding a preview target work? Can you preview using classes/resources from this target and not have the stuff in that target linked in the final build?
I hit this issue today. It would be great if SPM could have better support for development assets.
I ended up resorting to a specific package to contain all the dev assets and exporting those using public accessors.
As the last step of the build process, I delete the associated resource bundle from the build output (yeah, a bit hacky).
Those assets shouldn't be referenced from prod code but to protect myself from unexpected crashes in prod from referencing them, I wrapped the assets public accessors with #if DEBUG. This cascades down the code and previews using those assets which is a little inconvenient but I feel a little safer that way.
It's a bit annoying though that debug builds might work but release builds might not, detecting this issue later in the CI pipeline. But that should be better than potential crashes in prod.
Just for the record, I ended up refining the above approach. This is for a macOS app, not sure if this will work for other platforms. This was it:
A single package named SharedPreviewAssets that contains all preview assets
The package exports assets using public constants as described in the docs or this WWDC video
Example:
public enum SharedPreviewAssets {
public static let background = Image(.background)
// These assets might not be found in prod so even if these resources shouldn’t be accessed by production code, we make sure we don’t crash just in case
// The generated SwiftUI ImageResource Image extension won’t crash but the extension of NSImage will crash so we do it this way.
public static let someNSImage = Bundle.module.image(forResource: “image”) ?? placeholderNsImage
/// Placeholder image to return when the asset was not found
private static let placeholderNsImage = NSImage(size: NSSize(width: 1, height: 1))
}
This package must be included in the Package.swift of clients as a dependency. Then it can be used this way:
import SharedPreviewAssets
struct MyView: View {
var body: some View { ... }
}
#Preview {
MyView()
.background(SharedPreviewAssets.background)
}
A Run Script build phase at the end will delete the preview resources in the build output of the app. Note I didn’t delete the whole bundle but only its resources content. This way it shouldn’t crash. The bundle is still there at the final build but at least it is empty.
#!/bin/bash
echo "Cleaning up preview assets..."
PREVIEW_ASSETS_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/SharedPreviewAssets_SharedPreviewAssets.bundle/Contents/Resources"
if [ ! -d "$PREVIEW_ASSETS_PATH" ]; then
echo "Preview assets directory not found: $PREVIEW_ASSETS_PATH"
exit 0
fi
# Check if the directory has any files before deleting
if [ -z "$(ls -A "$PREVIEW_ASSETS_PATH")" ]; then
echo "No preview assets found to be deleted"
exit 0
fi
# Proceed to delete if files are present
rm -rf "${PREVIEW_ASSETS_PATH}"/*
echo "Preview assets deleted successfully"
I didn’t end up using #if DEBUG. This way the developer experience feels a bit better and it should still be safe. This also prevents having build issues on release if you forget wrapping all previews with the compiler directive. If you do what you shouldn’t do, i.e, referencing preview assets from prod code, it will not load the assets but at least it won’t crash.
Again, not ideal and a bit hacky but seems promising in the meantime. It would be great if proper developer assets support is added to SPM.