Using Swift Package Manager resources with Carthage/CocoaPods

As of Swift 5.3, Swift Package Manager now supports bundling resources with your package. This has been a blocker to adopting SwiftPM for us as we have a number of dependencies that require resources.

I’ve been happily using Carthage for years, but with some Xcode 12 issues and no XCFrameworks/Catalyst support, it seemed like a good time to give SwiftPM another shot. It turns out adding and using resources is really straight-forward, you just need to add a resource declaration to your target as as documented here.

When using that resource, you look it up via Bundle.module like so:

Bundle.module.url(forResource: "settings", withExtension: "json")

But there is one gotcha, which is that Bundle.module is only defined when building via SwiftPM. If you want to continue to support Carthage/CocoaPods and need to reference that resource internally, you can’t use Bundle.module. I couldn’t find a solution in the docs, so I lazy tweeted and luckily @neonacho (who just happens to work on Swift Package Manager) replied with the answer.

Turns out SwiftPM defines a preprocessor definition of SWIFT_PACKAGE, which later I found documented here. Now we can know at compile time whether we’re in a package and can condition our logic based on that and return the correct bundle:

static var bundle: Bundle {
    #if SWIFT_PACKAGE
        return Bundle.module
    #else
        return Bundle(for: SomeFrameworkClass.self)
    #endif
}