Load image from CocoaPods resource bundle
Asked Answered
E

8

21

I am creating a private library that includes resources that are referenced by components in the library. The library is shared with apps using CocoaPods. In the .podspec for the library, I've included a line like this:

s.resource_bundles = {'IXMapKitResources' => ['IXMapKit/Resources/*']}

One of the resources in the bundle is an asset catalog with multiple image sets, one of which is called 'IXMKAnnotationIcons-Normal-Accident'. The following code returns a nil:

UIImage * image = [UIImage imageNamed: @"IXMKAnnotationIcons-Normal-Accident"];

I found an article on mokacoding.com describing how to load fonts from a pod, but this didn't work for me:

- (UIImage *) ixmk_imageNamed: (NSString *) name
{
    NSString * IXMKResourceBundleName = @"IXMapKitResources.bundle";
    NSString * resourceName = [NSString stringWithFormat: @"%@/%@", IXMKResourceBundleName, name];

    NSString * imageTypeString = [self ixmk_stringForImageType: imageType];
    NSURL * url = [[NSBundle mainBundle] URLForResource: resourceName withExtension: imageTypeString];

    NSData * imageData = [NSData dataWithContentsOfURL: url];
    if (imageData != nil)
    {
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) imageData);
        CGImageRef imageRef = [self ixmk_imageFromDataProvider: provider imageType: imageType];

        UIImage * image = [UIImage imageWithCGImage: imageRef];

        CFRelease(imageRef);
        CFRelease(provider);

        return image;
    }
    else
    {
        return nil;
    }
}

The CocoaPods webpage describing the resources keyword has the following warning:

We strongly recommend library developers to adopt resource bundles as there can be name collisions using the resources attribute. Moreover resources specified with this attribute are copied directly to the client target and therefore they are not optimized by Xcode.

I'm at a loss of what to do here.

Emaemaciate answered 23/5, 2014 at 17:19 Comment(1)
See my answer for similar question here: https://mcmap.net/q/660523/-access-resources-in-podSequestration
E
12

This turns out to be a problem with asset catalogs, not CocoaPods or bundles. Moving the images out of the asset catalog solved the problem. It looks like Apple doesn't support asset catalogs in secondary resource bundles. Pity.

Emaemaciate answered 28/5, 2014 at 6:1 Comment(3)
+1 for more info. Are you saying that if you put a Image.xcassets in a resource bundle, it's impossible to load images from it?!Disforest
This is still an issue as of October 2015. If you have asset catalogs (.xcassets folders) in resource bundles generated by CocoaPods version 0.36 or later, you cannot load images using [UIImage imageNamed:inBundle: ...]Sequestration
Just a side note that it seems to work depending on your OS. For me using [[NSBundle ...] imageForResource: @"myImage"] works on El Captain but not on 10.9.Skill
H
8

For example Paramount

podspec

s.resource_bundle = {
    'Paramount' => ['Sources/Paramount.bundle/*.png']
}

swift

public extension UIImage {
  static func make(name: String) -> UIImage? {
    let bundle = NSBundle(forClass: Paramount.Toolbar.self)
    return UIImage(named: "Paramount.bundle/\(name)", inBundle: bundle, compatibleWithTraitCollection: nil)
  }
}

Here Paramount.Toolbar.self can be any class in that framework

Hanahanae answered 26/4, 2016 at 21:12 Comment(0)
T
3

The obvious solution that wasn’t listed anywhere is using the Cocoapods generated bundle (which the folder actually doesn’t exist) as a NSBundle:

have a try: http://blog.flaviocaetano.com/post/cocoapods-and-resource_bundles/

it worked for me!!

Tungstite answered 2/5, 2015 at 16:9 Comment(3)
I use [NSBundle bundleForClass:NSStringFromClass(self.class)] to access the correct bundle from within cocoapod code.Jannjanna
@PeterDeWeese bundleForClass: expected a Class object actually, Besides, for NSString* and Class type, this method will return the main bundle in result. I verified it in cocoapods with static library.Berzelius
Correct. You wouldn't send it string and expect the current bundle. You would send a type from a class in the targeted bundle. It works.Jannjanna
U
2

As for me, I had to add the "s.resources" into the .podspec. Before this was missing.

Only the s.resource_bundles were set this way: 'BPPicker/Assets/.', changed it to 'BPPicker/Assets/*' as well.

#
# Be sure to run `pod lib lint BPPicker.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
  s.name             = 'BPPicker'
  s.version          = '0.1.11'
  s.summary          = 'This is the imito Body Part Picker.'

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://bitbucket.org/imito/bppicker'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'Ievgen Naloiko' => '[email protected]' }
  s.source           = { :git => 'https://[email protected]/imito/bppicker.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '9.0'

  s.source_files = 'BPPicker/Classes/**/*'

  s.resource_bundles = {
    'BPPicker' => ['BPPicker/Assets/*']
  }
    s.resources = "BPPicker/**/*.{png,json}"

    s.frameworks = 'UIKit'
    s.dependency 'ObjectMapper'
end
Undersurface answered 28/11, 2016 at 15:53 Comment(1)
Yes finally, adding the s.resources location to the podspec finally solved this issue for me as well.Prosaic
F
1

In your pod file: images are in .xcassets so it can be easily organized with 1x, 2x, 3x

s.resources = 'PodName/Assets/**'

Usage: Swift

let bundle = Bundle(for: ClassThatWillUseTheImage.self)
let image = UIImage(named: "close", in: bundle, with: .none)

Usage: Objective-C

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UIImage *image = [UIImage imageNamed:@"close" inBundle:bundle withConfiguration:nil];

Tested in Version 11.4 (11E146) iOS 13.0+

Using #imageLiteral(resourceName: "close") does not work however, it shows the image being read but will throw and error.

Flivver answered 9/4, 2020 at 10:42 Comment(0)
C
0

I made a pod that can locate the resource bundles in pods. I hope it will end the "CocoaPods resource bundle" problem once and for all.

https://github.com/haifengkao/PodAsset

Chiropractor answered 7/4, 2016 at 3:50 Comment(1)
Downvote. I just check the method bundlePathForPod's implementation, it mixes the pod name and resource bundle name, for the pure pod project users, there is no need for them to know the resource bundle name.Berzelius
A
0

This answer works with Swift3 and Swift4.

Create a function under the pod directory into the file.

func loadImageBundle(named imageName:String) -> UIImage? {
    let podBundle = Bundle(for: self.classForCoder)
    if let bundleURL = podBundle.url(forResource: "Update with directory name", withExtension: "bundle")
    {
        let imageBundel = Bundle(url:bundleURL )
        let image = UIImage(named: imageName, in: imageBundel, compatibleWith: nil)
        return image
    }
    return nil
}

Usage

imageView.image = loadImageBundle(named: "imagefile")
Achromatize answered 2/1, 2018 at 16:0 Comment(0)
E
0

There are a couple of different issues here:

Prior to iOS 8.0, images from asset catalogs could only be read if the asset catalog was compiled into the app's main bundle. iOS 8 introduced init(named:in:compatibleWith:) to solve this part.

However, even in 2023 using the iOS 8 method, the issue of specifically being unable to load images and colors from an asset catalog seems to still be able to occur due, although it is not 100% reproducible.

The way this happens is that Pod authors (understandably) name one of their resource bundles with the same name as their Pod.

If the use_frameworks! directive is used by the app developer in their Podfile, CocoaPods creates a framework named something like (BarKit.framework*), and inside of that creates a bundle called BarKit.bundle containing the Assets.car file (you can use a tool like carDump to verify that the assets are indeed present in this file).

Crucially both the framework and the bundle have their product name (in their corresponding Info.plist files) set to org.cocoapods.BarKit. This appears to confuse the asset "de-compiler", although everything else in the bundle will continue to work.

A way to see if this fix helps is to manually rename the bundle ID of the resource bundle to something different from its companion framework.

  • Sorry, but "Foo" here might have been a bit racy for a family website.
Eusebioeusebius answered 6/4, 2023 at 23:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.