33

I have struggled a lot to how to load resource in cocoapods resource_bundle.

The following is what i put in the .podspecs file.

s.source_files = 'XDCoreLib/Pod/Classes/**/*'
s.resource_bundles = {
'XDCoreLib' => ['XDCoreLib/Pod/Resources/**/*.{png,storyboard}']
}

This is what I am trying to do from the main project.

let bundle = NSBundle(forClass: XDWebViewController.self)
let image = UIImage(named: "ic_arrow_back", inBundle: bundle, compatibleWithTraitCollection: nil)
print(image)

I did see the picture in the XDCoreLib.bundle, but it return a nil.

Joe SHI
  • 1,734
  • 4
  • 20
  • 39
  • Don't do that, just copy these resources into your project because these resources in pods can be updated / removed any time. – Tim007 Mar 08 '16 at 03:05
  • 5
    This pod is maintained by ourselves. The project size is growing bigger and bigger, and we want to separate each module. – Joe SHI Mar 09 '16 at 06:57

10 Answers10

58

I struggled with a similar issue for a while. The resource bundle for a pod is actually a separate bundle from where your code lives. Try the following:

Swift 5

let frameworkBundle = Bundle(for: XDWebViewController.self)
let bundleURL = frameworkBundle.resourceURL?.appendingPathComponent("XDCoreLib.bundle")
let resourceBundle = Bundle(url: bundleURL!)

let image = UIImage(named: "ic_arrow_back", in: resourceBundle, compatibleWith: nil)
print(image)

-- ORIGINAL ANSWER --

let frameworkBundle = NSBundle(forClass: XDWebViewController.self)
let bundleURL = frameworkBundle.resourceURL?.URLByAppendingPathComponent("XDCoreLib.bundle")
let resourceBundle = NSBundle(URL: bundleURL!)
let image = UIImage(named: "ic_arrow_back", inBundle: resourceBundle, compatibleWithTraitCollection: nil)
print(image)
gmogames
  • 2,993
  • 1
  • 28
  • 40
Quaid
  • 596
  • 5
  • 2
  • @Quaid What is XDWebViewController.self in first line – Siddh Mar 17 '17 at 11:47
  • 2
    @Siddh The `XDWebViewController.self` should be a class inside the pod. – Matt Robinson May 23 '17 at 17:12
  • 3
    expanding on what @MattRobinson said, you can substitute `XDWebViewController.self` for any class that is inside the pod. the goal of that line of code is to identify the framework bundle by using any class belonging to the framework (pod) – jarrodparkes Jan 03 '18 at 16:38
  • 1
    @Quaid I'm able to load resources this way, but one question when I'm printing bundle object in debug window this "NSBundle (not yet loaded)" is coming. what is this "not yet loaded" meaning here. – Dipika May 19 '21 at 17:11
13

I don't think any of the other answers have described the relationship to the Podspec. The bundle URL uses the name of the pod and then .bundle to find the resource bundle. This approach works with asset catalogs for accessing pod images both inside and outside of the pod.

Podspec

Pod::Spec.new do |s|
  s.name             = 'PodName'
  s.resource_bundles = {
    'ResourceBundleName' => ['path/to/resources/*/**']
  }
end

Objective-C

// grab bundle using `Class` in pod source (`self`, in this case)
NSBundle *bundle = [NSBundle bundleForClass:self.classForCoder];
NSURL *bundleURL = [[bundle resourceURL] URLByAppendingPathComponent:@"PodName.bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithURL:bundleURL];
UIImage *podImage = [UIImage imageNamed:@"image_name" inBundle:resourceBundle compatibleWithTraitCollection:nil];

Swift

See this answer.

Matt Robinson
  • 799
  • 7
  • 22
6

This will work

In .podspec add s.resources in addition to s.resource_bundles (pod install afterwards)

s.resources = 'XDCoreLib/Pod/Resources/**/*.{png,storyboard}'

Then in your code, its as easy as:

let image = UIImage(named: "image_name.png", in: Bundle(for: type(of: self)), compatibleWith: nil)

Note: You may need to run pod install after updating the .podspec to have the changes to your Xcode proj

Shaheen Ghiassy
  • 7,397
  • 3
  • 40
  • 40
  • 1
    it should be __s.resource = 'XDCoreLib/Pod/Resources/**/*.{png,storyboard}'__ rather then __s.resources = 'XDCoreLib/Pod/Resources/**/*.{png,storyboard}'__ – pk75 Jun 18 '19 at 05:47
  • Please note this will only work if your cocoapod is not a static framework. make sure s.static_framework should be false in podspec. For static frameworks the only solution is to use separate bundles. – abhinavroy23 Dec 05 '19 at 12:52
4

You can just check the ID of the Bundle by selecting the Pods project, selecting the desired target, and making sure you're on the General tab.

enter image description here

Then in code you can load say an image in that Pod like so:

backgroundImageView.image = UIImage(named: "business_fellas", in: Bundle(identifier: "org.cocoapods.StandardLibrary3-0"), compatibleWith: nil)
jhelzer
  • 295
  • 2
  • 12
  • This works for me when building a static library but it doesn't work for me when building a framework. That's because the framework and the resource bundle will both have the same bundle identifier and the bundle returned to by `Bundle()` will be the framework bundle (`.framework`) and the the resource bundle (`.bundle`) which contains the resources. – Mecki May 18 '21 at 21:46
2

Can create an extension to access the framework bundle easier in your framework source code

extension Bundle {
   static func getResourcesBundle() -> Bundle? {
      let bundle = Bundle(for {your class}.self)
      guard let resourcesBundleUrl = bundle.resourceURL?.appendingPathComponent({bundle name}) else {
         return nil
      }
      return Bundle(url: resourcesBundleUrl)
   }
}
PrimaryChicken
  • 963
  • 1
  • 8
  • 29
1

Just for the record: I wanted to open a NIB from a Category stored in a CocoaPods library. Of course [self classForCoder] gave UIKit back (because of the Category), so the above methods didn't work.

I resolved the issue using some hardcoded paths:

NSURL *bundleURL = [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent:@"Frameworks/MyCocoaPodLibraryName.framework"];
NSBundle *podBundle = [NSBundle bundleWithURL:bundleURL];

CustomView *customView = [[podBundle loadNibNamed:@"CustomView" owner:self options:nil] firstObject];
gklka
  • 2,459
  • 1
  • 26
  • 53
1

It seems like for some reason **/*.{extensions} pattern is required for this to work I ended up creating my podspec like this

s.resource_bundle = { '<BundleName>' => 'Pod/Resources/**/*.storyboard' }
s.resource = 'Pod/Resources/**/*.storyboard'

even though my actual path is Pod/Resources/.storyboard*

And then to find my bundle I used @Jhelzer's answer although any way to get bundle e.g. by class or URL or bundleID will work after above setup.

You can also have a look into this answer for more refrences.

pk75
  • 511
  • 7
  • 18
0

Interesting thing is when you need to do it in the source files that are also in the cocoapod library I faced it when used a xib and plist files in my pod.

That was solved in the next way. Example of loading a xib file:

let bundle = Bundle(for: self.classForCoder)
let viewController = CocoapodViewController(nibName: "CocoapodViewController", bundle: bundle)
RomanN
  • 2,097
  • 2
  • 15
  • 14
0
import class Foundation.Bundle

private class BundleFinder {}

extension Foundation.Bundle {
    /// Returns the resource bundle associated with the current Swift module.
    static var module: Bundle = {
        let bundleName = "ID3TagEditor_ID3TagEditorTests"

        let candidates = [
            // Bundle should be present here when the package is linked into an App.
            Bundle.main.resourceURL,

            // Bundle should be present here when the package is linked into a framework.
            Bundle(for: BundleFinder.self).resourceURL,

            // For command-line tools.
            Bundle.main.bundleURL,
        ]

        for candidate in candidates {
            let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
            if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
                return bundle
            }
        }
        fatalError("unable to find bundle named ID3TagEditor_ID3TagEditorTests")
    }()
}

From: https://www.fabrizioduroni.it/2020/10/19/swift-package-manager-resources/

irons163
  • 54
  • 6
0

For me the following worked because I'm using a static pod.


s.resource_bundle = '<BundleName>' => 'Path to resources'
s.resource = 'Path to resources'

Skywalker6131
  • 41
  • 1
  • 7