Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Package Manager - Storyboard bundle

I'm trying to add support for SPM in one of your 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"))
like image 234
Ace Green Avatar asked Sep 18 '19 20:09

Ace Green


People also ask

Does Swift have a package manager?

The Swift Package Manager is a tool for managing the distribution of Swift code. It's integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.

Does Swift package manager support iOS?

Swift Package Manager includes a build system that can build for macOS and Linux. Starting with Xcode 11, Xcode integrates with SwiftPM to provide support for including packages in iOS, macOS, watchOS, and tvOS applications.

How do I use swift package manager with Xcode?

Open your Xcode project, navigate the File tab within the macOS bar, and click on “Add Packages”. In the Add New Package window, you can select from recently used or Apple Swift Packages. Alternatively, you can search for a package via the name or the URL to the Github page.


1 Answers

As of Xcode 12.0 this sort of works, but needs a few extra steps to complete it.

Scenario:

  • an app that shows an embedded storyboard from a package named BadgeKit
  • a Swift package named BadgeKit with Package.swift header // swift-tools-version:5.3 or higher
  • a storyboard in BadgeKit called BadgeKit.storyboard

Goal:

  • Add a storyboard reference in an app storyboard and make it work in the app

Steps:

Add the storyboard reference to the app storyboard and configure it as follows:

Storyboard Reference property panel with Storyboard value "BadgeKit" and Bundle identifier "BadgeKit-BadgeKit-resources"

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.

Add this code to the app's 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)") }
}

Configure the storyboard in the SPM BadgeKit package:

  • Fill in “Module” with the SPM package target name ("BadgeKit")
  • Uncheck “Inherit Module from Target”

Storyboard property panel with Module value BadgeKit

like image 77
mm2001 Avatar answered Sep 20 '22 12:09

mm2001