Reputation: 391
I'm trying to add support for SPM in one of our projects that has storyboards.
Currently we grab it UIStoryboard(name: String, bundle: String?)
but this doesn't seem to work with SPM, as there isn't really a bundle. Even printing all the bundles doesn't show the bundle of our package.
Any way we can support storyboards or are SPM's meant to be just files?
Attempts:
UIStoryboard(name: "GiftCards", bundle: Bundle(for: self))
UIStoryboard(name: "GiftCards", bundle: Bundle(for: type(of: self)))
UIStoryboard(name: "GiftCards", bundle: Bundle(identifier: "com.x.x"))
Upvotes: 15
Views: 6005
Reputation: 3367
@mm2001's answer is mostly correct and still required as of Xcode 16 for referencing storyboards from SPM packages inside a parent storyboard. @derpoliuk's answer only applies if you are instantiating the storyboard from the package itself.
However, the bundle name for configuring the reference inside the parent storyboard does not seem to be correct in @mm2001's answer. The correct bundle identifier format FOR XCODE 16 onward is as follows: lowercasepackage.PascalCasePackage.resources
. For BadgeKit
, it should be badgekit.BadgeKit.resources
. The old bundle identifier format is still valid and working for Xcode 15.4.
In short:
resources
option in the library's Package.swift
. ....
targets: [
.target(name: "BadgeKit",
...
resources: [
.copy("Views/BadgeKit.storyboard"),
],
...
),
]
....
lowercasepackage.PascalCasePackage.resources
. Ex. For BadgeKit
, it should be badgekit.BadgeKit.resources
.@UIApplicationMain final class AppDelegate: UIResponder {
[…]
override init() {
super.init()
// WORKAROUND: Storyboards do not trigger the loading of resource bundles in Swift Packages.
let bundleNames = ["BadgeKit_BadgeKit"]
bundleNames.forEach { (bundleName) in
guard
let bundleURL = Bundle.main.url(forResource: bundleName, withExtension: "bundle"),
let bundle = Bundle(url: bundleURL) else {
preconditionFailure()
}
bundle.load()
}
[…]
}
[…]
}
Upvotes: 2
Reputation: 6969
Update: XCode 14.2 (and perhaps earlier) does not require any of the steps below. Storyboards in the packages are automagically loaded as needed. Thanks to the answer by @derpoliuk for pointing this out and providing a GitHub example.
As of Xcode 12.0 this sort of works, but needs a few extra steps to complete it.
BadgeKit
BadgeKit
with Package.swift
header // swift-tools-version:5.3
or higherBadgeKit.storyboard
Storyboard Reference property panel with Storyboard value BadgeKit
and Bundle identifier BadgeKit-BadgeKit-resources
.
Xcode automatically generates a bundle (and its identifier) for you to hold resources found in an SPM package using the following format: [package name]-[package target name]-resources
. In our case the package name and target name are the same (BadgeKit
).
While SPM resource bundles are always created and included in the app during the build process, they are not automatically available at runtime outside the package. If you aren't importing and using a package's target anywhere in your code, Xcode tries to optimize by not loading that package's resource bundle (it is probably an oversight on Apple's part that storyboard references alone aren't enough to trigger this). So a workaround is needed to trick Xcode into making an SPM package's bundle available if you are only using its resources in a storyboard.
AppDelegate.swift
file as a workaround:@UIApplicationMain final class AppDelegate: UIResponder {
[…]
override init() {
super.init()
// WORKAROUND: Storyboards do not trigger the loading of resource bundles in Swift Packages.
let bundleNames = ["BadgeKit_BadgeKit"]
bundleNames.forEach { (bundleName) in
guard
let bundleURL = Bundle.main.url(forResource: bundleName, withExtension: "bundle"),
let bundle = Bundle(url: bundleURL) else {
preconditionFailure()
}
bundle.load()
}
[…]
}
[…]
}
In our example, the array bundleNames
contains a single string that correspond to the expected filename of the bundle our package will create for its resources during the build process. Xcode automatically names these bundle files as follows: [package name]_[package target name].bundle
. Note that a bundle's filename is not the same as its identifier.
If you are curious about which bundles (and their corresponding identifiers) are loaded and available at runtime, you can use the following code to troubleshoot:
let bundles = Bundle.allBundles
bundles.forEach { (bundle) in
print("Bundle identifier loaded: \(bundle.bundleIdentifier)") }
}
Upvotes: 14
Reputation: 8671
the key thing is to use Bundle.module when instantiating the storyboard
1- Add this view controller extension to the swift package:
public extension UIViewController{
public static func getStoryboardVC() -> UIViewController {
let storyboard = UIStoryboard(name: String(describing: self), bundle: Bundle.module) // Use Bundle.module
return storyboard.instantiateInitialViewController()!
}
}
The Bundle.module represents the containing package.
2- In the app, in my case the swift package is called MySwiftPackage. I call that extension method from the swift package to instantiate the view controller I want to present:
@IBAction func openCard(){
let vc = MySwiftPackage.MyViewController.getStoryboardVC() as! MySwiftPackage.MyViewController
vc.personNo = "11111"
vc.personId = "8888888"
present(vc, animated: true, completion: nil)
}
Upvotes: 11
Reputation: 1816
For Xcode 13 both top-rated answers are partially helpful (first and second), so I decided to put summary together.
In your storyboard manually select Module
for your view controller:
Pass Bundle.module
when creating a storyboard:
let storyboard = UIStoryboard(name: "ViewController", bundle: Bundle.module)
return storyboard.instantiateInitialViewController() as! ViewController
I created a simple example for this on GitHub: https://github.com/derpoliuk/swift-module-storyboard
Upvotes: 5
Reputation: 740
starting on Swift 5.3, thanks to SE-0271, you can add bundle resources on swift package manager by adding resources
on your .target
declaration.
example:
.target(
name: "HelloWorldProgram",
dependencies: [],
resources: [.process(Images), .process("README.md")]
)
if you want to learn more, I have written an article on medium, discussing this topic
Upvotes: 3