在CocoaPods中使用资源包

16

我正在制作一个Pod(MySDK),并希望从单独的资源包中加载 CocoaPods 生成的资产。

但是,我无法使其工作。

这是我尝试加载storyboard的方式:

let storyBoard = UIStoryboard(name: "SDK", bundle: Bundle(identifier:"org.cocoapods.SchedJoulesSDK"))

这会导致错误:

'Could not find a storyboard named 'SDK' in bundle

在Xcode中已添加bundle:

我的podspec如下所示:

  s.resource_bundles = {
    'MySDK' => ['SDK/*/*.{xib,storyboard,xcassets}']
  }
任何想法?

可能是重复问题: https://dev59.com/hlsV5IYBdhLWcg3w2iDR - Popmedic
4个回答

44
如果您在CocoaPods PodSpec文件中使用resourceresources,则告诉Cocoapods这些是您的库在运行时加载的资源文件。
如果您的库构建为动态框架,则这些文件只会复制到该框架的资源文件夹路径中,一切都将很好。但是,如果您的库构建为静态库,则这些文件将被复制到主应用程序包(.app)的资源文件夹中,这可能是一个问题,因为该主应用程序可能已经有了该名称的资源或另一个Pod可能有了该名称的资源,在这种情况下,这些文件将互相覆盖。而Pod是作为动态框架还是静态库构建的并未在PodSpec中指定,而是由集成您的Pod的应用程序的Podfile指定。
因此,对于具有资源的Pod,强烈建议改用resource_bundles
在您的情况下,这些行
s.resource_bundles = {
    'MySDK' => ['SDK/*/*.{xib,storyboard,xcassets}'] }

告诉CocoaPods创建一个名为MySDKMySDK.bundle)的资源包,并将所有匹配该模式的文件放入该资源包中。如果您的Pod是作为框架构建的,则此捆绑包位于框架捆绑包的资源文件夹中;如果作为静态库构建,则将该捆绑包复制到主应用程序捆绑包的资源文件夹中,如果您将捆绑包命名为与Pod相同的方式,则应该是安全的(您不应将其命名为“MySDK”,而应该是“SchedJoulesSDK”)。
此捆绑包将具有与您的Pod相同的标识符,但是当构建动态框架时,您的框架捆绑包也将具有该标识符,然后在通过标识符加载它时将加载哪个捆绑包是未定义的行为(在我的测试中,外部捆绑包总是获胜)。
正确的代码应该像这样(尚未经过测试):
// Get the bundle containing the binary with the current class.
// If frameworks are used, this is the frameworks bundle (.framework),
// if static libraries are used, this is the main app bundle (.app).
let myBundle = Bundle(for: Self.self)

// Get the URL to the resource bundle within the bundle
// of the current class.
guard let resourceBundleURL = myBundle.url(
    forResource: "MySDK", withExtension: "bundle")
    else { fatalError("MySDK.bundle not found!") }

// Create a bundle object for the bundle found at that URL.
guard let resourceBundle = Bundle(url: resourceBundleURL)
    else { fatalError("Cannot access MySDK.bundle!") }

// Load your resources from this bundle.
let storyBoard = UIStoryboard(name: "SDK", bundle: resourceBundle)

由于resourceBundle在运行时无法更改,因此只需创建一次(例如,在应用程序启动或初始化框架时)并将其存储在全局变量(或全局类属性)中即可安全使用,这样当需要时您就可以随时使用它(bundle对象也几乎不会使用任何RAM内存,因为它仅封装元数据):

final class SchedJoulesSDK {
    static let resourceBundle: Bundle = {
        let myBundle = Bundle(for: SchedJoulesSDK.self)

        guard let resourceBundleURL = myBundle.url(
            forResource: "MySDK", withExtension: "bundle")
            else { fatalError("MySDK.bundle not found!") }

        guard let resourceBundle = Bundle(url: resourceBundleURL)
            else { fatalError("Cannot access MySDK.bundle!") }

        return resourceBundle
    }()
}

该属性是延迟初始化的(对于static let属性来说,这是默认值,不需要使用lazy关键字),系统确保只会发生一次初始化,因为let属性一旦初始化后就不能更改。请注意,在此上下文中,您不能使用Self.self,而需要使用实际的类名。

现在,您可以在代码中随时使用该bundle:

let storyBoard = UIStoryboard(name: "SDK", 
    bundle: SchedJoulesSDK.resourceBundle)

讲解得非常清晰!有一个问题,对于图像和其他资源也是同样的操作吗?如果是的话,如何将 bundle 中的图像添加到私有 pod 项目中的 Xib 文件中以在运行时被识别呢? - Dipika
@Dipika 这适用于您的 Pod 包含的所有资源,包括图像、字体、声音等。至于在 XIB 中引用图像:如果 XIB 通过名称引用图像,据我所知,系统将首先尝试在与 XIB 相同的捆绑包中查找该图像(无论是在该捆绑包资源文件夹中还是在其资产目录中)。只要您的图像和 XIB 都在同一个资源捆绑包中,它就应该可以正常工作。您试过了但没有成功吗?那么也许您应该在 SO 上发起一个详细的样例新问题。 - Mecki
我刚试了一下,它正常工作了,谢谢。 - Dipika

6

您可以像这样使用“...

s.resource  = "icon.png" //for single file

或者

s.resources = "Resources/*.png" //for png file

或者

s.resources = "Resources/**/*.{png,storyboard}" //for storyboard and png files

或者

s.resource_bundles = {
   "<ResourceBundleName>" => ["path/to/resources/*/**"]
}

4
这怎么能帮助我呢? - Balázs Vincze
我将XCAssets目录放到我的Pod中,并使用了第三个选项。它运行成功了! - Denis Kutlubaev

3
这是我最终得到的结果...
import Foundation

public extension Bundle {

    public static func resourceBundle(for frameworkClass: AnyClass) -> Bundle {
        guard let moduleName = String(reflecting: frameworkClass).components(separatedBy: ".").first else {
            fatalError("Couldn't determine module name from class \(frameworkClass)")
        }

        let frameworkBundle = Bundle(for: frameworkClass)

        guard let resourceBundleURL = frameworkBundle.url(forResource: moduleName, withExtension: "bundle"),
              let resourceBundle = Bundle(url: resourceBundleURL) else {
            fatalError("\(moduleName).bundle not found in \(frameworkBundle)")
        }

        return resourceBundle
    }
}

示例用法...

class MyViewController: UIViewController {
    init() {
        super.init(nibName: nil, bundle: Bundle.resourceBundle(for: Self.self))
    }
}

或者

tableView.register(UINib(nibName: Self.loginStatusCellID, bundle: Bundle.resourceBundle(for: Self.self)), forCellReuseIdentifier: Self.loginStatusCellID)

1

我有同样的问题,如果我只使用bundle行:

    s.resource_bundles = {
        'MySDK' => ['SDK/*/*.{xib,storyboard,xcassets}']
    }

当我引用我的storyboard时,程序运行时崩溃。但是,如果我像这样明确地将每个storyboard添加为资源:
s.resources = ["Resources/MyStoryboard.storyboard", "Resources/MyStoryboard2.storyboard"]

一切正常。我认为我们不应该需要明确将每个故事板添加为资源,但我无法通过其他方式使其工作。
你可以尝试的一件事是将你的资源包引用更改为使用“**”命名法递归搜索你的文件夹,像这样:
s.resource_bundles = {
    'MySDK' => ['SDK/**/*.{xib,storyboard,xcassets}']
}

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