更新:XCode 14.2(以及之前的版本)不需要下面的任何步骤。包中的故事板会自动按需加载。感谢 @derpoliuk 的答案指出并提供 GitHub 示例。
从 Xcode 12.0 开始,这样就可以工作了,但需要一些额外的步骤才能完成它。
场景:
- 一个显示来自名为
BadgeKit
的包中嵌入式故事板的应用程序
- 一个名为
BadgeKit
的 Swift 包,具有 // swift-tools-version:5.3
或更高版本的 Package.swift
头部
- 在 BadgeKit 中的一个名为
BadgeKit.storyboard
的故事板
目标:
- 在应用程序故事板中添加故事板引用,并使其在应用程序中工作
步骤:
将故事板引用添加到应用程序故事板中,并进行以下配置:
故事板引用属性面板,其中 Storyboard 值为 BadgeKit
,Bundle 标识符为 BadgeKit-BadgeKit-resources
。
Xcode 会自动生成一个包(及其标识符)来保存 SPM 包中找到的资源,格式如下:[包名]-[包目标名称]-resources
。在我们的案例中,包名和目标名称相同(BadgeKit
)。
虽然 SPM 资源包始终会在构建过程中创建并包含在应用程序中,但它们在运行时外部的包中不会自动可用。如果您没有在代码中导入和使用软件包的目标,则 Xcode 尝试通过不加载该软件包的资源包来进行优化(可能是 Apple 的疏忽,因为仅使用故事板引用不足以触发此行为)。因此,需要一种解决方法来欺骗 Xcode 使 SPM 包的捆绑库可用,如果您仅在故事板中使用其资源。
将以下代码作为解决方法添加到应用程序的 AppDelegate.swift
文件中:
@UIApplicationMain final class AppDelegate: UIResponder {
[…]
override init() {
super.init()
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()
}
[…]
}
[…]
}
在我们的示例中,数组
bundleNames
包含一个字符串,该字符串对应于构建过程中我们的软件包将为其资源创建的捆绑包的预期
文件名。Xcode会自动将这些bundle文件命名为
[package name]_[package target name].bundle
。请注意,bundle的
文件名与其
标识符不同。
如果您想了解哪些bundle(以及它们的相应标识符)在运行时已加载并可用,可以使用以下代码进行故障排除:
let bundles = Bundle.allBundles
bundles.forEach { (bundle) in
print("Bundle identifier loaded: \(bundle.bundleIdentifier)") }
}
在SPM BadgeKit包中配置storyboard:
- 将“Module”填写为SPM包目标名称(“BadgeKit”)
- 取消“从目标继承模块”的勾选
Bundle.module
来实例化故事板视图控制器。如果您想检查,我添加了一个新的答案。 - fullmoon