Swift 包管理器 - Storyboard 资源包

15

我正在尝试在一个使用storyboards的项目中添加SPM支持。

目前,我们是通过UIStoryboard(name: String, bundle: String?)进行获取,但这似乎不适用于SPM,因为它没有真正的bundle。 即使打印所有bundles也找不到我们包的bundle。

有什么方法可以支持storyboards,还是说SPM只适合文件?

尝试过的方法:

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"))
5个回答

13

更新:XCode 14.2(以及之前的版本)不需要下面的任何步骤。包中的故事板会自动按需加载。感谢 @derpoliuk 的答案指出并提供 GitHub 示例。

从 Xcode 12.0 开始,这样就可以工作了,但需要一些额外的步骤才能完成它。

场景:

  • 一个显示来自名为 BadgeKit 的包中嵌入式故事板的应用程序
  • 一个名为 BadgeKit 的 Swift 包,具有 // swift-tools-version:5.3 或更高版本的 Package.swift 头部
  • 在 BadgeKit 中的一个名为 BadgeKit.storyboard 的故事板

目标:

  • 在应用程序故事板中添加故事板引用,并使其在应用程序中工作

步骤:

将故事板引用添加到应用程序故事板中,并进行以下配置:

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

故事板引用属性面板,其中 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()
        
        // 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()
        }

        []
    }

    []

}
在我们的示例中,数组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”)
  • 取消“从目标继承模块”的勾选

带有模块值BadgeKit的Storyboard属性面板


太棒了!实际上还有另一种方法也可以工作。关键是在包中使用Bundle.module来实例化故事板视图控制器。如果您想检查,我添加了一个新的答案。 - fullmoon
我收到了“找不到名为”的故事板......它仍在我的主要.app中查找,而不是我指定的Bundle中......我在代码的其他部分中使用了package目标....可能是时间问题.....添加解决方法解决了它。 - bandejapaisa
有什么想法为什么当构建为App Store或.ipa文件时它停止工作? - fullmoon
@fullmoon,就我而言,在.ipa文件和App Store中都可以正常工作。您是否在AppDelegate中使用了加载解决方法(如上所示)? - mm2001
1
我用重新开始的魔力解决了这个问题 :) 嘿嘿。我正在使用带有应用剪辑的SP。我所做的就是删除应用剪辑,重新创建并链接SP,然后它在.ipa中工作,并且应用商店的审核人员没有抱怨。 - fullmoon
显示剩余3条评论

9

关键是在实例化storyboard时使用Bundle.module

1- 将此视图控制器扩展添加到Swift软件包中:

 public extension UIViewController{
        
        public static func getStoryboardVC() -> UIViewController { 
            let storyboard = UIStoryboard(name: String(describing: self), bundle: Bundle.module) // Use Bundle.module
            return storyboard.instantiateInitialViewController()!
        }
 }

Bundle.module代表了所包含的包。

2- 在应用中,我的情况下Swift Package 名称为MySwiftPackage。我从Swift Package中调用该扩展方法来实例化我想要展示的视图控制器:

   @IBAction func openCard(){
        let vc = MySwiftPackage.MyViewController.getStoryboardVC() as! MySwiftPackage.MyViewController
        vc.personNo = "11111"
        vc.personId = "8888888"
        present(vc, animated: true, completion: nil)
    }

没错,这也可以工作!我们选择将代码更改隔离到主应用程序中,在AppDelegate的初始化器中(我的答案现在已编辑以显示此内容)。希望苹果公司将来会改进这一点,这样我们就可以避免任何解决方法。谢谢! - mm2001

4

对于Xcode 13,两个排名靠前的答案(第一个第二个)都有一定帮助,因此我决定总结一下。

要使Swift Package中的故事板工作,您需要:

  1. In your storyboard manually select Module for your view controller:

    View Controller's Module

  2. Pass Bundle.module when creating a storyboard:

    let storyboard = UIStoryboard(name: "ViewController", bundle: Bundle.module)
    return storyboard.instantiateInitialViewController() as! ViewController
    

示例:

我在GitHub上创建了一个简单的示例: https://github.com/derpoliuk/swift-module-storyboard


3

从 Swift 5.3 开始,借助于 SE-0271 提案,您可以通过在您的 .target 声明中添加 resources 来在 Swift 包管理器上添加捆绑资源。

例如:

.target(
   name: "HelloWorldProgram",
   dependencies: [], 
   resources: [.process(Images), .process("README.md")]
)

如果您想了解更多信息,我在medium上撰写了一篇文章,探讨了这个主题。

0

目前SwiftPM不支持资源。这里有一个提案在这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接